a
This commit is contained in:
@@ -1,56 +1,79 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
title="角色切换"
|
||||
:title="dialogTitle"
|
||||
width="50%"
|
||||
:show-close="false"
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
:before-close="handleBeforeClose"
|
||||
center
|
||||
>
|
||||
<el-form>
|
||||
<!-- <el-form-item label="学校">-->
|
||||
<!-- <el-tag>{{schoolName}}</el-tag>-->
|
||||
<!-- </el-form-item>-->
|
||||
<el-form-item label="角色" class="role-form-item">
|
||||
<el-form-item class="role-form-item">
|
||||
<el-radio-group v-model="radio" class="role-radio-group" @change="handleChangeRole">
|
||||
<el-radio-button
|
||||
v-for="item in allRole"
|
||||
:key="item.roleCode"
|
||||
:label="item.roleCode"
|
||||
>
|
||||
{{ item.roleName }}
|
||||
</el-radio-button>
|
||||
<template v-for="(roles, groupName) in allRoleGroups" :key="groupName">
|
||||
<div class="role-group">
|
||||
<el-divider>{{ groupName }}</el-divider>
|
||||
<el-radio-button
|
||||
v-for="item in roles"
|
||||
:key="item.roleCode"
|
||||
:label="item.roleCode"
|
||||
>
|
||||
{{ item.roleName }}
|
||||
</el-radio-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
|
||||
<template #footer>
|
||||
<!-- <el-button type="primary" @click="handleChangeRole">切换</el-button>-->
|
||||
<template v-if="!requireSelectToClose" #footer>
|
||||
<el-button @click="handleFooterClose">关 闭</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, toRef } from 'vue'
|
||||
import { listAllRole } from '/@/api/admin/role'
|
||||
import { Local } from '/@/utils/storage'
|
||||
import { useMessage } from '/@/hooks/message'
|
||||
|
||||
/** 弹框标题,如「角色切换」「登录角色选择」 */
|
||||
const props = withDefaults(
|
||||
defineProps<{ title?: string; requireSelectToClose?: boolean }>(),
|
||||
{ title: '角色切换', requireSelectToClose: false }
|
||||
)
|
||||
const dialogTitle = computed(() => props.title)
|
||||
|
||||
const visible = ref(false)
|
||||
const radio = ref('')
|
||||
const allRole = reactive<any[]>([])
|
||||
/** 按分组名分组的角色列表:{ "未分组": [{ roleId, roleName, roleCode, ... }], ... } */
|
||||
const allRoleGroups = ref<Record<string, any[]>>({})
|
||||
const requireSelectToClose = toRef(props, 'requireSelectToClose')
|
||||
|
||||
const open = () => {
|
||||
if (visible.value) return
|
||||
visible.value = true
|
||||
listAllRole().then((res) => {
|
||||
Object.assign(allRole, res.data)
|
||||
allRoleGroups.value = res.data && typeof res.data === 'object' && !Array.isArray(res.data)
|
||||
? res.data
|
||||
: { '未分组': Array.isArray(res.data) ? res.data : [] }
|
||||
radio.value = Local.get('roleCode')
|
||||
visible.value = true
|
||||
})
|
||||
}
|
||||
|
||||
/** 根据 roleCode 从分组数据中查找角色 */
|
||||
const findRoleByCode = (code: string) => {
|
||||
for (const roles of Object.values(allRoleGroups.value)) {
|
||||
if (!Array.isArray(roles)) continue
|
||||
const found = roles.find((r: any) => r.roleCode === code)
|
||||
if (found) return found
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
const canClose = () => {
|
||||
if (!radio.value) {
|
||||
useMessage().warning('请选择一个角色')
|
||||
@@ -60,6 +83,10 @@ const canClose = () => {
|
||||
}
|
||||
|
||||
const handleBeforeClose = (done: () => void) => {
|
||||
if (requireSelectToClose.value) {
|
||||
useMessage().warning('请先选择登录角色')
|
||||
return
|
||||
}
|
||||
if (!canClose()) return
|
||||
done()
|
||||
}
|
||||
@@ -70,7 +97,7 @@ const handleFooterClose = () => {
|
||||
}
|
||||
|
||||
const handleChangeRole = (label: string) => {
|
||||
const obj = allRole.find((v: any) => v.roleCode === label)
|
||||
const obj = findRoleByCode(label)
|
||||
if (!obj) return
|
||||
Local.set('roleCode', obj.roleCode)
|
||||
Local.set('roleName', obj.roleName)
|
||||
@@ -94,6 +121,18 @@ defineExpose({
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
.role-group {
|
||||
margin-bottom: 12px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
.group-name {
|
||||
font-size: 13px;
|
||||
color: var(--el-text-color-secondary);
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.role-radio-group {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
<template>
|
||||
<el-dialog v-model="visible" width="50%" :close-on-click-modal="false" :close-on-press-escape="false" :show-close="false" center>
|
||||
<template #title>
|
||||
<div style="margin: 0 auto;width:100%;text-align:center;font-size:18px;font-weight:bold;">
|
||||
登录角色选择
|
||||
</div>
|
||||
</template>
|
||||
<div style="margin: 0 auto;width:100%;text-align:center;font-size:18px;font-weight:bold;">
|
||||
<el-radio-group v-model="radio">
|
||||
<el-radio-button v-for="(item,index) in allRole" :key="index" :label="item.roleCode" @click.native="handleChangeRole(item.roleCode)">{{item.roleName}}</el-radio-button>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {listAllRole} from '/@/api/admin/role'
|
||||
import {Local, Session} from '/@/utils/storage';
|
||||
import {useMessage} from "/@/hooks/message";
|
||||
// import {querySchoolName} from "/@/api/admin/tenant"
|
||||
|
||||
const visible=ref(false)
|
||||
const radio=ref('')
|
||||
const allRole=reactive([])
|
||||
const schoolName=ref('')
|
||||
|
||||
const open=()=>{
|
||||
// handleQuerySchoolName()
|
||||
listAllRole().then(res=>{
|
||||
Object.assign(allRole,res.data)
|
||||
radio.value=Local.get("roleCode")
|
||||
visible.value=true
|
||||
})
|
||||
}
|
||||
const handleChangeRole=(label:any)=>{
|
||||
let obj:any=allRole.find((v:any) => v.roleCode == label)
|
||||
Local.set("roleCode",obj.roleCode)
|
||||
Local.set("roleName",obj.roleName)
|
||||
Local.set("roleId",obj.roleId)
|
||||
useMessage().success("操作成功")
|
||||
setTimeout(()=>{
|
||||
window.location.reload()
|
||||
},500)
|
||||
}
|
||||
|
||||
// const handleQuerySchoolName=()=>{
|
||||
// querySchoolName({id:Session.get("tenantId")}).then((res:any)=>{
|
||||
// schoolName.value=res.data
|
||||
// })
|
||||
// }
|
||||
|
||||
defineExpose({
|
||||
open
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -7,6 +7,12 @@
|
||||
<el-form-item :label="$t('sysrole.roleCode')" prop="roleCode">
|
||||
<el-input :placeholder="$t('sysrole.please_enter_the_role_Code')" :disabled="form.roleId !== ''" clearable v-model="form.roleCode"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="分组" prop="roleGroup">
|
||||
<el-input placeholder="用于列表树状分组展示,可留空" clearable v-model="form.roleGroup" maxlength="50" show-word-limit />
|
||||
</el-form-item>
|
||||
<el-form-item label="排序" prop="roleSort">
|
||||
<el-input-number v-model="form.roleSort" :min="0" :max="9999" placeholder="数值越小越靠前" controls-position="right" style="width: 140px" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('sysrole.roleDesc')" prop="roleDesc">
|
||||
<el-input
|
||||
maxlength="100"
|
||||
@@ -67,6 +73,8 @@ const form = reactive({
|
||||
roleId: '',
|
||||
roleName: '',
|
||||
roleCode: '',
|
||||
roleGroup: '',
|
||||
roleSort: 0,
|
||||
roleDesc: '',
|
||||
dsType: 0,
|
||||
dsScope: '',
|
||||
@@ -187,6 +195,7 @@ const getRoleData = (id: string) => {
|
||||
// 获取部门数据
|
||||
getObj(id).then((res: any) => {
|
||||
Object.assign(form, res.data);
|
||||
if (res.data.roleSort == null) form.roleSort = 0;
|
||||
if (res.data.dsScope) {
|
||||
dataForm.checkedDsScope = res.data.dsScope.split(',');
|
||||
} else {
|
||||
|
||||
@@ -25,6 +25,12 @@
|
||||
<el-button plain :disabled="multiple" icon="Delete" type="primary" class="ml10" v-auth="'sys_user_del'" @click="handleDelete(selectObjs)">
|
||||
{{ $t('common.delBtn') }}
|
||||
</el-button>
|
||||
<el-button plain :disabled="multiple" type="primary" class="ml10" v-auth="'sys_role_edit'" @click="showBatchGroupDialog = true">
|
||||
批量指定分组
|
||||
</el-button>
|
||||
<el-button plain :disabled="selectObjs.length !== 1" type="primary" class="ml10" v-auth="'sys_role_edit'" @click="openAssignUserDialog">
|
||||
批量指定关联用户
|
||||
</el-button>
|
||||
<right-toolbar
|
||||
v-model:showSearch="showSearch"
|
||||
:export="'sys_role_export'"
|
||||
@@ -36,55 +42,165 @@
|
||||
</div>
|
||||
</el-row>
|
||||
<el-table
|
||||
:data="state.dataList"
|
||||
:data="roleTreeData"
|
||||
v-loading="state.loading"
|
||||
style="width: 100%"
|
||||
row-key="roleId"
|
||||
@selection-change="handleSelectionChange"
|
||||
row-key="roleId"
|
||||
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
|
||||
|
||||
@selection-change="handleSelectionChange"
|
||||
border
|
||||
:cell-style="tableStyle.cellStyle"
|
||||
:header-cell-style="tableStyle.headerCellStyle"
|
||||
>
|
||||
<el-table-column type="selection" :selectable="handleSelectable" width="50" align="center" />
|
||||
<el-table-column type="index" :label="$t('sysrole.index')" width="80" />
|
||||
<el-table-column prop="roleName" :label="$t('sysrole.roleName')" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="roleCode" :label="$t('sysrole.roleCode')" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="roleDesc" :label="$t('sysrole.roleDesc')" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="data_authority" :label="$t('sysrole.data_authority')" show-overflow-tooltip>
|
||||
<el-table-column type="selection" :selectable="handleSelectable" width="50" align="left" />
|
||||
<el-table-column type="index" :label="$t('sysrole.index')" width="80">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="dictType" :value="scope.row.dsType"></dict-tag>
|
||||
<span v-if="scope.row._isGroup">—</span>
|
||||
<span v-else>{{ scope.$index + 1 }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" :label="$t('sysrole.createTime')" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column :label="$t('common.action')" width="250">
|
||||
<el-table-column prop="roleName" :label="$t('sysrole.roleName')" show-overflow-tooltip min-width="140" align="left">
|
||||
<template #default="scope">
|
||||
<el-button text type="primary" icon="edit-pen" v-auth="'sys_role_edit'" @click="roleDialogRef.openDialog(scope.row.roleId)">{{
|
||||
$t('common.editBtn')
|
||||
}}</el-button>
|
||||
|
||||
<el-button text type="primary" icon="turn-off" v-auth="'sys_role_perm'" @click="permessionRef.openDialog(scope.row)">{{
|
||||
$t('sysrole.permissionTip')
|
||||
}}</el-button>
|
||||
|
||||
<el-tooltip :content="$t('sysrole.deleteDisabledTip')" :disabled="scope.row.roleId !== '1'" placement="top">
|
||||
<span style="margin-left: 12px">
|
||||
<el-button
|
||||
text
|
||||
type="primary"
|
||||
icon="delete"
|
||||
:disabled="scope.row.roleId === '1'"
|
||||
v-auth="'sys_role_del'"
|
||||
@click="handleDelete([scope.row.roleId])"
|
||||
>{{ $t('common.delBtn') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
<span v-if="scope.row._isGroup" class="role-group-name">{{ scope.row.roleName }}</span>
|
||||
<span v-else>{{ scope.row.roleName }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="roleSort" label="排序" width="80" align="center">
|
||||
<template #default="scope">{{ scope.row._isGroup ? '—' : (scope.row.roleSort ?? 0) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="roleCode" :label="$t('sysrole.roleCode')" show-overflow-tooltip min-width="120">
|
||||
<template #default="scope">{{ scope.row._isGroup ? '—' : scope.row.roleCode }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="roleDesc" :label="$t('sysrole.roleDesc')" show-overflow-tooltip min-width="140">
|
||||
<template #default="scope">{{ scope.row._isGroup ? '—' : scope.row.roleDesc }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="data_authority" :label="$t('sysrole.data_authority')" show-overflow-tooltip width="100">
|
||||
<template #default="scope">
|
||||
<template v-if="scope.row._isGroup">—</template>
|
||||
<dict-tag v-else :options="dictType" :value="scope.row.dsType"></dict-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" :label="$t('sysrole.createTime')" show-overflow-tooltip width="165">
|
||||
<template #default="scope">{{ scope.row._isGroup ? '—' : scope.row.createTime }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('common.action')" width="250" fixed="right">
|
||||
<template #default="scope">
|
||||
<template v-if="scope.row._isGroup">—</template>
|
||||
<template v-else>
|
||||
<el-button text type="primary" icon="edit-pen" v-auth="'sys_role_edit'" @click="roleDialogRef.openDialog(scope.row.roleId)">{{
|
||||
$t('common.editBtn')
|
||||
}}</el-button>
|
||||
<el-button text type="primary" icon="turn-off" v-auth="'sys_role_perm'" @click="permessionRef.openDialog(scope.row)">{{
|
||||
$t('sysrole.permissionTip')
|
||||
}}</el-button>
|
||||
<el-button text type="primary" icon="user" v-auth="'sys_role_view'" @click="openRoleUsersDialog(scope.row)">查看关联用户</el-button>
|
||||
<el-tooltip :content="$t('sysrole.deleteDisabledTip')" :disabled="scope.row.roleId !== '1'" placement="top">
|
||||
<span style="margin-left: 12px">
|
||||
<el-button
|
||||
text
|
||||
type="primary"
|
||||
icon="delete"
|
||||
:disabled="scope.row.roleId === '1'"
|
||||
v-auth="'sys_role_del'"
|
||||
@click="handleDelete([scope.row.roleId])"
|
||||
>{{ $t('common.delBtn') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" v-bind="state.pagination" />
|
||||
</div>
|
||||
|
||||
<!-- 批量指定分组弹窗 -->
|
||||
<el-dialog v-model="showBatchGroupDialog" title="批量指定分组" width="400px" @close="batchGroupName = ''">
|
||||
<el-form label-width="80px">
|
||||
<el-form-item label="分组名称">
|
||||
<el-input v-model="batchGroupName" placeholder="输入分组名,留空为未分组" clearable maxlength="50" show-word-limit />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="showBatchGroupDialog = false">取消</el-button>
|
||||
<el-button type="primary" :loading="batchGroupLoading" @click="handleBatchGroup">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 批量指定关联用户弹窗 -->
|
||||
<el-dialog
|
||||
v-model="showAssignUserDialog"
|
||||
title="批量指定关联用户"
|
||||
width="720px"
|
||||
destroy-on-close
|
||||
@close="assignUserKeyword = ''; assignUserType = ''; assignUserList = []; assignUserTree = []; assignSelectedIds = []">
|
||||
<template v-if="assignCurrentRole">
|
||||
<div class="mb12"><el-text type="info">当前角色:{{ assignCurrentRole.roleName }}({{ assignCurrentRole.roleCode }})</el-text></div>
|
||||
<el-form :inline="true" class="mb12">
|
||||
<el-form-item label="用户类型" required>
|
||||
<el-radio-group v-model="assignUserType">
|
||||
<el-radio label="1">教职工</el-radio>
|
||||
<el-radio label="2">学生</el-radio>
|
||||
<el-radio label="3">驻校单位</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="姓名/工号">
|
||||
<el-input v-model="assignUserKeyword" placeholder="姓名或工号检索" clearable style="width: 180px" @keyup.enter="loadAssignUserList" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" :loading="assignUserLoading" @click="loadAssignUserList">查询</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="assign-user-tip mb8" v-if="!assignUserType">请先选择用户类型后再查询</div>
|
||||
<el-table
|
||||
v-else
|
||||
:data="assignUserTree"
|
||||
row-key="id"
|
||||
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
|
||||
default-expand-all
|
||||
v-loading="assignUserLoading"
|
||||
max-height="360"
|
||||
border
|
||||
@selection-change="handleAssignUserSelectionChange">
|
||||
<el-table-column type="selection" width="50" align="left" :selectable="(row: any) => !row._isDept" />
|
||||
<el-table-column prop="label" label="部门 / 姓名" min-width="200">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row._isDept" class="dept-row">{{ row.label }}</span>
|
||||
<span v-else>{{ row.realName || row.username || row.label }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="username" label="工号" width="120">
|
||||
<template #default="{ row }">{{ row._isDept ? '—' : row.username }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="deptName" label="部门" width="140">
|
||||
<template #default="{ row }">{{ row._isDept ? '—' : (row.deptName || '—') }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</template>
|
||||
<template #footer>
|
||||
<el-button @click="showAssignUserDialog = false">取消</el-button>
|
||||
<el-button type="primary" :loading="assignSubmitLoading" :disabled="assignSelectedIds.length === 0" @click="handleAssignUsersSubmit">确定(已选 {{ assignSelectedIds.length }} 人)</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 查看角色关联用户弹窗 -->
|
||||
<el-dialog v-model="showRoleUsersDialog" title="关联用户" width="560px" destroy-on-close>
|
||||
<template v-if="currentRoleForUsers">
|
||||
<div class="mb12"><el-text type="info">角色:{{ currentRoleForUsers.roleName }}({{ currentRoleForUsers.roleCode }})</el-text></div>
|
||||
<el-table :data="roleUsersList" v-loading="roleUsersLoading" max-height="400" border>
|
||||
<el-table-column prop="deptName" label="部门" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column prop="realName" label="姓名" width="100" show-overflow-tooltip />
|
||||
<el-table-column prop="username" label="工号" width="120" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="80" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button text type="danger" icon="delete" v-auth="'sys_role_edit'" @click="handleUnassignUser(row)">解除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="mt8" v-if="!roleUsersLoading && roleUsersList.length === 0"><el-text type="info">该角色下暂无关联用户</el-text></div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 角色编辑、新增 -->
|
||||
<role-dialog ref="roleDialogRef" @refresh="getDataList()" />
|
||||
<!-- 导入角色 -->
|
||||
@@ -101,8 +217,10 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="systemRole">
|
||||
import { computed, reactive, ref } from 'vue';
|
||||
import { BasicTableProps, useTable } from '/@/hooks/table';
|
||||
import { pageList, delObj } from '/@/api/admin/role';
|
||||
import { list, delObj, batchUpdateRoleGroup, assignUsersToRole, getUsersByRoleId, unassignUserFromRole } from '/@/api/admin/role';
|
||||
import { pageList as userPageList } from '/@/api/admin/user';
|
||||
import { useMessage, useMessageBox } from '/@/hooks/message';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
@@ -121,15 +239,103 @@ const showSearch = ref(true);
|
||||
const selectObjs = ref([]) as any;
|
||||
// 是否可以多选
|
||||
const multiple = ref(true);
|
||||
// 批量指定分组
|
||||
const showBatchGroupDialog = ref(false);
|
||||
const batchGroupName = ref('');
|
||||
const batchGroupLoading = ref(false);
|
||||
|
||||
// 查看角色关联用户
|
||||
const showRoleUsersDialog = ref(false);
|
||||
const roleUsersList = ref<any[]>([]);
|
||||
const roleUsersLoading = ref(false);
|
||||
const currentRoleForUsers = ref<{ roleName: string; roleCode: string; roleId: string } | null>(null);
|
||||
|
||||
// 批量指定关联用户
|
||||
const showAssignUserDialog = ref(false);
|
||||
const assignUserType = ref('');
|
||||
const assignUserKeyword = ref('');
|
||||
const assignUserList = ref<any[]>([]);
|
||||
const assignUserLoading = ref(false);
|
||||
const assignSubmitLoading = ref(false);
|
||||
const assignSelectedIds = ref<string[]>([]);
|
||||
const assignCurrentRole = computed(() => {
|
||||
const id = selectObjs.value && selectObjs.value[0];
|
||||
if (!id) return null;
|
||||
return (state.dataList || []).find((r: any) => r.roleId === id) || null;
|
||||
});
|
||||
/** 按部门分组的用户树(用于表格树形展示) */
|
||||
const assignUserTree = computed(() => {
|
||||
const list = assignUserList.value || [];
|
||||
const map = new Map<string, any[]>();
|
||||
list.forEach((u: any) => {
|
||||
const deptKey = u.deptName && String(u.deptName).trim() ? u.deptName : '未分配部门';
|
||||
if (!map.has(deptKey)) map.set(deptKey, []);
|
||||
map.get(deptKey)!.push({ ...u, id: u.userId, _isDept: false });
|
||||
});
|
||||
const result: any[] = [];
|
||||
map.forEach((users, deptName) => {
|
||||
result.push({
|
||||
id: `dept_${deptName}`,
|
||||
label: deptName,
|
||||
_isDept: true,
|
||||
children: users,
|
||||
});
|
||||
});
|
||||
result.sort((a, b) => a.label.localeCompare(b.label));
|
||||
return result;
|
||||
});
|
||||
|
||||
// 列表不分页,显示全部数据
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
queryForm: {
|
||||
roleName: '',
|
||||
},
|
||||
pageList: pageList, // H
|
||||
isPage: false,
|
||||
pageList: async (params: any) => {
|
||||
const res = await list(params);
|
||||
let data = res?.data || [];
|
||||
if (Array.isArray(data) && params?.roleName) {
|
||||
const kw = String(params.roleName).trim().toLowerCase();
|
||||
if (kw) {
|
||||
data = data.filter(
|
||||
(r: any) =>
|
||||
(r.roleName && r.roleName.toLowerCase().includes(kw)) ||
|
||||
(r.roleCode && r.roleCode.toLowerCase().includes(kw))
|
||||
);
|
||||
}
|
||||
}
|
||||
return { data };
|
||||
},
|
||||
descs: ['create_time'],
|
||||
});
|
||||
|
||||
/** 按分组构建树形数据:分组为父节点,角色为子节点 */
|
||||
const roleTreeData = computed(() => {
|
||||
const list = state.dataList || [];
|
||||
const groupMap = new Map<string, any[]>();
|
||||
list.forEach((row: any) => {
|
||||
const groupName = row.roleGroup && String(row.roleGroup).trim() ? String(row.roleGroup).trim() : '未分组';
|
||||
if (!groupMap.has(groupName)) groupMap.set(groupName, []);
|
||||
groupMap.get(groupName)!.push(row);
|
||||
});
|
||||
const result: any[] = [];
|
||||
groupMap.forEach((roles, groupName) => {
|
||||
result.push({
|
||||
roleId: `group_${groupName}`,
|
||||
roleName: groupName,
|
||||
_isGroup: true,
|
||||
children: roles,
|
||||
});
|
||||
});
|
||||
// 未分组放最后,其余按分组名排序
|
||||
result.sort((a, b) => {
|
||||
if (a.roleName === '未分组') return 1;
|
||||
if (b.roleName === '未分组') return -1;
|
||||
return a.roleName.localeCompare(b.roleName);
|
||||
});
|
||||
return result;
|
||||
});
|
||||
|
||||
const dictType = ref([
|
||||
{
|
||||
label: '全部',
|
||||
@@ -153,8 +359,8 @@ const dictType = ref([
|
||||
},
|
||||
]);
|
||||
|
||||
// table hook
|
||||
const { getDataList, currentChangeHandle, sizeChangeHandle, downBlobFile, tableStyle } = useTable(state);
|
||||
// table hook(无分页,不暴露 currentChangeHandle/sizeChangeHandle)
|
||||
const { getDataList, downBlobFile, tableStyle } = useTable(state);
|
||||
|
||||
// 清空搜索条件
|
||||
const resetQuery = () => {
|
||||
@@ -167,15 +373,15 @@ const exportExcel = () => {
|
||||
downBlobFile('/admin/role/export',Object.assign(state.queryForm,{ids:selectObjs}), 'role.xlsx');
|
||||
};
|
||||
|
||||
// 是否可以多选
|
||||
// 是否可以多选(分组行不可选,管理员角色不可选)
|
||||
const handleSelectable = (row: any) => {
|
||||
return row.roleId !== '1';
|
||||
return !row._isGroup && row.roleId !== '1';
|
||||
};
|
||||
|
||||
// 多选事件
|
||||
// 多选事件(仅角色行可选,排除分组行)
|
||||
const handleSelectionChange = (objs: { roleId: string }[]) => {
|
||||
selectObjs.value = objs.map(({ roleId }) => roleId);
|
||||
multiple.value = !objs.length;
|
||||
selectObjs.value = objs.map(({ roleId }) => roleId).filter((id: string) => !String(id).startsWith('group_'));
|
||||
multiple.value = !selectObjs.value.length;
|
||||
};
|
||||
|
||||
// 删除操作
|
||||
@@ -194,4 +400,129 @@ const handleDelete = async (ids: string[]) => {
|
||||
useMessage().error(err.msg);
|
||||
}
|
||||
};
|
||||
|
||||
async function loadRoleUsersList() {
|
||||
const role = currentRoleForUsers.value;
|
||||
if (!role) return;
|
||||
roleUsersLoading.value = true;
|
||||
try {
|
||||
const res = await getUsersByRoleId(role.roleId);
|
||||
const data = res?.data;
|
||||
roleUsersList.value = Array.isArray(data) ? data : [];
|
||||
} catch {
|
||||
roleUsersList.value = [];
|
||||
} finally {
|
||||
roleUsersLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function openRoleUsersDialog(row: any) {
|
||||
currentRoleForUsers.value = { roleName: row.roleName, roleCode: row.roleCode, roleId: row.roleId };
|
||||
showRoleUsersDialog.value = true;
|
||||
roleUsersList.value = [];
|
||||
await loadRoleUsersList();
|
||||
}
|
||||
|
||||
async function handleUnassignUser(row: any) {
|
||||
const roleId = currentRoleForUsers.value?.roleId;
|
||||
const userId = row.userId;
|
||||
if (!roleId || !userId) return;
|
||||
try {
|
||||
await useMessageBox().confirm(`确定解除「${row.realName || row.username}」与该角色的关联?`);
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await unassignUserFromRole(roleId, userId);
|
||||
useMessage().success('已解除关联');
|
||||
await loadRoleUsersList();
|
||||
} catch (err: any) {
|
||||
useMessage().error(err?.msg || '操作失败');
|
||||
}
|
||||
}
|
||||
|
||||
function openAssignUserDialog() {
|
||||
if (selectObjs.value.length !== 1) return;
|
||||
showAssignUserDialog.value = true;
|
||||
assignUserType.value = '';
|
||||
assignUserKeyword.value = '';
|
||||
assignUserList.value = [];
|
||||
assignSelectedIds.value = [];
|
||||
}
|
||||
|
||||
async function loadAssignUserList() {
|
||||
if (!assignUserType.value) return;
|
||||
assignUserLoading.value = true;
|
||||
try {
|
||||
const res = await userPageList({
|
||||
current: 1,
|
||||
size: 2000,
|
||||
userType: assignUserType.value,
|
||||
realName: assignUserKeyword.value ? assignUserKeyword.value.trim() : undefined,
|
||||
});
|
||||
const records = res?.data?.records ?? res?.records ?? (Array.isArray(res?.data) ? res.data : []);
|
||||
assignUserList.value = Array.isArray(records) ? records : [];
|
||||
} catch (e) {
|
||||
assignUserList.value = [];
|
||||
} finally {
|
||||
assignUserLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function handleAssignUserSelectionChange(rows: any[]) {
|
||||
assignSelectedIds.value = (rows || [])
|
||||
.filter((r: any) => !r._isDept && r.userId)
|
||||
.map((r: any) => r.userId);
|
||||
}
|
||||
|
||||
async function handleAssignUsersSubmit() {
|
||||
const roleId = assignCurrentRole.value?.roleId;
|
||||
if (!roleId || !assignSelectedIds.value.length) {
|
||||
useMessage().warning('请选择要关联的用户');
|
||||
return;
|
||||
}
|
||||
assignSubmitLoading.value = true;
|
||||
try {
|
||||
await assignUsersToRole(roleId, assignSelectedIds.value);
|
||||
useMessage().success('已关联 ' + assignSelectedIds.value.length + ' 名用户');
|
||||
showAssignUserDialog.value = false;
|
||||
} catch (err: any) {
|
||||
useMessage().error(err?.msg || '操作失败');
|
||||
} finally {
|
||||
assignSubmitLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 批量指定分组
|
||||
const handleBatchGroup = async () => {
|
||||
const ids = selectObjs.value || [];
|
||||
if (!ids.length) {
|
||||
useMessage().warning('请先勾选要设置分组的角色');
|
||||
return;
|
||||
}
|
||||
batchGroupLoading.value = true;
|
||||
try {
|
||||
await batchUpdateRoleGroup(ids, batchGroupName.value || '');
|
||||
useMessage().success('分组已更新');
|
||||
showBatchGroupDialog.value = false;
|
||||
batchGroupName.value = '';
|
||||
getDataList();
|
||||
} catch (err: any) {
|
||||
useMessage().error(err?.msg || '操作失败');
|
||||
} finally {
|
||||
batchGroupLoading.value = false;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.role-group-name {
|
||||
font-weight: 600;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
.mb12 { margin-bottom: 12px; }
|
||||
.mb8 { margin-bottom: 8px; }
|
||||
.mt8 { margin-top: 8px; }
|
||||
.assign-user-tip { color: var(--el-text-color-secondary); font-size: 13px; }
|
||||
.dept-row { font-weight: 600; color: var(--el-text-color-primary); }
|
||||
</style>
|
||||
|
||||
@@ -204,7 +204,8 @@ const dataRules = ref({
|
||||
{ required: true, message: '学院不能为空', trigger: 'change' }
|
||||
],
|
||||
classCode: [
|
||||
{ required: true, message: '班级代码不能为空', trigger: 'blur' }
|
||||
{ required: true, message: '班级代码不能为空', trigger: 'blur' },
|
||||
{ min: 4, message: '班级代码至少4位(班号取后4位)', trigger: 'blur' }
|
||||
],
|
||||
classNo: [
|
||||
{ required: true, message: '班号不能为空', trigger: 'blur' }
|
||||
@@ -406,11 +407,11 @@ watch(() => form.enterDate, (newVal) => {
|
||||
}
|
||||
})
|
||||
|
||||
// 监听班级代码,取后四位为班号
|
||||
// 监听班级代码,取后四位为班号(班级代码至少4位)
|
||||
watch(() => form.classCode, (newVal) => {
|
||||
if (newVal) {
|
||||
const length = newVal.length
|
||||
if (length > 4) {
|
||||
if (length >= 4) {
|
||||
// 只在新增模式下自动填充班号,编辑模式下如果班号为空才填充
|
||||
if (!form.id || !form.classNo) {
|
||||
form.classNo = newVal.substring(length - 4, length)
|
||||
|
||||
@@ -196,7 +196,7 @@
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" fixed="right" width="200">
|
||||
<el-table-column label="操作" align="center" fixed="right" width="260">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
icon="View"
|
||||
|
||||
@@ -262,13 +262,25 @@
|
||||
<template #default="scope" v-if="col.prop === 'gender'">
|
||||
<GenderTag :gender="scope.row.gender" />
|
||||
</template>
|
||||
<template #default="scope" v-else-if="col.prop === 'education'">
|
||||
<el-tag v-if="getEducationLabel(scope.row.education)" size="small" type="info" effect="plain">
|
||||
{{ getEducationLabel(scope.row.education) }}
|
||||
</el-tag>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
<template #default="scope" v-else-if="col.prop === 'isDorm'">
|
||||
<el-tag v-if="scope.row.isDorm === 1 || scope.row.isDorm === '1'" size="small" type="success" effect="plain">是</el-tag>
|
||||
<el-tag v-else-if="scope.row.isDorm === 0 || scope.row.isDorm === '0'" size="small" type="info" effect="plain">否</el-tag>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
<template #default="scope" v-else-if="col.prop === 'enrollStatus'">
|
||||
<el-tag v-if="scope.row.enrollStatus" size="small" type="info" effect="plain">{{ scope.row.enrollStatus }}</el-tag>
|
||||
<el-tag
|
||||
v-if="getEnrollStatusLabel(scope.row.enrollStatus)"
|
||||
size="small"
|
||||
type="info"
|
||||
effect="plain">
|
||||
{{ getEnrollStatusLabel(scope.row.enrollStatus) }}
|
||||
</el-tag>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
<template #default="scope" v-else-if="col.prop === 'stuStatus'">
|
||||
@@ -427,7 +439,6 @@ import {
|
||||
editIsleader,
|
||||
updateInout,
|
||||
updateStuSimpleInfo,
|
||||
getStuStatus,
|
||||
prePrint
|
||||
} from "/@/api/basic/basicstudent";
|
||||
import { getDeptList, getClassListByRole } from "/@/api/basic/basicclass";
|
||||
@@ -456,6 +467,7 @@ const deptList = ref<any[]>([])
|
||||
const classList = ref<any[]>([])
|
||||
const statusList = ref<any[]>([])
|
||||
const stuStatusList = ref<any[]>([])
|
||||
const educationList = ref<any[]>([])
|
||||
const selectedRows = ref<any[]>([])
|
||||
const importCertificateDialogVisible = ref(false)
|
||||
const uploadLoading = ref(false)
|
||||
@@ -781,9 +793,17 @@ const getClassListData = async () => {
|
||||
// 获取学籍状态列表
|
||||
const getStatusListData = async () => {
|
||||
try {
|
||||
const res = await getStuStatus()
|
||||
const res = await getDicts('enroll_status')
|
||||
if (res.data) {
|
||||
statusList.value = Array.isArray(res.data) ? res.data : []
|
||||
if (Array.isArray(res.data)) {
|
||||
// 确保数据格式统一为 {label, value}
|
||||
statusList.value = res.data.map((item: any) => ({
|
||||
label: item.label || item.name || item.dictLabel || item.text || '',
|
||||
value: String(item.value || item.code || item.dictValue || item.id || '')
|
||||
})).filter((item: any) => item.label && item.value !== undefined && item.value !== null && item.value !== '')
|
||||
} else {
|
||||
statusList.value = []
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('获取学籍状态列表失败', err)
|
||||
@@ -791,6 +811,24 @@ const getStatusListData = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 获取文化程度字典
|
||||
const getEducationListData = async () => {
|
||||
try {
|
||||
const res = await getDicts('pre_school_education')
|
||||
if (res.data && Array.isArray(res.data)) {
|
||||
educationList.value = res.data.map((item: any) => ({
|
||||
label: item.label || item.dictLabel || item.name,
|
||||
value: item.value || item.dictValue || item.code
|
||||
}))
|
||||
} else {
|
||||
educationList.value = []
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('获取文化程度字典失败', err)
|
||||
educationList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
// 获取学生状态列表
|
||||
const getStuStatusListData = async () => {
|
||||
try {
|
||||
@@ -813,6 +851,20 @@ const getStuStatusListData = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 根据学籍状态值获取标签
|
||||
const getEnrollStatusLabel = (value: any) => {
|
||||
if (value === undefined || value === null || value === '') return ''
|
||||
const status = statusList.value.find(item => String(item.value) === String(value))
|
||||
return status ? status.label : ''
|
||||
}
|
||||
|
||||
// 根据文化程度值获取标签
|
||||
const getEducationLabel = (value: any) => {
|
||||
if (value === undefined || value === null || value === '') return ''
|
||||
const item = educationList.value.find(i => String(i.value) === String(value))
|
||||
return item ? item.label : ''
|
||||
}
|
||||
|
||||
// 根据学生状态值获取标签
|
||||
const getStuStatusLabel = (value: any) => {
|
||||
if (value === undefined || value === null || value === '') return ''
|
||||
@@ -833,6 +885,7 @@ onMounted(() => {
|
||||
getDeptListData()
|
||||
getClassListData()
|
||||
getStatusListData()
|
||||
getEducationListData()
|
||||
getStuStatusListData()
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -35,6 +35,14 @@
|
||||
招标代理管理
|
||||
</span>
|
||||
<div class="header-actions">
|
||||
<el-button
|
||||
icon="Files"
|
||||
link
|
||||
type="primary"
|
||||
|
||||
>
|
||||
代理汇总
|
||||
</el-button>
|
||||
<el-button
|
||||
icon="FolderAdd"
|
||||
type="primary"
|
||||
@@ -170,7 +178,7 @@ const handleDelete = async (row: any) => {
|
||||
}
|
||||
|
||||
try {
|
||||
await delObj(row.id);
|
||||
await delObj({"id":row.id});
|
||||
useMessage().success('删除成功');
|
||||
getDataList();
|
||||
} catch (err: any) {
|
||||
|
||||
@@ -21,12 +21,14 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 树形表格 -->
|
||||
<!-- 树形表格(懒加载:仅首屏加载根节点,展开时再加载子节点) -->
|
||||
<el-table
|
||||
ref="tableRef"
|
||||
:data="state.dataList"
|
||||
v-loading="state.loading"
|
||||
stripe
|
||||
lazy
|
||||
:load="loadTreeNode"
|
||||
:cell-style="tableStyle.cellStyle"
|
||||
:header-cell-style="tableStyle.headerCellStyle"
|
||||
class="modern-table"
|
||||
@@ -88,7 +90,7 @@
|
||||
<script setup lang="ts" name="PurchasingCategory">
|
||||
import { ref, reactive, defineAsyncComponent } from 'vue'
|
||||
import { BasicTableProps, useTable } from "/@/hooks/table";
|
||||
import { getTree, delObj } from "/@/api/finance/purchasingcategory";
|
||||
import { getTreeRoots, getTreeChildren, delObj } from "/@/api/finance/purchasingcategory";
|
||||
import { useMessage, useMessageBox } from "/@/hooks/message";
|
||||
import { List, Document, DocumentCopy, EditPen } from '@element-plus/icons-vue'
|
||||
|
||||
@@ -100,19 +102,40 @@ const tableRef = ref()
|
||||
const formDialogRef = ref()
|
||||
|
||||
/**
|
||||
* 查询树形数据方法
|
||||
* @param params - 查询参数
|
||||
* @returns Promise<any>
|
||||
* 查询树根节点(懒加载:首屏只加载根节点)
|
||||
*/
|
||||
const queryTree = (params?: any) => {
|
||||
return getTree(params);
|
||||
const queryTreeRoots = () => {
|
||||
return getTreeRoots().then((res: any) => {
|
||||
const list = res?.data ?? [];
|
||||
return { data: Array.isArray(list) ? list : [] };
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 懒加载子节点:展开某行时按需请求子节点
|
||||
* @param row 当前行
|
||||
* @param treeNode 树节点信息
|
||||
* @param resolve 回调,传入子节点数组
|
||||
*/
|
||||
const loadTreeNode = (row: any, treeNode: any, resolve: (data: any[]) => void) => {
|
||||
const parentCode = row?.code;
|
||||
if (!parentCode) {
|
||||
resolve([]);
|
||||
return;
|
||||
}
|
||||
getTreeChildren(parentCode)
|
||||
.then((res: any) => {
|
||||
const list = res?.data ?? [];
|
||||
resolve(Array.isArray(list) ? list : []);
|
||||
})
|
||||
.catch(() => resolve([]));
|
||||
};
|
||||
|
||||
/**
|
||||
* 定义响应式表格数据
|
||||
*/
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
pageList: queryTree,
|
||||
pageList: queryTreeRoots,
|
||||
queryForm: {},
|
||||
isPage: false, // 树形表格不分页
|
||||
});
|
||||
|
||||
@@ -0,0 +1,317 @@
|
||||
<template>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="160px" >
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="验收方式" prop="acceptType">
|
||||
<el-radio-group v-model="form.acceptType" :disabled="readonly">
|
||||
<el-radio label="1" :disabled="!canFill">填写履约验收评价表</el-radio>
|
||||
<el-radio label="2">上传履约验收评价表</el-radio>
|
||||
</el-radio-group>
|
||||
<div v-if="!canFill" class="el-form-item__tip">金额≥30万,仅支持上传模版</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="验收日期" prop="acceptDate">
|
||||
<el-date-picker
|
||||
v-model="form.acceptDate"
|
||||
type="date"
|
||||
placeholder="请选择"
|
||||
format="YYYY-MM-DD"
|
||||
value-format="YYYY-MM-DD"
|
||||
style="width: 100%"
|
||||
:disabled="readonly"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<!-- 填报方式:验收内容表格 -->
|
||||
<template v-if="form.acceptType === '1' && canFill">
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="验收内容" prop="acceptContents">
|
||||
<el-table :data="form.acceptContents" border size="small" class="accept-content-table">
|
||||
<el-table-column prop="itemName" label="验收项" >
|
||||
<template #default="{ row }">
|
||||
<div>
|
||||
<span>{{row.itemName}}</span>
|
||||
<el-input
|
||||
v-if="row.type === 'input'"
|
||||
v-model="row.remark"
|
||||
placeholder="请输入"
|
||||
size="small"
|
||||
:disabled="readonly"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="isQualified" label="合格/不合格" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-radio-group v-model="row.isQualified" size="small" :disabled="readonly">
|
||||
<el-radio label="1">合格</el-radio>
|
||||
<el-radio label="0">不合格</el-radio>
|
||||
</el-radio-group>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
</el-table>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</template>
|
||||
|
||||
<!-- 上传方式 -->
|
||||
<template v-if="form.acceptType === '2'">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="履约验收模版" prop="templateFileIds">
|
||||
<UploadFile
|
||||
v-model="templateFileIdsStr"
|
||||
:limit="1"
|
||||
:data="{ purchaseId: purchaseId || '', fileType: '110' }"
|
||||
upload-file-url="/purchase/purchasingfiles/upload"
|
||||
:disabled="readonly"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</template>
|
||||
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="验收地点" prop="acceptAddress">
|
||||
<el-input v-model="form.acceptAddress" placeholder="请输入验收地点" :disabled="readonly" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="存在问题及改进意见" prop="question">
|
||||
<el-input
|
||||
v-model="form.question"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
placeholder="请输入存在问题及改进意见"
|
||||
:disabled="readonly"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<!-- 验收小组 -->
|
||||
<el-col :span="12">
|
||||
<el-form-item label="验收小组" prop="acceptTeam">
|
||||
<div class="team-list">
|
||||
<div v-for="(m, idx) in form.acceptTeam" :key="idx" class="team-row">
|
||||
<el-select
|
||||
v-model="m.roleType"
|
||||
placeholder="身份"
|
||||
size="small"
|
||||
style="width: 130px; margin-right: 8px"
|
||||
:disabled="readonly"
|
||||
@change="(val) => onRoleChange(idx, val as string)"
|
||||
>
|
||||
<el-option label="组长(校内)" value="LEADER_IN" />
|
||||
<el-option label="组长(校外)" value="LEADER_OUT" />
|
||||
<el-option label="组员(校内)" value="MEMBER_IN" />
|
||||
<el-option label="组员(校外)" value="MEMBER_OUT" />
|
||||
</el-select>
|
||||
<template v-if="m.roleType === 'LEADER_IN' || m.roleType === 'MEMBER_IN'">
|
||||
<org-selector
|
||||
v-model:orgList="m.userList"
|
||||
type="user"
|
||||
:multiple="false"
|
||||
@update:orgList="(list: any[]) => onTeamUserChange(idx, list)"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-input v-model="m.name" placeholder="姓名" size="small" style="width:120px" :disabled="readonly" />
|
||||
<el-input v-model="m.deptName" placeholder="单位/部门" size="small" style="width:160px" :disabled="readonly" />
|
||||
</template>
|
||||
<el-button v-if="!readonly && form.acceptTeam.length > 3" type="danger" link size="small" @click="removeTeam(idx)">删除</el-button>
|
||||
</div>
|
||||
<el-button v-if="!readonly" type="primary" link size="small" @click="addTeam">+ 增加成员</el-button>
|
||||
</div>
|
||||
<div class="el-form-item__tip">
|
||||
至少3人,且为单数
|
||||
<template v-if="(previousBatchesTeams || []).length > 0">
|
||||
;
|
||||
<span class="copy-from-inline">
|
||||
从往期带入
|
||||
<el-select
|
||||
v-model="copyFromBatch"
|
||||
placeholder="同第N期"
|
||||
clearable
|
||||
size="small"
|
||||
style="width: 100px"
|
||||
@change="onCopyFromBatch"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in (previousBatchesTeams || [])"
|
||||
:key="item.batch"
|
||||
:label="`同第${item.batch}期`"
|
||||
:value="item.batch"
|
||||
/>
|
||||
</el-select>
|
||||
</span>
|
||||
</template>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="24">
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="form.remark" type="textarea" :rows="2" placeholder="请输入" :disabled="readonly" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, watch } from 'vue'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
modelValue: Record<string, any>
|
||||
canFill: boolean
|
||||
readonly?: boolean
|
||||
purchaseId?: string
|
||||
acceptanceItems?: any[]
|
||||
batchNum?: number
|
||||
previousBatchesTeams?: { batch: number; team: any[] }[]
|
||||
}>(),
|
||||
{ readonly: false, canFill: true, purchaseId: '', batchNum: 1, previousBatchesTeams: () => [] }
|
||||
)
|
||||
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
|
||||
const formRef = ref<FormInstance>()
|
||||
const templateFileIdsStr = ref('')
|
||||
const copyFromBatch = ref<number | null>(null)
|
||||
|
||||
const form = reactive({
|
||||
acceptType: '1',
|
||||
acceptDate: '',
|
||||
acceptContents: [] as any[],
|
||||
acceptTeam: [
|
||||
{ name: '', deptCode: '', deptName: '', roleType: '' },
|
||||
{ name: '', deptCode: '', deptName: '', roleType: '' },
|
||||
{ name: '', deptCode: '', deptName: '', roleType: '' },
|
||||
] as any[],
|
||||
templateFileIds: [] as string[],
|
||||
acceptAddress: '',
|
||||
question: '',
|
||||
remark: '',
|
||||
...props.modelValue,
|
||||
})
|
||||
|
||||
watch(() => props.modelValue, (val) => {
|
||||
Object.assign(form, val || {})
|
||||
// 金额≥30万时,强制为上传方式
|
||||
if (!props.canFill && form.acceptType === '1') {
|
||||
form.acceptType = '2'
|
||||
}
|
||||
}, { deep: true })
|
||||
watch(form, () => emit('update:modelValue', { ...form }), { deep: true })
|
||||
// 金额≥30万时,默认选中上传方式
|
||||
watch(() => props.canFill, (val) => {
|
||||
if (!val && form.acceptType === '1') {
|
||||
form.acceptType = '2'
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
watch(() => props.acceptanceItems, (items) => {
|
||||
if (items?.length && form.acceptContents.length === 0) {
|
||||
form.acceptContents = items.map((it: any) => ({
|
||||
configId: it.id,
|
||||
itemName: it.itemName,
|
||||
type: it.type,
|
||||
isQualified: '1',
|
||||
remark: '',
|
||||
}))
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
watch(templateFileIdsStr, (s) => {
|
||||
const arr = s ? s.split(',').map((x: string) => x.trim()).filter(Boolean) : []
|
||||
if (JSON.stringify(form.templateFileIds) !== JSON.stringify(arr)) {
|
||||
form.templateFileIds = arr
|
||||
}
|
||||
})
|
||||
|
||||
watch(() => form.templateFileIds, (arr) => {
|
||||
if (Array.isArray(arr) && arr.length) templateFileIdsStr.value = arr.join(',')
|
||||
}, { immediate: true, deep: true })
|
||||
|
||||
const addTeam = () => {
|
||||
form.acceptTeam.push({ name: '', deptCode: '', deptName: '', roleType: '' })
|
||||
}
|
||||
|
||||
const removeTeam = (idx: number) => {
|
||||
form.acceptTeam.splice(idx, 1)
|
||||
}
|
||||
|
||||
const onCopyFromBatch = (n: number | null) => {
|
||||
if (!n) return
|
||||
const item = props.previousBatchesTeams?.find((x) => x.batch === n)
|
||||
if (item?.team?.length) {
|
||||
form.acceptTeam = item.team.map((m: any) => ({
|
||||
name: m.name || '',
|
||||
deptCode: m.deptCode || '',
|
||||
deptName: m.deptName || '',
|
||||
roleType: m.roleType || '',
|
||||
}))
|
||||
}
|
||||
copyFromBatch.value = null
|
||||
}
|
||||
|
||||
const rules: FormRules = {
|
||||
acceptType: [{ required: true, message: '请选择验收方式', trigger: 'change' }],
|
||||
acceptDate: [{ required: true, message: '请选择验收日期', trigger: 'change' }],
|
||||
}
|
||||
|
||||
const onRoleChange = (idx: number, val: string) => {
|
||||
const isLeader = val === 'LEADER_IN' || val === 'LEADER_OUT'
|
||||
if (isLeader) {
|
||||
const hasOtherLeader = form.acceptTeam.some((m, i) =>
|
||||
i !== idx && (m.roleType === 'LEADER_IN' || m.roleType === 'LEADER_OUT')
|
||||
)
|
||||
if (hasOtherLeader) {
|
||||
// 只能有一个组长
|
||||
form.acceptTeam[idx].roleType = ''
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const onTeamUserChange = (idx: number, list: any[]) => {
|
||||
const m = form.acceptTeam[idx]
|
||||
if (!m) return
|
||||
if (list && list.length) {
|
||||
const u = list[0]
|
||||
m.name = u.name || u.realName || ''
|
||||
m.deptCode = u.deptCode || u.commonDeptCode || ''
|
||||
m.deptName = u.deptName || u.commonDeptName || ''
|
||||
m.userList = list
|
||||
} else {
|
||||
m.name = ''
|
||||
m.deptCode = ''
|
||||
m.deptName = ''
|
||||
m.userList = []
|
||||
}
|
||||
}
|
||||
|
||||
const validate = () => formRef.value?.validate()
|
||||
|
||||
defineExpose({ validate, form })
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.mb20 {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.copy-from-inline {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
margin-left: 4px;
|
||||
}
|
||||
.copy-from-inline :deep(.el-select) {
|
||||
vertical-align: middle;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,231 @@
|
||||
<template>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="140px">
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="8" class="mb20">
|
||||
<el-form-item label="项目名称">
|
||||
<el-input :model-value="projectName || form.projectName" readonly placeholder="-" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8" class="mb20">
|
||||
<el-form-item label="需求部门">
|
||||
<el-input :model-value="deptName || form.deptName" readonly placeholder="-" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8" class="mb20">
|
||||
<el-form-item label="是否签订合同" prop="hasContract">
|
||||
<el-radio-group v-model="form.hasContract">
|
||||
<el-radio label="0">否</el-radio>
|
||||
<el-radio label="1">是</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8" class="mb20" v-if="form.hasContract === '1'">
|
||||
<el-form-item label="合同" prop="contractId">
|
||||
<el-select
|
||||
v-model="form.contractId"
|
||||
placeholder="请选择合同"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 100%"
|
||||
:loading="contractLoading"
|
||||
@visible-change="onContractSelectVisibleChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in contractOptions"
|
||||
:key="item.id"
|
||||
:label="item.contractName || item.contractNo || item.id"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8" class="mb20">
|
||||
<el-form-item label="是否分期验收" prop="isInstallment">
|
||||
<el-radio-group v-model="form.isInstallment">
|
||||
<el-radio label="0">否</el-radio>
|
||||
<el-radio label="1">是</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8" class="mb20" v-if="form.isInstallment === '1'">
|
||||
<el-form-item label="分期次数" prop="totalPhases">
|
||||
<el-input-number v-model="form.totalPhases" :min="1" :max="99" placeholder="请输入" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8" class="mb20">
|
||||
<el-form-item label="供应商名称" prop="supplierName">
|
||||
<el-input v-model="form.supplierName" placeholder="选择合同后自动带出" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8" class="mb20">
|
||||
<el-form-item label="供应商联系人及电话" prop="supplierContact">
|
||||
<el-input v-model="form.supplierContact" placeholder="请输入" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8" class="mb20">
|
||||
<el-form-item label="采购人员" prop="purchaserId">
|
||||
<org-selector v-model:orgList="purchaserList" type="user" :multiple="false" @update:orgList="onPurchaserChange" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8" class="mb20">
|
||||
<el-form-item label="资产管理员" prop="assetAdminId">
|
||||
<org-selector v-model:orgList="assetAdminList" type="user" :multiple="false" @update:orgList="onAssetAdminChange" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, watch, onMounted } from 'vue'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import { getContracts } from '/@/api/finance/purchasingrequisition'
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: Record<string, any>
|
||||
projectName?: string
|
||||
deptName?: string
|
||||
/** 采购申请ID,用于拉取合同列表 */
|
||||
purchaseId?: string | number
|
||||
/** 每次打开弹窗时变化,用于强制重置内部 form */
|
||||
resetKey?: number
|
||||
}>()
|
||||
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
|
||||
const formRef = ref<FormInstance>()
|
||||
const contractOptions = ref<any[]>([])
|
||||
const contractLoading = ref(false)
|
||||
const contractLoaded = ref(false)
|
||||
const purchaserList = ref<any[]>([])
|
||||
const assetAdminList = ref<any[]>([])
|
||||
|
||||
const form = reactive({
|
||||
hasContract: '0',
|
||||
contractId: '',
|
||||
isInstallment: '0',
|
||||
totalPhases: 1,
|
||||
projectName: '',
|
||||
deptName: '',
|
||||
supplierName: '',
|
||||
supplierContact: '',
|
||||
purchaserId: '',
|
||||
purchaserName: '',
|
||||
assetAdminId: '',
|
||||
assetAdminName: '',
|
||||
...props.modelValue,
|
||||
})
|
||||
|
||||
const syncFormFromModel = (val: Record<string, any> | undefined) => {
|
||||
Object.assign(form, val || {})
|
||||
// 同步采购人员、资产管理员回 org-selector
|
||||
if (form.purchaserId && form.purchaserName) {
|
||||
purchaserList.value = [{ id: form.purchaserId, name: form.purchaserName, type: 'user' }]
|
||||
} else {
|
||||
purchaserList.value = []
|
||||
}
|
||||
if (form.assetAdminId && form.assetAdminName) {
|
||||
assetAdminList.value = [{ id: form.assetAdminId, name: form.assetAdminName, type: 'user' }]
|
||||
} else {
|
||||
assetAdminList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
const loadContractOptions = async () => {
|
||||
if (contractLoaded.value || contractLoading.value) return
|
||||
contractLoading.value = true
|
||||
try {
|
||||
const res = await getContracts(props.purchaseId ? { id: props.purchaseId } : {})
|
||||
const list = res?.data
|
||||
contractOptions.value = Array.isArray(list) ? list : []
|
||||
contractLoaded.value = true
|
||||
} catch (_) {
|
||||
contractOptions.value = []
|
||||
} finally {
|
||||
contractLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const onContractSelectVisibleChange = (visible: boolean) => {
|
||||
if (visible && form.hasContract === '1' && contractOptions.value.length === 0) {
|
||||
loadContractOptions()
|
||||
}
|
||||
}
|
||||
|
||||
watch(() => props.modelValue, syncFormFromModel, { deep: true, immediate: true })
|
||||
// resetKey 变化时强制用 modelValue 覆盖内部 form,并重置合同列表以便重新拉取
|
||||
watch(() => props.resetKey, () => {
|
||||
syncFormFromModel(props.modelValue)
|
||||
contractLoaded.value = false
|
||||
contractOptions.value = []
|
||||
})
|
||||
watch(form, () => emit('update:modelValue', { ...form }), { deep: true })
|
||||
|
||||
watch(() => form.hasContract, (val) => {
|
||||
if (val === '1') {
|
||||
contractLoaded.value = false
|
||||
loadContractOptions()
|
||||
} else {
|
||||
contractOptions.value = []
|
||||
contractLoaded.value = false
|
||||
}
|
||||
})
|
||||
|
||||
// 选择合同后,自动带出合同供应商名称
|
||||
watch(
|
||||
() => form.contractId,
|
||||
(val) => {
|
||||
if (!val) {
|
||||
form.supplierName = ''
|
||||
return
|
||||
}
|
||||
const c = contractOptions.value.find((it: any) => it.id === val)
|
||||
if (c && c.supplierName) {
|
||||
form.supplierName = c.supplierName
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
if (form.hasContract === '1') {
|
||||
loadContractOptions()
|
||||
}
|
||||
})
|
||||
|
||||
const onPurchaserChange = (list: any[]) => {
|
||||
if (list?.length) {
|
||||
const u = list[0]
|
||||
form.purchaserId = u.userId || u.id || ''
|
||||
form.purchaserName = u.name || u.realName || ''
|
||||
} else {
|
||||
form.purchaserId = ''
|
||||
form.purchaserName = ''
|
||||
}
|
||||
}
|
||||
|
||||
const onAssetAdminChange = (list: any[]) => {
|
||||
if (list?.length) {
|
||||
const u = list[0]
|
||||
form.assetAdminId = u.userId || u.id || ''
|
||||
form.assetAdminName = u.name || u.realName || ''
|
||||
} else {
|
||||
form.assetAdminId = ''
|
||||
form.assetAdminName = ''
|
||||
}
|
||||
}
|
||||
|
||||
const rules: FormRules = {
|
||||
isInstallment: [{ required: true, message: '请选择是否分期验收', trigger: 'change' }],
|
||||
totalPhases: [{ required: true, message: '请输入分期次数', trigger: 'blur' }],
|
||||
}
|
||||
|
||||
const validate = () => formRef.value?.validate()
|
||||
|
||||
defineExpose({ validate, form })
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.mb20 {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,575 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
title="履约验收"
|
||||
width="75%"
|
||||
:close-on-click-modal="false"
|
||||
destroy-on-close
|
||||
class="purchasing-accept-modal"
|
||||
@close="handleClose"
|
||||
>
|
||||
<div v-loading="loading" class="modal-body" :key="String(purchaseId)">
|
||||
<div class="main-tabs">
|
||||
<div class="main-tab-nav">
|
||||
<div
|
||||
class="main-tab-item"
|
||||
:class="{ active: mainTab === 'common' }"
|
||||
@click="mainTab = 'common'"
|
||||
>
|
||||
公共信息
|
||||
</div>
|
||||
<div
|
||||
class="main-tab-item"
|
||||
:class="{ active: mainTab === 'batch' }"
|
||||
@click="mainTab = 'batch'"
|
||||
>
|
||||
{{ commonForm?.isInstallment === '0' ? '验收' : '分期验收' }}{{ commonForm?.isInstallment !== '0' && batches.length > 0 ? ` (${batches.length})` : '' }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="main-tab-content">
|
||||
<div v-show="mainTab === 'common'" class="tab-content">
|
||||
<AcceptCommonForm
|
||||
:key="`${purchaseId}-${openToken}`"
|
||||
:reset-key="openToken"
|
||||
ref="commonFormRef"
|
||||
v-model="commonForm"
|
||||
:purchase-id="purchaseId"
|
||||
:project-name="applyInfo?.projectName"
|
||||
:dept-name="applyInfo?.deptName"
|
||||
/>
|
||||
</div>
|
||||
<div v-show="mainTab === 'batch'" class="tab-content">
|
||||
<div v-if="batches.length > 0">
|
||||
<div v-show="commonForm?.isInstallment !== '0'" class="batch-tabs">
|
||||
<div
|
||||
v-for="b in batches"
|
||||
:key="b.id"
|
||||
class="batch-tab-item"
|
||||
:class="{ active: String(b.batch) === activeTab, disabled: !canEditBatch(b.batch) }"
|
||||
@click="canEditBatch(b.batch) && (activeTab = String(b.batch))"
|
||||
>
|
||||
<span>第{{ b.batch }}期</span>
|
||||
<el-tag v-if="isBatchCompleted(b)" type="success" size="small">已填</el-tag>
|
||||
<el-tag v-else-if="!canEditBatch(b.batch)" type="info" size="small">需先完成上一期</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
<div class="batch-panel">
|
||||
<AcceptBatchForm
|
||||
v-for="b in batches"
|
||||
v-show="String(b.batch) === activeTab"
|
||||
:key="b.id"
|
||||
:ref="(el) => setBatchFormRef(b.batch, el)"
|
||||
v-model="batchForms[b.batch]"
|
||||
:can-fill="canFillForm"
|
||||
:readonly="false"
|
||||
:purchase-id="String(purchaseId)"
|
||||
:acceptance-items="acceptanceItems"
|
||||
:batch-num="b.batch"
|
||||
:previous-batches-teams="getPreviousBatchesTeams(b.batch)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="tip-box">
|
||||
<el-alert type="info" :closable="false" show-icon>
|
||||
请先在「公共信息」中填写并点击「保存公共配置」,系统将按分期次数自动生成验收批次
|
||||
</el-alert>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<span>
|
||||
<el-button @click="handleClose">关 闭</el-button>
|
||||
<!-- 下载履约验收模板按钮 -->
|
||||
<el-dropdown split-button type="primary" @click="handleDownloadTemplate" @command="handleDownloadTemplateCommand">
|
||||
下载履约模板
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="current" :disabled="!activeBatchId">
|
||||
下载当前期({{ activeTab }}期)
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item command="all" :disabled="batches.length === 0">
|
||||
下载全部期数
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<el-button
|
||||
v-if="mainTab === 'common' || batches.length === 0"
|
||||
type="primary"
|
||||
@click="saveCommonConfig"
|
||||
:loading="saving"
|
||||
>
|
||||
保存公共配置
|
||||
</el-button>
|
||||
<el-button
|
||||
v-else-if="mainTab === 'batch' && activeBatchId"
|
||||
type="primary"
|
||||
@click="saveCurrentBatch"
|
||||
:loading="saving"
|
||||
>
|
||||
保存第{{ activeTab }}期
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, watch, nextTick } from 'vue'
|
||||
import { useMessage } from '/@/hooks/message'
|
||||
import { handleBlobFile } from '/@/utils/other'
|
||||
import {
|
||||
saveCommonConfig as apiSaveCommonConfig,
|
||||
getCommonConfigWithBatches,
|
||||
updateBatch,
|
||||
canFillForm as apiCanFillForm,
|
||||
getAcceptanceItems,
|
||||
getDetail,
|
||||
downloadPerformanceAcceptanceTemplate,
|
||||
} from '/@/api/purchase/purchasingAccept'
|
||||
import AcceptCommonForm from './AcceptCommonForm.vue'
|
||||
import AcceptBatchForm from './AcceptBatchForm.vue'
|
||||
|
||||
const emit = defineEmits(['refresh'])
|
||||
|
||||
const visible = ref(false)
|
||||
const loading = ref(false)
|
||||
const saving = ref(false)
|
||||
const purchaseId = ref<string | number>('')
|
||||
const applyInfo = ref<any>(null)
|
||||
const rowProjectType = ref<string>('A')
|
||||
const canFillForm = ref(true)
|
||||
const acceptanceItems = ref<any[]>([])
|
||||
const batches = ref<any[]>([])
|
||||
const mainTab = ref('common')
|
||||
const activeTab = ref('1')
|
||||
const commonFormRef = ref()
|
||||
const batchFormRefMap = ref<Record<number, any>>({})
|
||||
/** 使用 ref 并在每次打开时替换整个对象,确保子组件能感知引用变化并清空 */
|
||||
const commonForm = ref<Record<string, any>>({})
|
||||
/** 每次打开自增,用于强制 AcceptCommonForm 重新挂载,确保公共信息彻底清空 */
|
||||
const openToken = ref(0)
|
||||
const batchForms = reactive<Record<number, any>>({})
|
||||
/** 记录哪些期已保存到服务器,用于控制“下一期可填”:只有上一期已保存才允许填下一期 */
|
||||
const batchSavedFlags = ref<Record<number, boolean>>({})
|
||||
|
||||
const setBatchFormRef = (batch: number, el: any) => {
|
||||
if (el) batchFormRefMap.value[batch] = el
|
||||
}
|
||||
|
||||
const activeBatchId = computed(() => {
|
||||
const b = batches.value.find((x: any) => String(x.batch) === activeTab.value)
|
||||
return b?.id || ''
|
||||
})
|
||||
|
||||
const getPreviousBatchesTeams = (batchNum: number) => {
|
||||
const list: { batch: number; team: any[] }[] = []
|
||||
for (let n = 1; n < batchNum; n++) {
|
||||
const team = batchForms[n]?.acceptTeam
|
||||
if (Array.isArray(team) && team.length > 0) {
|
||||
list.push({ batch: n, team })
|
||||
}
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
/** 是否允许编辑该期:第 1 期始终可编辑;第 N 期仅当第 1~N-1 期均已保存后才可编辑 */
|
||||
const canEditBatch = (batch: number) => {
|
||||
if (batch === 1) return true
|
||||
for (let i = 1; i < batch; i++) {
|
||||
if (!batchSavedFlags.value[i]) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/** 该期是否已保存(用于 tab 上显示“已填”标签) */
|
||||
const isBatchCompleted = (b: any) => {
|
||||
return !!batchSavedFlags.value[b.batch]
|
||||
}
|
||||
|
||||
const isBatchCompletedByIdx = (batch: number) => {
|
||||
return !!batchSavedFlags.value[batch]
|
||||
}
|
||||
|
||||
const loadData = async () => {
|
||||
if (!purchaseId.value) return
|
||||
const currentId = String(purchaseId.value)
|
||||
loading.value = true
|
||||
try {
|
||||
const [configRes, canFillRes] = await Promise.all([
|
||||
getCommonConfigWithBatches(currentId),
|
||||
apiCanFillForm(currentId),
|
||||
])
|
||||
// 防止快速切换:若已打开其他申请单,忽略本次结果
|
||||
if (String(purchaseId.value) !== currentId) return
|
||||
|
||||
const config = configRes?.data
|
||||
canFillForm.value = !!canFillRes?.data
|
||||
|
||||
if (config?.common) {
|
||||
applyInfo.value = config.common
|
||||
// 仅当存在已保存批次时,才用接口数据回填公共信息;否则保持 open() 中的默认清空值
|
||||
if (config?.batches?.length) {
|
||||
Object.assign(commonForm.value, {
|
||||
hasContract: config.common.hasContract || '0',
|
||||
contractId: config.common.contractId || '',
|
||||
isInstallment: config.common.isInstallment || '0',
|
||||
totalPhases: config.common.totalPhases || 1,
|
||||
supplierName: config.common.supplierName || '',
|
||||
supplierContact: config.common.supplierContact || '',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const projectType = applyInfo.value?.projectType || rowProjectType.value || 'A'
|
||||
const typeMap: Record<string, string> = { A: 'A', B: 'B', C: 'C' }
|
||||
const at = typeMap[projectType] || 'A'
|
||||
const itemsRes = await getAcceptanceItems(at)
|
||||
if (String(purchaseId.value) !== currentId) return
|
||||
acceptanceItems.value = itemsRes?.data || []
|
||||
|
||||
if (config?.batches?.length) {
|
||||
batches.value = config.batches.sort((a: any, b: any) => (a.batch || 0) - (b.batch || 0))
|
||||
activeTab.value = String(batches.value[0]?.batch || '1')
|
||||
mainTab.value = 'batch'
|
||||
for (const b of batches.value) {
|
||||
if (!batchForms[b.batch]) batchForms[b.batch] = {}
|
||||
}
|
||||
await loadBatchDetails()
|
||||
if (String(purchaseId.value) !== currentId) return
|
||||
} else {
|
||||
batches.value = []
|
||||
}
|
||||
} catch (e: any) {
|
||||
useMessage().error(e?.msg || '加载失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const loadBatchDetails = async () => {
|
||||
for (const b of batches.value) {
|
||||
batchSavedFlags.value[b.batch] = false
|
||||
}
|
||||
for (const b of batches.value) {
|
||||
try {
|
||||
const res = await getDetail(String(purchaseId.value), b.batch)
|
||||
const d = res?.data
|
||||
if (d?.accept) {
|
||||
// 仅当该期在服务端有验收日期(且验收方式)时才视为已保存,避免空结构被当成“已填”
|
||||
const hasSaved = !!(d.accept.acceptDate && d.accept.acceptType)
|
||||
batchSavedFlags.value[b.batch] = hasSaved
|
||||
const itemMap = (acceptanceItems.value || []).reduce((acc: any, it: any) => {
|
||||
acc[it.id] = it
|
||||
return acc
|
||||
}, {})
|
||||
batchForms[b.batch] = {
|
||||
acceptType: d.accept.acceptType || '1',
|
||||
acceptDate: d.accept.acceptDate || '',
|
||||
acceptAddress: d.accept.acceptAddress || '',
|
||||
question: d.accept.question || '',
|
||||
remark: d.accept.remark || '',
|
||||
templateFileIds: d.accept.templateFileIds || [],
|
||||
acceptContents: (d.contents || []).map((c: any) => {
|
||||
const cfg = itemMap[c.configId]
|
||||
return {
|
||||
configId: c.configId,
|
||||
itemName: cfg?.itemName || '',
|
||||
type: cfg?.type,
|
||||
isQualified: c.isQualified || '1',
|
||||
remark: c.remark || '',
|
||||
}
|
||||
}),
|
||||
acceptTeam: (d.team || []).map((t: any) => ({
|
||||
name: t.name,
|
||||
deptCode: t.deptCode,
|
||||
deptName: t.deptName,
|
||||
roleType: t.roleType || '',
|
||||
})),
|
||||
}
|
||||
if (batchForms[b.batch].acceptTeam.length < 3) {
|
||||
while (batchForms[b.batch].acceptTeam.length < 3) {
|
||||
batchForms[b.batch].acceptTeam.push({ name: '', deptCode: '', deptName: '' })
|
||||
}
|
||||
}
|
||||
if (acceptanceItems.value.length && (!batchForms[b.batch].acceptContents || batchForms[b.batch].acceptContents.length === 0)) {
|
||||
batchForms[b.batch].acceptContents = acceptanceItems.value.map((it: any) => ({
|
||||
configId: it.id,
|
||||
itemName: it.itemName,
|
||||
type: it.type,
|
||||
isQualified: '1',
|
||||
remark: '',
|
||||
}))
|
||||
}
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
}
|
||||
|
||||
const saveCommonConfig = async () => {
|
||||
const formRef = commonFormRef.value
|
||||
const valid = await formRef?.validate?.().catch(() => false)
|
||||
if (!valid) return
|
||||
// 直接从子组件 form 读取,确保拿到用户填写的最新值(避免 v-model 同步延迟)
|
||||
const form = formRef?.form || commonForm.value
|
||||
const isInstallment = form.isInstallment === '1' || form.isInstallment === 1
|
||||
if (isInstallment && (!form.totalPhases || form.totalPhases < 1)) {
|
||||
useMessage().error('请填写分期次数')
|
||||
return
|
||||
}
|
||||
saving.value = true
|
||||
try {
|
||||
await apiSaveCommonConfig({
|
||||
purchaseId: String(purchaseId.value),
|
||||
hasContract: form.hasContract ?? '0',
|
||||
contractId: form.contractId ?? '',
|
||||
isInstallment: form.isInstallment ?? '0',
|
||||
totalPhases: isInstallment ? (Number(form.totalPhases) || 1) : 1,
|
||||
supplierName: String(form.supplierName ?? ''),
|
||||
supplierContact: String(form.supplierContact ?? ''),
|
||||
purchaserId: String(form.purchaserId ?? ''),
|
||||
purchaserName: String(form.purchaserName ?? ''),
|
||||
assetAdminId: String(form.assetAdminId ?? ''),
|
||||
assetAdminName: String(form.assetAdminName ?? ''),
|
||||
})
|
||||
useMessage().success('保存成功')
|
||||
await loadData()
|
||||
} catch (e: any) {
|
||||
useMessage().error(e?.msg || '保存失败')
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const saveCurrentBatch = async () => {
|
||||
const curBatch = Number(activeTab.value)
|
||||
const batchFormRef = batchFormRefMap.value[curBatch]
|
||||
const valid = await batchFormRef?.validate?.().catch(() => false)
|
||||
if (!valid) return
|
||||
|
||||
const b = batches.value.find((x: any) => String(x.batch) === activeTab.value)
|
||||
if (!b?.id) return
|
||||
const form = batchForms[curBatch]
|
||||
if (!form) return
|
||||
|
||||
if (!form.acceptType) {
|
||||
useMessage().error('请选择验收方式')
|
||||
return
|
||||
}
|
||||
if (!form.acceptDate) {
|
||||
useMessage().error('请选择验收日期')
|
||||
return
|
||||
}
|
||||
|
||||
const team = (form.acceptTeam || []).filter((m: any) => m?.name)
|
||||
if (team.length < 3 || team.length % 2 === 0) {
|
||||
useMessage().error('验收小组至少3人且为单数')
|
||||
return
|
||||
}
|
||||
|
||||
saving.value = true
|
||||
try {
|
||||
await updateBatch({
|
||||
id: b.id,
|
||||
purchaseId: String(purchaseId.value),
|
||||
acceptType: form.acceptType,
|
||||
acceptDate: form.acceptDate,
|
||||
acceptAddress: form.acceptAddress,
|
||||
question: form.question,
|
||||
remark: form.remark,
|
||||
templateFileIds: form.templateFileIds || [],
|
||||
acceptContents: form.acceptContents || [],
|
||||
acceptTeam: team,
|
||||
})
|
||||
useMessage().success('保存成功')
|
||||
batchSavedFlags.value[curBatch] = true
|
||||
await loadData()
|
||||
} catch (e: any) {
|
||||
useMessage().error(e?.msg || '保存失败')
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
visible.value = false
|
||||
emit('refresh')
|
||||
}
|
||||
|
||||
// 下载履约验收模板
|
||||
const handleDownloadTemplate = async () => {
|
||||
if (!purchaseId.value) {
|
||||
useMessage().error('请先选择采购项目')
|
||||
return
|
||||
}
|
||||
|
||||
// 默认下载当前期
|
||||
await downloadTemplateForBatch(Number(activeTab.value))
|
||||
}
|
||||
|
||||
// 处理下拉菜单命令
|
||||
const handleDownloadTemplateCommand = async (command: string) => {
|
||||
if (!purchaseId.value) {
|
||||
useMessage().error('请先选择采购项目')
|
||||
return
|
||||
}
|
||||
|
||||
if (command === 'current') {
|
||||
await downloadTemplateForBatch(Number(activeTab.value))
|
||||
} else if (command === 'all') {
|
||||
// 下载全部期数的模板
|
||||
for (const batch of batches.value) {
|
||||
await downloadTemplateForBatch(batch.batch)
|
||||
}
|
||||
useMessage().success(`已触发${batches.value.length}期模板下载`)
|
||||
}
|
||||
}
|
||||
|
||||
// 为指定批次下载模板
|
||||
const downloadTemplateForBatch = async (batchNum: number) => {
|
||||
try {
|
||||
const response = await downloadPerformanceAcceptanceTemplate(String(purchaseId.value), batchNum)
|
||||
|
||||
// 使用项目中现有的工具函数处理文件下载
|
||||
const fileName = `履约验收表-${purchaseId.value}-第${batchNum}期-${new Date().getTime()}.docx`;
|
||||
handleBlobFile(response, fileName)
|
||||
|
||||
useMessage().success(`第${batchNum}期履约模板下载成功`)
|
||||
} catch (error: any) {
|
||||
console.error('下载模板失败:', error)
|
||||
useMessage().error(error?.msg || '下载履约模板失败')
|
||||
}
|
||||
}
|
||||
|
||||
const DEFAULT_COMMON_FORM = {
|
||||
hasContract: '0',
|
||||
contractId: '',
|
||||
isInstallment: '0',
|
||||
totalPhases: 1,
|
||||
supplierName: '',
|
||||
supplierContact: '',
|
||||
purchaserId: '',
|
||||
purchaserName: '',
|
||||
assetAdminId: '',
|
||||
assetAdminName: '',
|
||||
}
|
||||
|
||||
/** 将弹窗内所有内容恢复为初始空值(替换整个对象以确保引用变化) */
|
||||
const resetAllToDefault = () => {
|
||||
openToken.value++
|
||||
commonForm.value = { ...DEFAULT_COMMON_FORM }
|
||||
applyInfo.value = null
|
||||
mainTab.value = 'common'
|
||||
activeTab.value = '1'
|
||||
batchFormRefMap.value = {}
|
||||
batches.value = []
|
||||
acceptanceItems.value = []
|
||||
canFillForm.value = true
|
||||
Object.keys(batchForms).forEach((k) => delete batchForms[Number(k)])
|
||||
batchSavedFlags.value = {}
|
||||
}
|
||||
|
||||
const open = async (row: any) => {
|
||||
purchaseId.value = row?.id ?? ''
|
||||
rowProjectType.value = row?.projectType || 'A'
|
||||
|
||||
// 1. 先将弹窗内所有内容恢复为初始空值
|
||||
resetAllToDefault()
|
||||
|
||||
// 2. 显示弹窗并开启 loading,避免接口返回前展示旧数据
|
||||
visible.value = true
|
||||
loading.value = true
|
||||
|
||||
// 3. 等待 Vue 完成渲染,确保子组件已接收并展示空值
|
||||
await nextTick()
|
||||
await nextTick()
|
||||
|
||||
// 4. 再进行接口查询并覆盖
|
||||
await loadData()
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.modal-body {
|
||||
padding: 0;
|
||||
max-height: 70vh;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
.main-tab-nav {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
margin-bottom: 16px;
|
||||
border-bottom: 1px solid var(--el-border-color);
|
||||
}
|
||||
.main-tab-item {
|
||||
padding: 12px 20px;
|
||||
cursor: pointer;
|
||||
color: var(--el-text-color-regular);
|
||||
border-bottom: 2px solid transparent;
|
||||
margin-bottom: -1px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.main-tab-item:hover {
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
.main-tab-item.active {
|
||||
color: var(--el-color-primary);
|
||||
font-weight: 600;
|
||||
border-bottom-color: var(--el-color-primary);
|
||||
}
|
||||
.main-tab-content {
|
||||
padding-top: 4px;
|
||||
}
|
||||
.tab-content {
|
||||
min-height: 200px;
|
||||
display: block;
|
||||
}
|
||||
.tip-box {
|
||||
padding: 20px 0;
|
||||
}
|
||||
.batch-tabs {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-bottom: 16px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.batch-tab-item {
|
||||
padding: 8px 16px;
|
||||
border: 1px solid var(--el-border-color);
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.batch-tab-item:hover:not(.disabled) {
|
||||
border-color: var(--el-color-primary);
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
.batch-tab-item.active {
|
||||
background: var(--el-color-primary);
|
||||
border-color: var(--el-color-primary);
|
||||
color: #fff;
|
||||
}
|
||||
.batch-tab-item.disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.6;
|
||||
}
|
||||
.batch-panel {
|
||||
min-height: 200px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
/* 弹窗横向滚动修复,需非 scoped 以影响 el-dialog */
|
||||
.purchasing-accept-modal .el-dialog__body {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
</style>
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
355
src/views/finance/purchasingrequisition/implement.vue
Normal file
355
src/views/finance/purchasingrequisition/implement.vue
Normal file
@@ -0,0 +1,355 @@
|
||||
<template>
|
||||
<div class="implement-page">
|
||||
<div class="implement-form">
|
||||
<el-form-item label="实施采购方式" required>
|
||||
<el-radio-group v-model="implementType">
|
||||
<el-radio label="1">自行组织采购</el-radio>
|
||||
<el-radio label="2">委托代理采购</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 采购文件版本列表:保留原文件,多版本分别显示 -->
|
||||
<el-divider content-position="left">采购文件版本</el-divider>
|
||||
<div v-if="purchaseFileVersions.length" class="file-versions mb-2">
|
||||
<el-table :data="purchaseFileVersions" border size="small" max-height="280">
|
||||
<el-table-column type="index" label="版本" width="70" align="center">
|
||||
<template #default="{ $index }">V{{ $index + 1 }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="fileTitle" label="文件名" min-width="180" show-overflow-tooltip />
|
||||
<el-table-column prop="createBy" label="上传人" width="100" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="createTime" label="上传时间" width="165" align="center">
|
||||
<template #default="{ row }">{{ formatCreateTime(row.createTime) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="90" align="center" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link size="small" @click="handleDownloadVersion(row)">下载</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
<div class="mb-2">可继续上传新版本(保留原文件,格式 doc/docx/pdf,单文件不超过 5MB):</div>
|
||||
<upload-file
|
||||
v-model="implementFileIds"
|
||||
:limit="5"
|
||||
:file-type="['doc', 'docx', 'pdf']"
|
||||
:data="{ fileType: PURCHASE_FILE_TYPE }"
|
||||
upload-file-url="/purchase/purchasingfiles/upload"
|
||||
/>
|
||||
|
||||
<!-- 仅部门审核角色显示:采购代表相关 -->
|
||||
<template v-if="isDeptAuditRole">
|
||||
<el-divider content-position="left">采购代表</el-divider>
|
||||
<div class="mb-2">需求部门初审需指定采购代表人,请选择一种方式:</div>
|
||||
<el-radio-group v-model="representorMode" class="mb-2">
|
||||
<el-radio label="single">指定采购代表人(单人)</el-radio>
|
||||
<el-radio label="multi">部门多人由系统自动抽取</el-radio>
|
||||
</el-radio-group>
|
||||
<el-form-item v-if="representorMode === 'single'" label="采购代表人">
|
||||
<el-select v-model="representorTeacherNo" placeholder="请选择" clearable filterable style="width: 100%">
|
||||
<el-option v-for="m in deptMembers" :key="m.userId || m.teacherNo || m.id" :label="m.realName || m.name || m.teacherNo" :value="m.userId || m.teacherNo || m.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item v-else label="部门多人">
|
||||
<el-select v-model="representorsMulti" placeholder="请选择多人,系统将自动抽取一人" clearable filterable multiple style="width: 100%">
|
||||
<el-option v-for="m in deptMembers" :key="m.userId || m.teacherNo || m.id" :label="m.realName || m.name || m.teacherNo" :value="m.userId || m.teacherNo || m.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
</div>
|
||||
<div class="implement-footer">
|
||||
<el-button @click="handleClose">取消</el-button>
|
||||
<template v-if="implementHasPurchaseFiles && !applyRow?.fileFlowInstId">
|
||||
<el-button type="primary" :loading="implementSubmitting" @click="handleImplementSubmit">保存实施采购</el-button>
|
||||
<el-button v-if="canStartFileFlow" type="success" :loading="startFileFlowSubmitting" @click="handleStartFileFlow">发起采购文件审批</el-button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-button type="primary" :loading="implementSubmitting" @click="handleImplementSubmit">确定</el-button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="PurchasingImplement">
|
||||
import { ref, computed, onMounted, watch } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { implementApply, getApplyFiles, startFileFlow, getDeptMembers, getObj } from '/@/api/finance/purchasingrequisition'
|
||||
import { useMessage } from '/@/hooks/message'
|
||||
import other from '/@/utils/other'
|
||||
import UploadFile from '/@/components/Upload/index.vue'
|
||||
import { Session } from '/@/utils/storage'
|
||||
import * as orderVue from '/@/api/order/order-key-vue'
|
||||
|
||||
/** 部门审核角色编码:仅该角色下显示采购代表相关页面和功能,流转至部门审核时需填写采购代表 */
|
||||
const PURCHASE_DEPT_AUDIT_ROLE_CODE = 'PURCHASE_DEPT_AUDIT'
|
||||
/** 采购中心角色编码:可保存/发起实施采购,但不出现采购代表相关内容和接口 */
|
||||
const PURCHASE_CENTER_ROLE_CODE = 'PURCHASE_CENTER'
|
||||
|
||||
const roleCode = computed(() => Session.getRoleCode() || '')
|
||||
const isDeptAuditRole = computed(() => roleCode.value === PURCHASE_DEPT_AUDIT_ROLE_CODE)
|
||||
const isPurchaseCenterRole = computed(() => roleCode.value === PURCHASE_CENTER_ROLE_CODE)
|
||||
/** 可发起采购文件审批:部门审核(需填采购代表)、采购中心(不填采购代表) */
|
||||
const canStartFileFlow = computed(() => isDeptAuditRole.value || isPurchaseCenterRole.value)
|
||||
|
||||
// 与编辑界面一致:支持流程 dynamic-link 传入 currJob/currElTab,申请单 ID 优先取 currJob.orderId
|
||||
const props = defineProps({
|
||||
currJob: { type: Object, default: null },
|
||||
currElTab: { type: Object, default: null }
|
||||
})
|
||||
const emit = defineEmits(['handleJob'])
|
||||
|
||||
/** 是否被流程 handle 页面通过 dynamic-link 嵌入 */
|
||||
const isFlowEmbed = computed(() => !!props.currJob)
|
||||
|
||||
const route = useRoute()
|
||||
const PURCHASE_FILE_TYPE = '130'
|
||||
|
||||
/** 申请单 ID(数值,用于 getObj 等):与 add 一致,优先流程 currJob.orderId,否则 route.query.id */
|
||||
const applyId = computed(() => {
|
||||
const raw = applyIdRaw.value
|
||||
if (raw == null || raw === '') return null
|
||||
const n = Number(raw)
|
||||
return Number.isNaN(n) ? null : n
|
||||
})
|
||||
|
||||
/** 申请单 ID 原始字符串(用于 getApplyFiles 的 purchaseId,与编辑页一致,避免类型/精度问题) */
|
||||
const applyIdRaw = computed(() => {
|
||||
if (props.currJob?.orderId != null && props.currJob?.orderId !== '') {
|
||||
return String(props.currJob.orderId)
|
||||
}
|
||||
const id = route.query.id
|
||||
return id != null && id !== '' ? String(id) : ''
|
||||
})
|
||||
|
||||
const applyRow = ref<any>(null)
|
||||
/** 已有采购文件版本列表(按 createTime 排序,用于展示与提交时一并关联) */
|
||||
const purchaseFileVersions = ref<{ id: string; fileTitle?: string; createBy?: string; createTime?: string; remark?: string }[]>([])
|
||||
/** 本次新上传的采购文件(仅新版本,不与已有版本混在一起) */
|
||||
const implementFileIds = ref<string | string[]>([])
|
||||
const implementType = ref<string>('1')
|
||||
const implementSubmitting = ref(false)
|
||||
|
||||
const representorMode = ref<'single' | 'multi'>('single')
|
||||
const representorTeacherNo = ref<string>('')
|
||||
const representorsMulti = ref<string[]>([])
|
||||
const deptMembers = ref<any[]>([])
|
||||
const startFileFlowSubmitting = ref(false)
|
||||
|
||||
const implementHasPurchaseFiles = computed(() => {
|
||||
if (purchaseFileVersions.value.length > 0) return true
|
||||
const raw = implementFileIds.value
|
||||
if (Array.isArray(raw)) return raw.length > 0
|
||||
return !!raw
|
||||
})
|
||||
|
||||
function formatCreateTime(t?: string) {
|
||||
if (!t) return '-'
|
||||
const d = new Date(t)
|
||||
return isNaN(d.getTime()) ? t : d.toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' })
|
||||
}
|
||||
|
||||
function handleDownloadVersion(file: { remark?: string; fileTitle?: string }) {
|
||||
if (!file?.remark) {
|
||||
useMessage().warning('无法获取文件路径')
|
||||
return
|
||||
}
|
||||
const url = `/purchase/purchasingfiles/download?fileName=${encodeURIComponent(file.remark)}&fileTitle=${encodeURIComponent(file.fileTitle || '采购文件')}`
|
||||
other.downBlobFile(url, {}, file.fileTitle || '采购文件')
|
||||
}
|
||||
|
||||
const isInIframe = () => typeof window !== 'undefined' && window.self !== window.top
|
||||
|
||||
const postMessage = (type: string, payload?: any) => {
|
||||
if (typeof window !== 'undefined' && window.parent) {
|
||||
window.parent.postMessage({ type, ...payload }, '*')
|
||||
}
|
||||
}
|
||||
|
||||
const loadData = async () => {
|
||||
const id = applyId.value
|
||||
if (!id) {
|
||||
useMessage().warning('缺少申请单ID')
|
||||
return
|
||||
}
|
||||
const needDeptMembers = isDeptAuditRole.value
|
||||
try {
|
||||
const idStr = applyIdRaw.value || String(id)
|
||||
const requests: [ReturnType<typeof getObj>, ReturnType<typeof getApplyFiles>, ReturnType<typeof getDeptMembers>?] = [
|
||||
getObj(id),
|
||||
getApplyFiles(idStr)
|
||||
]
|
||||
if (needDeptMembers) requests.push(getDeptMembers())
|
||||
const results = await Promise.all(requests)
|
||||
const detailRes = results[0]
|
||||
const filesRes = results[1]
|
||||
const membersRes = needDeptMembers ? results[2] : null
|
||||
|
||||
applyRow.value = detailRes?.data ? { ...detailRes.data, id: detailRes.data.id ?? id } : { id }
|
||||
const row = applyRow.value
|
||||
if (row?.implementType) implementType.value = row.implementType
|
||||
// 回显需求部门初审-采购代表人方式与人员(与发起采购文件审批时保存的一致)
|
||||
if (row?.representorTeacherNo) {
|
||||
representorMode.value = 'single'
|
||||
representorTeacherNo.value = row.representorTeacherNo ?? ''
|
||||
representorsMulti.value = []
|
||||
} else if (row?.representors) {
|
||||
representorMode.value = 'multi'
|
||||
representorTeacherNo.value = ''
|
||||
const parts = typeof row.representors === 'string' ? row.representors.split(',') : []
|
||||
representorsMulti.value = parts.map((s: string) => s.trim()).filter(Boolean)
|
||||
} else {
|
||||
representorTeacherNo.value = ''
|
||||
representorsMulti.value = []
|
||||
}
|
||||
const list = filesRes?.data || []
|
||||
const purchaseFiles = list.filter((f: any) => f.fileType === PURCHASE_FILE_TYPE)
|
||||
purchaseFileVersions.value = purchaseFiles.map((f: any) => ({
|
||||
id: String(f.id),
|
||||
fileTitle: f.fileTitle || f.file_title || '采购文件',
|
||||
createBy: f.createBy ?? f.create_by ?? '-',
|
||||
createTime: f.createTime || f.create_time,
|
||||
remark: f.remark
|
||||
}))
|
||||
deptMembers.value = needDeptMembers && membersRes?.data ? membersRes.data : []
|
||||
} catch (_) {
|
||||
applyRow.value = { id }
|
||||
purchaseFileVersions.value = []
|
||||
deptMembers.value = []
|
||||
}
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
postMessage('purchasingimplement:close')
|
||||
if (!isInIframe()) {
|
||||
window.history.back()
|
||||
}
|
||||
}
|
||||
|
||||
const handleImplementSubmit = async () => {
|
||||
const row = applyRow.value
|
||||
if (!row?.id && !applyId.value) return
|
||||
const id = row?.id ?? applyId.value
|
||||
if (!id) return
|
||||
if (!implementType.value) {
|
||||
useMessage().warning('请选择实施采购方式')
|
||||
return
|
||||
}
|
||||
const existingIds = purchaseFileVersions.value.map((f) => f.id)
|
||||
const raw = implementFileIds.value
|
||||
const newIds: string[] = Array.isArray(raw)
|
||||
? raw.map((x: any) => (typeof x === 'object' && x?.id ? x.id : x)).filter(Boolean)
|
||||
: raw ? [String(raw)] : []
|
||||
const fileIds = [...existingIds, ...newIds]
|
||||
if (fileIds.length === 0) {
|
||||
useMessage().warning('请至少上传一个采购文件')
|
||||
return
|
||||
}
|
||||
// 仅部门审核角色提交采购代表;采购中心保存时不传采购代表
|
||||
const single = isDeptAuditRole.value && representorMode.value === 'single' ? representorTeacherNo.value : undefined
|
||||
const multi = isDeptAuditRole.value && representorMode.value === 'multi' && representorsMulti.value?.length ? representorsMulti.value.join(',') : undefined
|
||||
implementSubmitting.value = true
|
||||
try {
|
||||
await implementApply(id, fileIds, implementType.value, single, multi)
|
||||
useMessage().success('实施采购已保存')
|
||||
implementFileIds.value = []
|
||||
await loadData()
|
||||
postMessage('purchasingimplement:saved')
|
||||
// 流程嵌入场景:通知流程当前 Tab 已保存,避免审批时提示“未保存”
|
||||
if (isFlowEmbed.value && props.currJob && props.currElTab?.id) {
|
||||
orderVue.currElTabIsSave(props.currJob, props.currElTab.id, true, emit)
|
||||
}
|
||||
} catch (err: any) {
|
||||
useMessage().error(err?.msg || '实施采购失败')
|
||||
} finally {
|
||||
implementSubmitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleStartFileFlow = async () => {
|
||||
const row = applyRow.value
|
||||
const id = row?.id ?? applyId.value
|
||||
if (!id) return
|
||||
// 部门审核角色必须填写采购代表;采购中心不填采购代表
|
||||
if (isDeptAuditRole.value) {
|
||||
if (representorMode.value === 'single') {
|
||||
if (!representorTeacherNo.value) {
|
||||
useMessage().warning('请选择采购代表人')
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if (!representorsMulti.value?.length) {
|
||||
useMessage().warning('请选择部门多人')
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
startFileFlowSubmitting.value = true
|
||||
try {
|
||||
const single = isDeptAuditRole.value && representorMode.value === 'single' ? representorTeacherNo.value : undefined
|
||||
const multi = isDeptAuditRole.value && representorMode.value === 'multi' ? representorsMulti.value.join(',') : undefined
|
||||
await startFileFlow(id, single, multi)
|
||||
useMessage().success('已发起采购文件审批流程')
|
||||
postMessage('purchasingimplement:submitSuccess')
|
||||
await loadData()
|
||||
} catch (err: any) {
|
||||
useMessage().error(err?.msg || '发起失败')
|
||||
} finally {
|
||||
startFileFlowSubmitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 流程嵌入时供 handle.vue 调用的“保存”回调:与页面按钮保存逻辑保持一致 */
|
||||
async function flowSubmitForm() {
|
||||
await handleImplementSubmit()
|
||||
}
|
||||
|
||||
// 流程切换工单时重新加载数据(与 add 编辑页一致)
|
||||
watch(
|
||||
() => props.currJob?.orderId ?? props.currJob?.id,
|
||||
(newVal, oldVal) => {
|
||||
if (newVal !== oldVal && applyId.value) {
|
||||
loadData()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
onMounted(async () => {
|
||||
await loadData()
|
||||
if (isInIframe()) {
|
||||
document.documentElement.classList.add('iframe-mode')
|
||||
document.body.classList.add('iframe-mode')
|
||||
}
|
||||
// 流程嵌入:注册 tab 保存回调,供审批页调用(与采购申请编辑页保持一致)
|
||||
if (isFlowEmbed.value && props.currJob && props.currElTab?.id) {
|
||||
orderVue.currElTabIsExist(props.currJob, props.currElTab.id)
|
||||
await orderVue.currElTabIsView({}, props.currJob, props.currElTab.id, flowSubmitForm)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.implement-page {
|
||||
padding: 20px;
|
||||
min-height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.implement-form {
|
||||
flex: 1;
|
||||
.mb-2 {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
}
|
||||
.implement-form-tip {
|
||||
margin-top: 12px;
|
||||
padding: 8px 0;
|
||||
}
|
||||
.implement-footer {
|
||||
margin-top: 24px;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid var(--el-border-color-lighter);
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
</style>
|
||||
95
src/views/finance/purchasingrequisition/implementForm.vue
Normal file
95
src/views/finance/purchasingrequisition/implementForm.vue
Normal file
@@ -0,0 +1,95 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
title="实施采购"
|
||||
width="50%"
|
||||
:close-on-click-modal="false"
|
||||
destroy-on-close
|
||||
class="implement-iframe-dialog"
|
||||
@close="handleClose"
|
||||
>
|
||||
<div class="implement-iframe-content">
|
||||
<iframe
|
||||
ref="iframeRef"
|
||||
:src="iframeSrc"
|
||||
frameborder="0"
|
||||
class="implement-iframe"
|
||||
/>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="ImplementForm">
|
||||
import { ref, computed, watch } from 'vue'
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'refresh'): void
|
||||
}>()
|
||||
|
||||
const visible = ref(false)
|
||||
const iframeRef = ref<HTMLIFrameElement>()
|
||||
const applyId = ref<string | number>('')
|
||||
|
||||
const iframeSrc = computed(() => {
|
||||
const baseUrl = window.location.origin + window.location.pathname
|
||||
return `${baseUrl}#/finance/purchasingrequisition/implement?id=${applyId.value}`
|
||||
})
|
||||
|
||||
const handleClose = () => {
|
||||
visible.value = false
|
||||
window.removeEventListener('message', handleMessage)
|
||||
}
|
||||
|
||||
const handleMessage = (event: MessageEvent) => {
|
||||
if (event.data?.type === 'purchasingimplement:submitSuccess') {
|
||||
handleClose()
|
||||
emit('refresh')
|
||||
} else if (event.data?.type === 'purchasingimplement:close') {
|
||||
handleClose()
|
||||
} else if (event.data?.type === 'purchasingimplement:saved') {
|
||||
emit('refresh')
|
||||
}
|
||||
}
|
||||
|
||||
const openDialog = (row: { id: string | number }) => {
|
||||
applyId.value = row?.id ?? ''
|
||||
visible.value = true
|
||||
window.addEventListener('message', handleMessage)
|
||||
}
|
||||
|
||||
watch(visible, (val) => {
|
||||
if (!val) {
|
||||
window.removeEventListener('message', handleMessage)
|
||||
}
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
openDialog,
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.implement-iframe-content {
|
||||
width: 100%;
|
||||
height: 65vh;
|
||||
min-height: 480px;
|
||||
max-height: calc(100vh - 180px);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
.implement-iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 480px;
|
||||
border: none;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
.implement-iframe-dialog .el-dialog__body {
|
||||
padding: 16px;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
@@ -43,12 +43,11 @@
|
||||
placeholder="请选择状态"
|
||||
clearable
|
||||
style="width: 200px">
|
||||
<el-option label="撤回" value="-2" />
|
||||
|
||||
<el-option label="暂存" value="-1" />
|
||||
<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="isCentralized">
|
||||
@@ -77,6 +76,14 @@
|
||||
采购申请管理
|
||||
</span>
|
||||
<div class="header-actions">
|
||||
<el-button
|
||||
icon="Files"
|
||||
link
|
||||
type="primary"
|
||||
|
||||
>
|
||||
采购申请汇总
|
||||
</el-button>
|
||||
<el-button
|
||||
icon="FolderAdd"
|
||||
type="primary"
|
||||
@@ -102,7 +109,7 @@
|
||||
<el-icon><List /></el-icon>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="code" label="申请单编号" min-width="140" show-overflow-tooltip>
|
||||
<el-table-column prop="purchaseNo" label="申请单编号" min-width="140" show-overflow-tooltip>
|
||||
<template #header>
|
||||
<el-icon><DocumentCopy /></el-icon>
|
||||
<span style="margin-left: 4px">申请单编号</span>
|
||||
@@ -160,8 +167,10 @@
|
||||
<span style="margin-left: 4px">是否特殊</span>
|
||||
</template>
|
||||
<template #default="scope">
|
||||
<el-tag v-if="scope.row.isSpecial === '1' || scope.row.isSpecial === 1" type="warning">是</el-tag>
|
||||
<el-tag v-else-if="scope.row.isSpecial === '0' || scope.row.isSpecial === 0" type="info">否</el-tag>
|
||||
<el-tag v-if="String(scope.row.isSpecial) === '1'" type="warning">紧急</el-tag>
|
||||
<el-tag v-else-if="String(scope.row.isSpecial) === '2'" type="danger">单一</el-tag>
|
||||
<el-tag v-else-if="String(scope.row.isSpecial) === '3'" type="info">进口</el-tag>
|
||||
<el-tag v-else-if="String(scope.row.isSpecial) === '0'" type="info">否</el-tag>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -171,8 +180,8 @@
|
||||
<span style="margin-left: 4px">是否集采</span>
|
||||
</template>
|
||||
<template #default="scope">
|
||||
<el-tag v-if="scope.row.isCentralized === '1'" type="success">是</el-tag>
|
||||
<el-tag v-else-if="scope.row.isCentralized === '0'" type="info">否</el-tag>
|
||||
<el-tag v-if="String(scope.row.isCentralized) === '1'" type="success">是</el-tag>
|
||||
<el-tag v-else-if="String(scope.row.isCentralized) === '0'" type="info">否</el-tag>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -182,40 +191,111 @@
|
||||
<span style="margin-left: 4px">审核状态</span>
|
||||
</template>
|
||||
<template #default="scope">
|
||||
<el-tag v-if="scope.row.status === '-2'" type="info">撤回</el-tag>
|
||||
<el-tag v-else-if="scope.row.status === '-1'" type="warning">暂存</el-tag>
|
||||
<el-tag v-else-if="scope.row.status === '0'" type="primary">运行中</el-tag>
|
||||
<el-tag v-else-if="scope.row.status === '1'" type="success">完成</el-tag>
|
||||
<el-tag v-else-if="scope.row.status === '2'" type="danger">作废</el-tag>
|
||||
<el-tag v-else-if="scope.row.status === '3'" type="info">终止</el-tag>
|
||||
<span v-else>-</span>
|
||||
<el-tooltip v-if="scope.row.flowInstId" content="点击查看审批过程" placement="top">
|
||||
<el-tag
|
||||
v-if="scope.row.status === '-2'"
|
||||
type="info"
|
||||
class="status-tag-clickable"
|
||||
@click="handleShowFlowComment(scope.row)">撤回</el-tag>
|
||||
<el-tag
|
||||
v-else-if="scope.row.status === '-1'"
|
||||
type="warning"
|
||||
class="status-tag-clickable"
|
||||
@click="handleShowFlowComment(scope.row)">暂存</el-tag>
|
||||
<el-tag
|
||||
v-else-if="scope.row.status === '0'"
|
||||
type="primary"
|
||||
class="status-tag-clickable"
|
||||
@click="handleShowFlowComment(scope.row)">运行中</el-tag>
|
||||
<el-tag
|
||||
v-else-if="scope.row.status === '1'"
|
||||
type="success"
|
||||
class="status-tag-clickable"
|
||||
@click="handleShowFlowComment(scope.row)">完成</el-tag>
|
||||
<el-tag
|
||||
v-else-if="scope.row.status === '2'"
|
||||
type="danger"
|
||||
class="status-tag-clickable"
|
||||
@click="handleShowFlowComment(scope.row)">作废</el-tag>
|
||||
<el-tag
|
||||
v-else-if="scope.row.status === '3'"
|
||||
type="info"
|
||||
class="status-tag-clickable"
|
||||
@click="handleShowFlowComment(scope.row)">终止</el-tag>
|
||||
<span v-else>-</span>
|
||||
</el-tooltip>
|
||||
<template v-else>
|
||||
<el-tag v-if="scope.row.status === '-2'" type="info">撤回</el-tag>
|
||||
<el-tag v-else-if="scope.row.status === '-1'" type="warning">暂存</el-tag>
|
||||
<el-tag v-else-if="scope.row.status === '0'" type="primary">运行中</el-tag>
|
||||
<el-tag v-else-if="scope.row.status === '1'" type="success">完成</el-tag>
|
||||
<el-tag v-else-if="scope.row.status === '2'" type="danger">作废</el-tag>
|
||||
<el-tag v-else-if="scope.row.status === '3'" type="info">终止</el-tag>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" fixed="right" width="300">
|
||||
<el-table-column prop="fileFlowStatus" label="文件审批状态" width="110" align="center">
|
||||
<template #header>
|
||||
<el-icon><DocumentChecked /></el-icon>
|
||||
<span style="margin-left: 4px">文件审批状态</span>
|
||||
</template>
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
icon="View"
|
||||
link
|
||||
type="primary"
|
||||
@click="formDialogRef.openDialog('view', scope.row)">
|
||||
查看
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="scope.row.status === '-1'"
|
||||
icon="Edit"
|
||||
link
|
||||
type="primary"
|
||||
@click="formDialogRef.openDialog('edit', scope.row)">
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="scope.row.status === '-1'"
|
||||
icon="Delete"
|
||||
link
|
||||
type="danger"
|
||||
@click="handleDelete(scope.row)">
|
||||
删除
|
||||
</el-button>
|
||||
<template v-if="scope.row.fileFlowInstId">
|
||||
<el-tooltip content="点击查看审批过程" placement="top">
|
||||
<el-tag
|
||||
v-if="scope.row.fileFlowStatus === '-2'"
|
||||
type="info"
|
||||
class="status-tag-clickable"
|
||||
@click="handleShowFileFlowComment(scope.row)">撤回</el-tag>
|
||||
<el-tag
|
||||
v-else-if="scope.row.fileFlowStatus === '-1'"
|
||||
type="warning"
|
||||
class="status-tag-clickable"
|
||||
@click="handleShowFileFlowComment(scope.row)">暂存</el-tag>
|
||||
<el-tag
|
||||
v-else-if="scope.row.fileFlowStatus === '0'"
|
||||
type="primary"
|
||||
class="status-tag-clickable"
|
||||
@click="handleShowFileFlowComment(scope.row)">运行中</el-tag>
|
||||
<el-tag
|
||||
v-else-if="scope.row.fileFlowStatus === '1'"
|
||||
type="success"
|
||||
class="status-tag-clickable"
|
||||
@click="handleShowFileFlowComment(scope.row)">完成</el-tag>
|
||||
<el-tag
|
||||
v-else-if="scope.row.fileFlowStatus === '2'"
|
||||
type="danger"
|
||||
class="status-tag-clickable"
|
||||
@click="handleShowFileFlowComment(scope.row)">作废</el-tag>
|
||||
<el-tag
|
||||
v-else-if="scope.row.fileFlowStatus === '3'"
|
||||
type="info"
|
||||
class="status-tag-clickable"
|
||||
@click="handleShowFileFlowComment(scope.row)">终止</el-tag>
|
||||
<span v-else class="status-tag-clickable" @click="handleShowFileFlowComment(scope.row)">-</span>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span style="color: #909399;">—</span>
|
||||
</template>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" fixed="right" width="150">
|
||||
<template #default="scope">
|
||||
<div class="op-cell">
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
icon="View"
|
||||
@click="handleView(scope.row)">
|
||||
查看
|
||||
</el-button>
|
||||
<ActionDropdown
|
||||
:items="getActionMenuItems(scope.row)"
|
||||
@command="(command) => handleMoreCommand(command, scope.row)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -226,52 +306,56 @@
|
||||
:total="state.pagination.total"
|
||||
:current="state.pagination.current"
|
||||
:size="state.pagination.size"
|
||||
@pagination="getDataList"
|
||||
@sizeChange="sizeChangeHandle"
|
||||
@currentChange="currentChangeHandle"
|
||||
/>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<!-- 新增页面 iframe 对话框 -->
|
||||
<el-dialog
|
||||
v-model="showAddIframe"
|
||||
title="新增采购申请"
|
||||
width="90%"
|
||||
:style="{ maxWidth: '1600px' }"
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="true"
|
||||
destroy-on-close
|
||||
class="iframe-dialog"
|
||||
@close="closeAddIframe">
|
||||
<div class="iframe-dialog-content">
|
||||
<iframe
|
||||
ref="addIframeRef"
|
||||
:src="addIframeSrc"
|
||||
frameborder="0"
|
||||
class="add-iframe"
|
||||
/>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 编辑、新增表单对话框 -->
|
||||
<!-- 新增/编辑/查看:统一使用 form.vue 弹窗(iframe 引入 add.vue) -->
|
||||
<FormDialog
|
||||
ref="formDialogRef"
|
||||
:dict-data="dictData"
|
||||
@refresh="getDataList" />
|
||||
|
||||
<!-- 履约验收弹窗 -->
|
||||
<PurchasingAcceptModal ref="acceptModalRef" @refresh="getDataList" />
|
||||
|
||||
<!-- 查看审批过程(申请单审批 / 文件审批) -->
|
||||
<el-dialog
|
||||
v-model="showFlowComment"
|
||||
v-if="showFlowComment"
|
||||
:title="currFlowCommentType === 'file' ? '查看文件审批过程' : '查看审批过程'"
|
||||
top="20px"
|
||||
width="90%"
|
||||
append-to-body
|
||||
destroy-on-close
|
||||
@close="currFlowJob = null; currFlowCommentType = 'apply'">
|
||||
<FlowCommentTimeline v-if="currFlowJob" :key="String(currFlowJob.flowInstId) + currFlowCommentType" :curr-job="currFlowJob" />
|
||||
</el-dialog>
|
||||
|
||||
<!-- 实施采购:iframe 嵌入 implement.vue,供列表与流程页面使用 -->
|
||||
<ImplementForm ref="implementFormRef" @refresh="getDataList" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="PurchasingRequisition">
|
||||
import { ref, reactive, defineAsyncComponent, onUnmounted, onMounted } from 'vue'
|
||||
import { ref, reactive, defineAsyncComponent, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { BasicTableProps, useTable } from "/@/hooks/table";
|
||||
import { getPage, delObj } from "/@/api/finance/purchasingrequisition";
|
||||
import { getPage, delObj, submitObj, getArchiveDownloadUrl, getApplyTemplateDownloadUrl, getFileApplyTemplateDownloadUrl } from "/@/api/finance/purchasingrequisition";
|
||||
import { useMessage, useMessageBox } from "/@/hooks/message";
|
||||
import { getDicts } from '/@/api/admin/dict';
|
||||
import { getTree } from '/@/api/finance/purchasingcategory';
|
||||
import { List, Document, DocumentCopy, Search, Collection, Money, CircleCheck, InfoFilled, Calendar, OfficeBuilding, Warning } from '@element-plus/icons-vue'
|
||||
import { List, Document, DocumentCopy, Search, Collection, Money, CircleCheck, InfoFilled, Calendar, OfficeBuilding, Warning, DocumentChecked, Edit, Delete, Upload, FolderOpened, Download } from '@element-plus/icons-vue'
|
||||
import other from '/@/utils/other'
|
||||
|
||||
// 引入组件
|
||||
const FormDialog = defineAsyncComponent(() => import('./form.vue'));
|
||||
const ImplementForm = defineAsyncComponent(() => import('./implementForm.vue'));
|
||||
const ActionDropdown = defineAsyncComponent(() => import('/@/components/tools/action-dropdown.vue'));
|
||||
const PurchasingAcceptModal = defineAsyncComponent(() => import('./accept/PurchasingAcceptModal.vue'));
|
||||
const FlowCommentTimeline = defineAsyncComponent(() => import('/@/views/jsonflow/comment/timeline.vue'));
|
||||
|
||||
// 字典数据和品目树数据
|
||||
const dictData = ref({
|
||||
@@ -288,11 +372,15 @@ const dictData = ref({
|
||||
const router = useRouter()
|
||||
const tableRef = ref()
|
||||
const formDialogRef = ref()
|
||||
const acceptModalRef = ref()
|
||||
const searchFormRef = ref()
|
||||
const showSearch = ref(true)
|
||||
const showAddIframe = ref(false)
|
||||
const addIframeRef = ref<HTMLIFrameElement>()
|
||||
const addIframeSrc = ref('')
|
||||
/** 审批过程弹窗:是否显示、当前行对应的流程 job(供 Comment 组件用)、类型(申请单/文件) */
|
||||
const showFlowComment = ref(false)
|
||||
const currFlowJob = ref<{ id?: number; flowInstId?: number } | null>(null)
|
||||
const currFlowCommentType = ref<'apply' | 'file'>('apply')
|
||||
|
||||
const implementFormRef = ref()
|
||||
|
||||
/**
|
||||
* 定义响应式表格数据
|
||||
@@ -312,7 +400,7 @@ const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
/**
|
||||
* 使用 useTable 定义表格相关操作
|
||||
*/
|
||||
const { getDataList, tableStyle } = useTable(state);
|
||||
const { getDataList, tableStyle, sizeChangeHandle, currentChangeHandle } = useTable(state);
|
||||
|
||||
/**
|
||||
* 重置搜索表单
|
||||
@@ -323,42 +411,67 @@ const handleReset = () => {
|
||||
};
|
||||
|
||||
/**
|
||||
* 新增采购申请 - 在 iframe 中展示
|
||||
* 新增采购申请 - 统一通过 form.vue 弹窗(iframe 引入 add.vue)
|
||||
*/
|
||||
const handleAdd = () => {
|
||||
// 构建 iframe 的 src,使用当前页面的 hash 路由
|
||||
const baseUrl = window.location.origin + window.location.pathname
|
||||
addIframeSrc.value = `${baseUrl}#/finance/purchasingrequisition/add`
|
||||
showAddIframe.value = true
|
||||
// 监听来自 iframe 的消息
|
||||
window.addEventListener('message', handleIframeMessage)
|
||||
formDialogRef.value?.openDialog('add')
|
||||
};
|
||||
|
||||
/**
|
||||
* 关闭新增 iframe
|
||||
*/
|
||||
const closeAddIframe = () => {
|
||||
showAddIframe.value = false
|
||||
// 移除消息监听器
|
||||
window.removeEventListener('message', handleIframeMessage)
|
||||
};
|
||||
|
||||
/**
|
||||
* 处理 iframe 发送的消息
|
||||
* 点击审核状态:若有流程实例则打开「查看审批过程」弹窗(参考 hi-job.vue)
|
||||
* @param row - 当前行数据(需含 flowInstId)
|
||||
*/
|
||||
const handleIframeMessage = (event: MessageEvent) => {
|
||||
// 验证消息来源(可选,根据实际需求)
|
||||
// if (event.origin !== window.location.origin) return
|
||||
|
||||
if (event.data && event.data.type === 'purchasingrequisition:submitSuccess') {
|
||||
// 提交成功,关闭 iframe 并刷新列表
|
||||
closeAddIframe()
|
||||
getDataList()
|
||||
useMessage().success('提交成功')
|
||||
} else if (event.data && event.data.type === 'purchasingrequisition:close') {
|
||||
// 关闭 iframe
|
||||
closeAddIframe()
|
||||
/** 点击审核状态:打开申请单审批过程 */
|
||||
const handleShowFlowComment = (row: any) => {
|
||||
if (!row?.flowInstId) {
|
||||
useMessage().info('暂存状态无审批过程');
|
||||
return;
|
||||
}
|
||||
currFlowCommentType.value = 'apply';
|
||||
currFlowJob.value = { id: row.id, flowInstId: row.flowInstId };
|
||||
showFlowComment.value = true;
|
||||
};
|
||||
|
||||
/** 点击文件审批状态:打开文件审批过程 */
|
||||
const handleShowFileFlowComment = (row: any) => {
|
||||
if (!row?.fileFlowInstId) {
|
||||
useMessage().info('未发起文件审批流程');
|
||||
return;
|
||||
}
|
||||
currFlowCommentType.value = 'file';
|
||||
const flowInstId = typeof row.fileFlowInstId === 'string' ? parseInt(row.fileFlowInstId, 10) : row.fileFlowInstId;
|
||||
currFlowJob.value = { id: row.id, flowInstId: Number.isNaN(flowInstId) ? row.fileFlowInstId : flowInstId };
|
||||
showFlowComment.value = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* 打开查看对话框
|
||||
* @param row - 当前行数据
|
||||
*/
|
||||
const handleView = (row: any) => {
|
||||
formDialogRef.value?.openDialog('view', row);
|
||||
};
|
||||
|
||||
/**
|
||||
* 打开编辑对话框
|
||||
* @param row - 当前行数据
|
||||
*/
|
||||
const handleEdit = (row: any) => {
|
||||
formDialogRef.value?.openDialog('edit', row);
|
||||
};
|
||||
|
||||
/**
|
||||
* 履约验收
|
||||
* @param row - 当前行数据
|
||||
*/
|
||||
const handleAccept = (row: any) => {
|
||||
acceptModalRef.value?.open(row);
|
||||
};
|
||||
|
||||
/** 打开实施采购(仅暂存状态可点;通过 iframe 嵌入 implement.vue) */
|
||||
const handleImplement = (row: any) => {
|
||||
implementFormRef.value?.openDialog(row);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -381,6 +494,142 @@ const handleDelete = async (row: any) => {
|
||||
}
|
||||
};
|
||||
|
||||
/** 暂存状态下提交采购申请(启动流程) */
|
||||
const handleSubmit = async (row: any) => {
|
||||
try {
|
||||
await useMessageBox().confirm('确定要提交该采购申请并启动流程吗?');
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await submitObj({ id: row.id });
|
||||
useMessage().success('提交成功');
|
||||
getDataList();
|
||||
} catch (err: any) {
|
||||
useMessage().error(err?.msg || '提交失败');
|
||||
}
|
||||
};
|
||||
|
||||
/** 操作栏「更多」菜单项配置 */
|
||||
const getActionMenuItems = (row: any) => {
|
||||
const isTemp = row?.status === '-1';
|
||||
return [
|
||||
{
|
||||
command: 'edit',
|
||||
label: '编辑',
|
||||
icon: Edit,
|
||||
visible: () => isTemp,
|
||||
},
|
||||
{
|
||||
command: 'submit',
|
||||
label: '提交',
|
||||
icon: Upload,
|
||||
visible: () => isTemp,
|
||||
},
|
||||
{
|
||||
command: 'delete',
|
||||
label: '删除',
|
||||
icon: Delete,
|
||||
visible: () => isTemp,
|
||||
},
|
||||
{
|
||||
command: 'accept',
|
||||
label: '履约验收',
|
||||
icon: DocumentChecked,
|
||||
visible: () => true,
|
||||
},
|
||||
{
|
||||
command: 'implement',
|
||||
label: '实施采购',
|
||||
icon: Upload
|
||||
},
|
||||
{
|
||||
command: 'archive',
|
||||
label: '文件归档',
|
||||
icon: FolderOpened,
|
||||
visible: () => true,
|
||||
},
|
||||
{
|
||||
command: 'downloadApply',
|
||||
label: '下载审批表',
|
||||
icon: Download,
|
||||
visible: () => true,
|
||||
},
|
||||
{
|
||||
command: 'downloadFileApply',
|
||||
label: '下载文件审批表',
|
||||
icon: Download,
|
||||
visible: () => true,
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
/** 处理更多操作下拉菜单命令 */
|
||||
const handleMoreCommand = (command: string, row: any) => {
|
||||
switch (command) {
|
||||
case 'edit':
|
||||
handleEdit(row);
|
||||
break;
|
||||
case 'submit':
|
||||
handleSubmit(row);
|
||||
break;
|
||||
case 'delete':
|
||||
handleDelete(row);
|
||||
break;
|
||||
case 'accept':
|
||||
handleAccept(row);
|
||||
break;
|
||||
case 'implement':
|
||||
handleImplement(row);
|
||||
break;
|
||||
case 'archive':
|
||||
handleArchive(row);
|
||||
break;
|
||||
case 'downloadApply':
|
||||
handleDownloadApply(row);
|
||||
break;
|
||||
case 'downloadFileApply':
|
||||
handleDownloadFileApply(row);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
/** 下载审批表 */
|
||||
const handleDownloadApply = (row: any) => {
|
||||
const id = row?.id ?? row?.purchaseId;
|
||||
if (id == null || id === '') {
|
||||
useMessage().warning('无法获取申请单ID');
|
||||
return;
|
||||
}
|
||||
const url = getApplyTemplateDownloadUrl(id);
|
||||
const fileName = `审批表_${row?.purchaseNo || id}.docx`;
|
||||
other.downBlobFile(url, {}, fileName);
|
||||
};
|
||||
|
||||
/** 下载文件审批表 */
|
||||
const handleDownloadFileApply = (row: any) => {
|
||||
const id = row?.id ?? row?.purchaseId;
|
||||
if (id == null || id === '') {
|
||||
useMessage().warning('无法获取申请单ID');
|
||||
return;
|
||||
}
|
||||
const url = getFileApplyTemplateDownloadUrl(id);
|
||||
const fileName = `文件审批表_${row?.purchaseNo || id}.docx`;
|
||||
other.downBlobFile(url, {}, fileName);
|
||||
};
|
||||
|
||||
/** 文件归档:按文件类型打包下载该申请单下所有附件 */
|
||||
const handleArchive = (row: any) => {
|
||||
const id = row?.id ?? row?.purchaseId;
|
||||
if (id == null || id === '') {
|
||||
useMessage().warning('无法获取申请单ID');
|
||||
return;
|
||||
}
|
||||
const url = getArchiveDownloadUrl(id);
|
||||
const fileName = `归档_${row?.purchaseNo || id}.zip`;
|
||||
other.downBlobFile(url, {}, fileName);
|
||||
};
|
||||
|
||||
// 获取字典数据和品目树数据
|
||||
const loadDictData = async () => {
|
||||
try {
|
||||
@@ -526,53 +775,19 @@ onMounted(() => {
|
||||
loadDictData();
|
||||
});
|
||||
|
||||
// 组件卸载时清理事件监听器
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('message', handleIframeMessage)
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '/@/assets/styles/modern-page.scss';
|
||||
|
||||
.iframe-dialog-content {
|
||||
width: 100%;
|
||||
height: 70vh;
|
||||
min-height: 500px;
|
||||
max-height: calc(100vh - 200px);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
.add-iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 500px;
|
||||
border: none;
|
||||
display: block;
|
||||
}
|
||||
.op-cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
:deep(.iframe-dialog) {
|
||||
.el-dialog {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-height: 90vh;
|
||||
margin-top: 5vh !important;
|
||||
}
|
||||
|
||||
.el-dialog__header {
|
||||
flex-shrink: 0;
|
||||
padding: 20px 20px 10px;
|
||||
}
|
||||
|
||||
.el-dialog__body {
|
||||
padding: 20px;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
max-height: calc(100vh - 200px);
|
||||
}
|
||||
.status-tag-clickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -161,14 +161,14 @@
|
||||
</el-timeline-item>
|
||||
</el-timeline>
|
||||
</div>
|
||||
<footer class="el-dialog__footer" style="text-align: center;">
|
||||
<span class="dialog-footer">
|
||||
<el-button type="primary" @click="printForm">{{
|
||||
t('jfI18n.print')
|
||||
}}
|
||||
</el-button>
|
||||
</span>
|
||||
</footer>
|
||||
<!-- <footer class="el-dialog__footer" style="text-align: center;">-->
|
||||
<!-- <span class="dialog-footer">-->
|
||||
<!-- <el-button type="primary" @click="printForm">{{-->
|
||||
<!-- t('jfI18n.print')-->
|
||||
<!-- }}-->
|
||||
<!-- </el-button>-->
|
||||
<!-- </span>-->
|
||||
<!-- </footer>-->
|
||||
|
||||
<el-drawer
|
||||
v-if="data.showHiJob"
|
||||
|
||||
121
src/views/professional/common/import-teacher-other-info.vue
Normal file
121
src/views/professional/common/import-teacher-other-info.vue
Normal file
@@ -0,0 +1,121 @@
|
||||
<template>
|
||||
<el-dialog v-model="visible" :title="title" width="600" append-to-body>
|
||||
<!-- <el-alert-->
|
||||
<!-- type="warning"-->
|
||||
<!-- :closable="false"-->
|
||||
<!-- show-icon-->
|
||||
<!-- style="margin-bottom: 20px;">-->
|
||||
<!-- <template #title>-->
|
||||
<!-- <span>下载模板</span>-->
|
||||
<!-- </template>-->
|
||||
<!-- </el-alert>-->
|
||||
|
||||
<div style="text-align: center; margin-bottom: 20px">
|
||||
<el-button type="success" :icon="Download" @click="handleDownloadTemplate">下载模板</el-button>
|
||||
</div>
|
||||
|
||||
<el-upload
|
||||
ref="uploadRef"
|
||||
class="upload-demo"
|
||||
:action="uploadUrl"
|
||||
:headers="headers"
|
||||
:accept="'.xls,.xlsx'"
|
||||
:on-success="handleUploadSuccess"
|
||||
:on-error="handleAvatarError"
|
||||
:limit="1"
|
||||
drag
|
||||
>
|
||||
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
|
||||
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip">只能上传 .xls 或 .xlsx 格式的 Excel 文件</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, nextTick } from 'vue';
|
||||
import { ElNotification } from 'element-plus';
|
||||
import { Download, UploadFilled } from '@element-plus/icons-vue';
|
||||
import { Session } from '/@/utils/storage';
|
||||
import { downBlobFile } from '/@/utils/other';
|
||||
|
||||
const title = ref('');
|
||||
// 响应式数据
|
||||
const visible = ref(false);
|
||||
|
||||
// 计算属性
|
||||
const headers = computed(() => {
|
||||
return {
|
||||
Authorization: 'Bearer ' + Session.getToken(),
|
||||
TENANT_ID: Session.getTenant()
|
||||
};
|
||||
});
|
||||
|
||||
const uploadUrl = ref('')
|
||||
const currentType = ref('')
|
||||
const uploadRef = ref<{ clearFiles?: () => void }>()
|
||||
const titleMap: Record<string, string> = {
|
||||
titleRelation: '职称信息导入',
|
||||
quaRelation: '职业资格信息导入',
|
||||
cerRelation: '教师资格证信息导入',
|
||||
eduDegree: '学历学位信息导入',
|
||||
partyChange: '党组织变动信息导入',
|
||||
honor: '综合表彰信息导入'
|
||||
}
|
||||
// 方法
|
||||
const init = (type: any) => {
|
||||
currentType.value = type
|
||||
uploadUrl.value = '/professional/file/importTeacherOtherInfo?type=' + type
|
||||
title.value = titleMap[type] || '信息导入'
|
||||
visible.value = true
|
||||
nextTick(() => {
|
||||
uploadRef.value?.clearFiles()
|
||||
})
|
||||
}
|
||||
|
||||
// Emits
|
||||
const emit = defineEmits<{
|
||||
(e: 'refreshData'): void
|
||||
}>()
|
||||
|
||||
const handleUploadSuccess = () => {
|
||||
visible.value = false;
|
||||
ElNotification({
|
||||
title: '成功',
|
||||
message: '导入成功',
|
||||
type: 'success',
|
||||
});
|
||||
|
||||
emit('refreshData')
|
||||
};
|
||||
|
||||
const handleAvatarError = (err: any) => {
|
||||
const result = JSON.parse(err.message);
|
||||
if (result.code == '1') {
|
||||
ElNotification.error({
|
||||
title: '错误',
|
||||
message: result.msg,
|
||||
duration: 30000,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleDownloadTemplate = () => {
|
||||
downBlobFile('/professional/file/exportTeacherInfoTemplate', { type: currentType.value || 'titleRelation' }, title.value+'模板.xlsx')
|
||||
}
|
||||
|
||||
// 暴露方法给父组件
|
||||
defineExpose({
|
||||
init,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.upload-demo {
|
||||
:deep(.el-upload-dragger) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -37,6 +37,20 @@
|
||||
</template>
|
||||
</search-form>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<el-row>
|
||||
<div class="mb15" style="width: 100%;">
|
||||
<el-button
|
||||
v-auth="'professional_teacherinfo_import'"
|
||||
type="primary"
|
||||
plain
|
||||
icon="UploadFilled"
|
||||
:loading="exportLoading"
|
||||
@click="handleImportDialog"
|
||||
>导入信息</el-button>
|
||||
</div>
|
||||
</el-row>
|
||||
|
||||
<!-- 表格 -->
|
||||
<el-table
|
||||
ref="tableRef"
|
||||
@@ -77,6 +91,9 @@
|
||||
@current-change="currentChangeHandle"
|
||||
@size-change="sizeChangeHandle"
|
||||
/>
|
||||
|
||||
<!-- 子组件 -->
|
||||
<import-teacher-other-info ref="importTeacherOtherInfoRef" @refreshData="handleFilter" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -88,6 +105,7 @@ import { BasicTableProps, useTable } from '/@/hooks/table'
|
||||
import { fetchList } from '/@/api/professional/professionaluser/professionalpartychange'
|
||||
|
||||
const TeacherNameNo = defineAsyncComponent(() => import('/@/components/TeacherNameNo/index.vue'))
|
||||
const ImportTeacherOtherInfo = defineAsyncComponent(() => import('/@/views/professional/common/import-teacher-other-info.vue'))
|
||||
|
||||
// 表格引用
|
||||
const tableRef = ref()
|
||||
@@ -100,6 +118,10 @@ const search = reactive({
|
||||
realName: ''
|
||||
})
|
||||
|
||||
// 导入加载状态
|
||||
const exportLoading = ref(false)
|
||||
const importTeacherOtherInfoRef = ref()
|
||||
|
||||
// 配置 useTable
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
pageList: async (params: any) => {
|
||||
@@ -130,6 +152,11 @@ const resetQuery = () => {
|
||||
getDataList()
|
||||
}
|
||||
|
||||
// 打开导入弹窗
|
||||
const handleImportDialog = () => {
|
||||
importTeacherOtherInfoRef.value?.init('partyChange')
|
||||
}
|
||||
|
||||
// 表格数据由 useTable(createdIsNeed 默认 true)在挂载时自动请求
|
||||
</script>
|
||||
|
||||
|
||||
@@ -73,6 +73,14 @@
|
||||
@click="handleDownLoadWord"
|
||||
:loading="exportLoading"
|
||||
>导出信息</el-button>
|
||||
<el-button
|
||||
v-auth="'professional_teacherinfo_import'"
|
||||
type="primary"
|
||||
plain
|
||||
icon="UploadFilled"
|
||||
:loading="exportLoading"
|
||||
@click="handleImportDialog"
|
||||
>导入信息</el-button>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<RightToolbar
|
||||
@@ -207,6 +215,7 @@
|
||||
<!-- 子组件 -->
|
||||
<DataForm ref="dataFormRef" @refreshData="handleFilter" />
|
||||
<ProfessionalBackResaon ref="backReasonRef" @refreshData="handleFilter" />
|
||||
<import-teacher-other-info ref="importTeacherOtherInfoRef" @refreshData="handleFilter" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -220,11 +229,10 @@ import { useDict } from '/@/hooks/dict'
|
||||
import {
|
||||
fetchList,
|
||||
examObj,
|
||||
delObj,
|
||||
exportExcel
|
||||
} from '/@/api/professional/professionaluser/professionalqualificationrelation'
|
||||
delObj} from '/@/api/professional/professionaluser/professionalqualificationrelation'
|
||||
import { getLevelList } from '/@/api/professional/rsbase/professionalqualificationconfig'
|
||||
import { getWorkTypeList } from '/@/api/professional/rsbase/professionalworktype'
|
||||
import { makeExportTeacherInfoByTypeTask } from '/@/api/professional/professionalfile';
|
||||
import { PROFESSIONAL_AUDIT_STATE_OPTIONS } from '/@/config/global'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
import { Medal } from '@element-plus/icons-vue'
|
||||
@@ -234,6 +242,7 @@ const AuditState = defineAsyncComponent(() => import('/@/components/AuditState/i
|
||||
const DataForm = defineAsyncComponent(() => import('./form.vue'))
|
||||
const ProfessionalBackResaon = defineAsyncComponent(() => import('/@/views/professional/common/professional-back-resaon.vue'))
|
||||
const previewFile = defineAsyncComponent(() => import('/@/components/tools/preview-file.vue'))
|
||||
const ImportTeacherOtherInfo = defineAsyncComponent(() => import('/@/views/professional/common/import-teacher-other-info.vue'))
|
||||
|
||||
// 审核状态选项
|
||||
const auditStateOptions = PROFESSIONAL_AUDIT_STATE_OPTIONS
|
||||
@@ -265,8 +274,9 @@ const search = reactive({
|
||||
// 材料预览
|
||||
const imgUrl = ref<Array<{ title: string; url: string }>>([])
|
||||
|
||||
// 导出加载状态
|
||||
// 导出/导入加载状态
|
||||
const exportLoading = ref(false)
|
||||
const importTeacherOtherInfoRef = ref()
|
||||
|
||||
// 资格等级和工种列表
|
||||
const qualificationLevelList = ref<any[]>([])
|
||||
@@ -380,27 +390,15 @@ const handleDel = (row: any) => {
|
||||
|
||||
// 导出
|
||||
const handleDownLoadWord = async () => {
|
||||
exportLoading.value = true
|
||||
try {
|
||||
const response: any = await exportExcel(search)
|
||||
|
||||
const blob = new Blob([response as BlobPart])
|
||||
const fileName = '职业资格信息.xls'
|
||||
const elink = document.createElement('a')
|
||||
elink.download = fileName
|
||||
elink.style.display = 'none'
|
||||
elink.href = URL.createObjectURL(blob)
|
||||
document.body.appendChild(elink)
|
||||
elink.click()
|
||||
URL.revokeObjectURL(elink.href)
|
||||
document.body.removeChild(elink)
|
||||
message.success('导出成功')
|
||||
} catch (error) {
|
||||
message.error('导出失败')
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
exportLoading.value = true;
|
||||
let params = Object.assign(search, { type: 'P20003' });
|
||||
makeExportTeacherInfoByTypeTask(params).then((res: any) => {
|
||||
message.success('后台下载进行中,请稍后查看任务列表');
|
||||
});
|
||||
setTimeout(() => {
|
||||
exportLoading.value = false;
|
||||
}, 3000); // 5分钟后自动关闭
|
||||
};
|
||||
|
||||
// 获取资格等级名称
|
||||
const getQualificationLevelName = (id: string | number) => {
|
||||
@@ -414,6 +412,11 @@ const getWorkTypeName = (id: string | number) => {
|
||||
return item ? item.workName : '-'
|
||||
}
|
||||
|
||||
// 打开导入弹窗
|
||||
const handleImportDialog = () => {
|
||||
importTeacherOtherInfoRef.value?.init('quaRelation')
|
||||
}
|
||||
|
||||
// 加载字典数据
|
||||
const loadDictData = async () => {
|
||||
try {
|
||||
|
||||
@@ -84,10 +84,10 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="证书编码" prop="certificateNumber">
|
||||
<el-form-item label="证书编号" prop="certificateNumber">
|
||||
<el-input
|
||||
v-model="dataForm.certificateNumber"
|
||||
placeholder="请输入证书编码(仅支持英文和数字)"
|
||||
placeholder="请输入证书编号(仅支持英文和数字)"
|
||||
clearable
|
||||
show-word-limit
|
||||
maxlength="100"
|
||||
@@ -215,8 +215,8 @@ const formRules = computed(() => {
|
||||
{ required: true, message: '请输入所学专业', trigger: 'blur' }
|
||||
],
|
||||
certificateNumber: [
|
||||
{ required: true, message: '请输入证书编码', trigger: 'blur' },
|
||||
{ pattern: /^[A-Za-z0-9]+$/, message: '证书编码只能包含英文和数字', trigger: 'blur' }
|
||||
{ required: true, message: '请输入证书编号', trigger: 'blur' },
|
||||
{ pattern: /^[A-Za-z0-9]+$/, message: '证书编号只能包含英文和数字', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@@ -69,6 +69,14 @@
|
||||
@click="handleDownLoadWord"
|
||||
:loading="exportLoading">导出信息
|
||||
</el-button>
|
||||
<el-button
|
||||
v-auth="'professional_teacherinfo_import'"
|
||||
type="primary"
|
||||
plain
|
||||
icon="UploadFilled"
|
||||
:loading="exportLoading"
|
||||
@click="handleImportDialog">导入信息
|
||||
</el-button>
|
||||
</div>
|
||||
</el-row>
|
||||
|
||||
@@ -218,6 +226,7 @@
|
||||
<!-- 子组件 -->
|
||||
<DataForm ref="dataFormRef" @refreshData="handleFilter" />
|
||||
<ProfessionalBackResaon ref="backReasonRef" @refreshData="handleFilter" />
|
||||
<import-teacher-other-info ref="importTeacherOtherInfoRef" @refreshData="handleFilter" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -232,19 +241,19 @@ import { useDict } from '/@/hooks/dict'
|
||||
import {
|
||||
fetchList,
|
||||
examObj,
|
||||
delObj,
|
||||
exportExcel
|
||||
} from '/@/api/professional/professionaluser/professionalteacheracademicrelation'
|
||||
delObj} from '/@/api/professional/professionaluser/professionalteacheracademicrelation'
|
||||
import { getDegreeList } from '/@/api/professional/rsbase/professionalacademicdegreeconfig'
|
||||
import { getQualificationList } from '/@/api/professional/rsbase/academicqualificationsconfig'
|
||||
import { getAllTypeList } from '/@/api/professional/rsbase/professionalacademiceducationtypeconfig'
|
||||
import { PROFESSIONAL_AUDIT_STATE_OPTIONS } from '/@/config/global'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
import {makeExportTeacherInfoByTypeTask} from "/@/api/professional/professionalfile";
|
||||
const TeacherNameNo = defineAsyncComponent(() => import('/@/components/TeacherNameNo/index.vue'))
|
||||
const AuditState = defineAsyncComponent(() => import('/@/components/AuditState/index.vue'))
|
||||
const DataForm = defineAsyncComponent(() => import('./form.vue'))
|
||||
const ProfessionalBackResaon = defineAsyncComponent(() => import('/@/views/professional/common/professional-back-resaon.vue'))
|
||||
const previewFile = defineAsyncComponent(() => import('/@/components/tools/preview-file.vue'))
|
||||
const ImportTeacherOtherInfo = defineAsyncComponent(() => import('/@/views/professional/common/import-teacher-other-info.vue'))
|
||||
|
||||
// 审核状态选项
|
||||
const auditStateOptions = PROFESSIONAL_AUDIT_STATE_OPTIONS
|
||||
@@ -279,6 +288,7 @@ const imgUrl = ref<Array<{ title: string; url: string }>>([])
|
||||
|
||||
// 导出加载状态
|
||||
const exportLoading = ref(false)
|
||||
const importTeacherOtherInfoRef = ref()
|
||||
|
||||
// 学位、学历和教育类型列表
|
||||
const degreeList = ref<any[]>([])
|
||||
@@ -401,26 +411,14 @@ const handleDel = (row: any) => {
|
||||
|
||||
// 导出
|
||||
const handleDownLoadWord = async () => {
|
||||
exportLoading.value = true
|
||||
try {
|
||||
const response: any = await exportExcel(search)
|
||||
|
||||
const blob = new Blob([response as BlobPart])
|
||||
const fileName = '学历学位信息.xls'
|
||||
const elink = document.createElement('a')
|
||||
elink.download = fileName
|
||||
elink.style.display = 'none'
|
||||
elink.href = URL.createObjectURL(blob)
|
||||
document.body.appendChild(elink)
|
||||
elink.click()
|
||||
URL.revokeObjectURL(elink.href)
|
||||
document.body.removeChild(elink)
|
||||
message.success('导出成功')
|
||||
} catch (error) {
|
||||
message.error('导出失败')
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
exportLoading.value = true;
|
||||
let params = Object.assign(search, { type: 'P20005' });
|
||||
makeExportTeacherInfoByTypeTask(params).then((res: any) => {
|
||||
message.success('后台下载进行中,请稍后查看任务列表');
|
||||
});
|
||||
setTimeout(() => {
|
||||
exportLoading.value = false;
|
||||
}, 3000); // 5分钟后自动关闭
|
||||
}
|
||||
|
||||
// 获取学位名称
|
||||
@@ -442,6 +440,11 @@ const getEducationTypeName = (id: string | number | undefined) => {
|
||||
return item ? item.name : '-'
|
||||
}
|
||||
|
||||
// 打开导入弹窗
|
||||
const handleImportDialog = () => {
|
||||
importTeacherOtherInfoRef.value?.init('eduDegree')
|
||||
}
|
||||
|
||||
// 加载字典数据
|
||||
const loadDictData = async () => {
|
||||
try {
|
||||
|
||||
@@ -69,6 +69,14 @@
|
||||
@click="handleDownLoadWord"
|
||||
:loading="exportLoading">导出信息
|
||||
</el-button>
|
||||
<el-button
|
||||
v-auth="'professional_teacherinfo_import'"
|
||||
type="primary"
|
||||
plain
|
||||
icon="UploadFilled"
|
||||
:loading="exportLoading"
|
||||
@click="handleImportDialog">导入信息
|
||||
</el-button>
|
||||
</div>
|
||||
</el-row>
|
||||
|
||||
@@ -188,6 +196,7 @@
|
||||
<!-- 子组件 -->
|
||||
<DataForm ref="dataFormRef" @refreshData="handleFilter" />
|
||||
<ProfessionalBackResaon ref="backReasonRef" @refreshData="handleFilter" />
|
||||
<import-teacher-other-info ref="importTeacherOtherInfoRef" @refreshData="handleFilter" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -203,9 +212,10 @@ import { getTeacherCertificateList } from '/@/api/professional/rsbase/profession
|
||||
import {
|
||||
fetchList,
|
||||
examObj,
|
||||
delObj,
|
||||
exportExcel
|
||||
delObj
|
||||
} from '/@/api/professional/professionaluser/professionalteachercertificaterelation'
|
||||
import { makeExportTeacherInfoByTypeTask } from '/@/api/professional/professionalfile';
|
||||
|
||||
import { PROFESSIONAL_AUDIT_STATE_OPTIONS } from '/@/config/global'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
const TeacherNameNo = defineAsyncComponent(() => import('/@/components/TeacherNameNo/index.vue'))
|
||||
@@ -213,6 +223,7 @@ const AuditState = defineAsyncComponent(() => import('/@/components/AuditState/i
|
||||
const DataForm = defineAsyncComponent(() => import('./form.vue'))
|
||||
const ProfessionalBackResaon = defineAsyncComponent(() => import('/@/views/professional/common/professional-back-resaon.vue'))
|
||||
const previewFile = defineAsyncComponent(() => import('/@/components/tools/preview-file.vue'))
|
||||
const ImportTeacherOtherInfo = defineAsyncComponent(() => import('/@/views/professional/common/import-teacher-other-info.vue'))
|
||||
|
||||
// 审核状态选项(独立定义,防止其他页面修改时被波及)
|
||||
import type { StateOption } from '/@/components/AuditState/index.vue'
|
||||
@@ -247,6 +258,7 @@ const imgUrl = ref<Array<{ title: string; url: string }>>([])
|
||||
|
||||
// 导出加载状态
|
||||
const exportLoading = ref(false)
|
||||
const importTeacherOtherInfoRef = ref()
|
||||
|
||||
// 证书列表
|
||||
const certificateList = ref<any[]>([])
|
||||
@@ -357,27 +369,15 @@ const handleDel = (row: any) => {
|
||||
|
||||
// 导出
|
||||
const handleDownLoadWord = async () => {
|
||||
exportLoading.value = true
|
||||
try {
|
||||
const response: any = await exportExcel(search)
|
||||
|
||||
const blob = new Blob([response as BlobPart])
|
||||
const fileName = '教师资格证信息.xls'
|
||||
const elink = document.createElement('a')
|
||||
elink.download = fileName
|
||||
elink.style.display = 'none'
|
||||
elink.href = URL.createObjectURL(blob)
|
||||
document.body.appendChild(elink)
|
||||
elink.click()
|
||||
URL.revokeObjectURL(elink.href)
|
||||
document.body.removeChild(elink)
|
||||
message.success('导出成功')
|
||||
} catch (error) {
|
||||
message.error('导出失败')
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
exportLoading.value = true;
|
||||
let params = Object.assign(search, { type: 'P20004' });
|
||||
makeExportTeacherInfoByTypeTask(params).then((res: any) => {
|
||||
message.success('后台下载进行中,请稍后查看任务列表');
|
||||
});
|
||||
setTimeout(() => {
|
||||
exportLoading.value = false;
|
||||
}, 3000); // 5分钟后自动关闭
|
||||
};
|
||||
|
||||
// 获取证书名称
|
||||
const getCertificateName = (id: string | number) => {
|
||||
@@ -385,6 +385,11 @@ const getCertificateName = (id: string | number) => {
|
||||
return item ? item.cretificateName : '-'
|
||||
}
|
||||
|
||||
// 打开导入弹窗
|
||||
const handleImportDialog = () => {
|
||||
importTeacherOtherInfoRef.value?.init('cerRelation')
|
||||
}
|
||||
|
||||
// 加载字典数据
|
||||
const loadDictData = async () => {
|
||||
try {
|
||||
|
||||
@@ -68,6 +68,14 @@
|
||||
@click="handleDownLoadWord"
|
||||
:loading="exportLoading">导出信息
|
||||
</el-button>
|
||||
<el-button
|
||||
v-auth="'professional_teacherinfo_import'"
|
||||
type="primary"
|
||||
plain
|
||||
icon="UploadFilled"
|
||||
:loading="exportLoading"
|
||||
@click="handleImportDialog">导入信息
|
||||
</el-button>
|
||||
</div>
|
||||
</el-row>
|
||||
|
||||
@@ -216,6 +224,7 @@
|
||||
<!-- 子组件 -->
|
||||
<ProfessionalBackResaon ref="backReasonRef" @refreshData="handleFilter" />
|
||||
<DataForm ref="dataFormRef" @refreshData="handleFilter" />
|
||||
<import-teacher-other-info ref="importTeacherOtherInfoRef" @refreshData="handleFilter" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -234,6 +243,7 @@ import {
|
||||
} from '/@/api/professional/professionaluser/professionalteacherhonor'
|
||||
import { PROFESSIONAL_AUDIT_STATE_OPTIONS, getStatusConfig } from '/@/config/global'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
import {makeExportTeacherInfoByTypeTask} from "/@/api/professional/professionalfile";
|
||||
const TeacherNameNo = defineAsyncComponent(() => import('/@/components/TeacherNameNo/index.vue'))
|
||||
const AuditState = defineAsyncComponent(() => import('/@/components/AuditState/index.vue'))
|
||||
const ClickableTag = defineAsyncComponent(() => import('/@/components/ClickableTag/index.vue'))
|
||||
@@ -241,6 +251,7 @@ const DetailPopover = defineAsyncComponent(() => import('/@/components/DetailPop
|
||||
const ProfessionalBackResaon = defineAsyncComponent(() => import('/@/views/professional/common/professional-back-resaon.vue'))
|
||||
const DataForm = defineAsyncComponent(() => import('./form.vue'))
|
||||
const previewFile = defineAsyncComponent(() => import('/@/components/tools/preview-file.vue'))
|
||||
const ImportTeacherOtherInfo = defineAsyncComponent(() => import('/@/views/professional/common/import-teacher-other-info.vue'))
|
||||
|
||||
// 消息提示 hooks
|
||||
const message = useMessage()
|
||||
@@ -281,6 +292,7 @@ const imgUrl = ref<Array<{ title: string; url: string }>>([])
|
||||
|
||||
// 导出加载状态
|
||||
const exportLoading = ref(false)
|
||||
const importTeacherOtherInfoRef = ref()
|
||||
|
||||
// 配置 useTable
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
@@ -382,29 +394,19 @@ const handleDel = (row: any) => {
|
||||
|
||||
// 导出
|
||||
const handleDownLoadWord = async () => {
|
||||
exportLoading.value = true
|
||||
try {
|
||||
const response = await fetchList({
|
||||
current: 1,
|
||||
size: 999999,
|
||||
...search
|
||||
})
|
||||
const data = response.data.records || []
|
||||
|
||||
const tHeader = ['工号', '姓名', '荣誉', '表彰单位', '年份']
|
||||
const filterVal = ['teacherNo', 'teacherName', 'honor', 'honorCompany', 'year']
|
||||
|
||||
// 动态导入导出工具
|
||||
const { export_json_to_excel } = await import('/@/excel/Export2Excel.js')
|
||||
const exportData = data.map((v: any) => filterVal.map((j: string) => v[j]))
|
||||
export_json_to_excel(tHeader, exportData, '综合表彰.xls')
|
||||
|
||||
message.success('导出成功')
|
||||
} catch (error) {
|
||||
message.error('导出失败')
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
exportLoading.value = true;
|
||||
let params = Object.assign(search, { type: 'P20006' });
|
||||
makeExportTeacherInfoByTypeTask(params).then((res: any) => {
|
||||
message.success('后台下载进行中,请稍后查看任务列表');
|
||||
});
|
||||
setTimeout(() => {
|
||||
exportLoading.value = false;
|
||||
}, 3000); // 5分钟后自动关闭
|
||||
}
|
||||
|
||||
// 打开导入弹窗
|
||||
const handleImportDialog = () => {
|
||||
importTeacherOtherInfoRef.value?.init('honor')
|
||||
}
|
||||
|
||||
// 表格数据由 useTable(createdIsNeed 默认 true)在挂载时自动请求
|
||||
|
||||
@@ -6,23 +6,25 @@
|
||||
show-icon
|
||||
style="margin-bottom: 20px;">
|
||||
<template #title>
|
||||
<span>导入前请先下载字典文件,部分字段需严格按照字典值填写</span>
|
||||
<span> 可先导出教职工信息,按照导出信息的模板填入职工数据,再执行导入</span>
|
||||
</template>
|
||||
</el-alert>
|
||||
|
||||
<div style="text-align: center; margin-bottom: 20px;">
|
||||
<a href="excel/dictlist.xlsx" rel="external nofollow" download="职工信息字典下载" style="text-decoration: none;">
|
||||
<el-button type="success" :icon="Download">下载字典文件</el-button>
|
||||
</a>
|
||||
</div>
|
||||
<!-- <div style="text-align: center; margin-bottom: 20px;">-->
|
||||
<!-- <a href="excel/dictlist.xlsx" rel="external nofollow" download="职工信息字典下载" style="text-decoration: none;">-->
|
||||
<!-- <el-button type="success" :icon="Download">下载字典文件</el-button>-->
|
||||
<!-- </a>-->
|
||||
<!-- </div>-->
|
||||
|
||||
<el-upload
|
||||
ref="uploadRef"
|
||||
class="upload-demo"
|
||||
action="/professional/file/makeImportTeacherInfoSimpleTask"
|
||||
:headers="headers"
|
||||
:accept="'.xls,.xlsx'"
|
||||
:on-success="handleUploadSuccess"
|
||||
:on-error="handleAvatarError"
|
||||
:limit="1"
|
||||
drag>
|
||||
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
|
||||
<div class="el-upload__text">
|
||||
@@ -38,7 +40,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { ref, computed, nextTick } from 'vue'
|
||||
import { ElNotification } from 'element-plus'
|
||||
import { Download, UploadFilled } from '@element-plus/icons-vue'
|
||||
import { Session } from '/@/utils/storage'
|
||||
@@ -53,9 +55,14 @@
|
||||
}
|
||||
})
|
||||
|
||||
const uploadRef = ref<{ clearFiles?: () => void }>()
|
||||
|
||||
// 方法
|
||||
const init = () => {
|
||||
visible.value = true
|
||||
nextTick(() => {
|
||||
uploadRef.value?.clearFiles?.()
|
||||
})
|
||||
}
|
||||
|
||||
const handleUploadSuccess = () => {
|
||||
|
||||
@@ -1024,7 +1024,7 @@
|
||||
</el-table-column>
|
||||
<el-table-column prop="graduateSchool" label="毕业学校" min-width="180" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="major" label="所学专业" min-width="150" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="certificateNumber" label="证书编码" min-width="120" align="center" />
|
||||
<el-table-column prop="certificateNumber" label="证书编号" min-width="120" align="center" />
|
||||
<el-table-column label="学历证书" min-width="150" align="center">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
|
||||
@@ -1,225 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-form
|
||||
ref="dataFormRef"
|
||||
:model="form"
|
||||
:rules="dataRules"
|
||||
label-width="120px"
|
||||
v-loading="loading"
|
||||
:disabled="operType === 'view'"
|
||||
>
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="12" class="mb20" v-if="!hiddenFields.projectName">
|
||||
<el-form-item label="采购项目名称" prop="projectName">
|
||||
<el-input v-model="form.projectName" placeholder="请输入采购项目名称" :disabled="disabledFields.projectName" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20" v-if="!hiddenFields.projectType">
|
||||
<el-form-item label="项目类别" prop="projectType">
|
||||
<el-select v-model="form.projectType" placeholder="请选择" :disabled="disabledFields.projectType" clearable style="width: 100%">
|
||||
<el-option label="货物" value="A" />
|
||||
<el-option label="工程" value="B" />
|
||||
<el-option label="服务" value="C" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" class="mb20" v-if="!hiddenFields.projectContent">
|
||||
<el-form-item label="采购内容" prop="projectContent">
|
||||
<el-input v-model="form.projectContent" type="textarea" :rows="3" placeholder="请输入采购内容" :disabled="disabledFields.projectContent" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20" v-if="!hiddenFields.applyDate">
|
||||
<el-form-item label="填报日期" prop="applyDate">
|
||||
<el-date-picker
|
||||
v-model="form.applyDate"
|
||||
type="date"
|
||||
value-format="YYYY-MM-DD"
|
||||
placeholder="选择日期"
|
||||
:disabled="disabledFields.applyDate"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20" v-if="!hiddenFields.fundSource">
|
||||
<el-form-item label="资金来源" prop="fundSource">
|
||||
<el-input v-model="form.fundSource" placeholder="资金来源" :disabled="disabledFields.fundSource" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20" v-if="!hiddenFields.budget">
|
||||
<el-form-item label="预算金额(元)" prop="budget">
|
||||
<el-input-number v-model="form.budget" :min="0" :precision="2" :disabled="disabledFields.budget" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20" v-if="!hiddenFields.isCentralized">
|
||||
<el-form-item label="是否集采" prop="isCentralized">
|
||||
<el-select v-model="form.isCentralized" placeholder="请选择" :disabled="disabledFields.isCentralized" clearable style="width: 100%">
|
||||
<el-option label="否" value="0" />
|
||||
<el-option label="是" value="1" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20" v-if="!hiddenFields.isSpecial">
|
||||
<el-form-item label="是否特殊情况" prop="isSpecial">
|
||||
<el-select v-model="form.isSpecial" placeholder="请选择" :disabled="disabledFields.isSpecial" clearable style="width: 100%">
|
||||
<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-col>
|
||||
<el-col :span="12" class="mb20" v-if="!hiddenFields.purchaseMode">
|
||||
<el-form-item label="采购形式" prop="purchaseMode">
|
||||
<el-input v-model="form.purchaseMode" placeholder="采购形式" :disabled="disabledFields.purchaseMode" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20" v-if="!hiddenFields.purchaseType">
|
||||
<el-form-item label="采购方式" prop="purchaseType">
|
||||
<el-input v-model="form.purchaseType" placeholder="采购方式" :disabled="disabledFields.purchaseType" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" class="mb20" v-if="!hiddenFields.remark">
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="form.remark" type="textarea" :rows="2" placeholder="备注" :disabled="disabledFields.remark" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template v-if="data.submitBtn">
|
||||
<footer class="el-dialog__footer">
|
||||
<span class="dialog-footer">
|
||||
<el-button type="primary" @click="submitForm" :disabled="loading">提交</el-button>
|
||||
</span>
|
||||
</footer>
|
||||
</template>
|
||||
<template v-else>
|
||||
<footer class="el-dialog__footer">
|
||||
<span class="dialog-footer" />
|
||||
</footer>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="PurchaseApplyFlow">
|
||||
import { useMessage } from '/@/hooks/message'
|
||||
import * as purchaseApply from '/@/api/order/purchase-apply'
|
||||
import * as orderVue from '/@/api/order/order-key-vue'
|
||||
import { handleCustomFormPerm, handleFormPrint } from '/@/flow/utils/form-perm'
|
||||
import { deepClone } from '/@/utils/other'
|
||||
import { initCustomFormMethods } from '/@/views/order/index'
|
||||
|
||||
const emits = defineEmits(['handleJob'])
|
||||
|
||||
const dataFormRef = ref()
|
||||
const loading = ref(false)
|
||||
const operType = ref<'view' | 'flow'>('flow')
|
||||
|
||||
const props = defineProps({
|
||||
currJob: { type: Object, default: null },
|
||||
currElTab: { type: Object, default: () => ({}) }
|
||||
})
|
||||
|
||||
const form = reactive<Record<string, any>>({
|
||||
id: null,
|
||||
code: '',
|
||||
flowKey: 'PURCHASE_APPLY',
|
||||
flowInstId: null,
|
||||
purchaseNo: '',
|
||||
projectName: '',
|
||||
projectType: '',
|
||||
projectContent: '',
|
||||
applyDate: '',
|
||||
fundSource: '',
|
||||
budget: null,
|
||||
isCentralized: '',
|
||||
isSpecial: '',
|
||||
purchaseMode: '',
|
||||
purchaseSchool: '',
|
||||
purchaseType: '',
|
||||
categoryCode: '',
|
||||
fileIds: [],
|
||||
remark: '',
|
||||
runJobId: '',
|
||||
flowVarUser: null
|
||||
})
|
||||
|
||||
const dataRules = ref({
|
||||
projectContent: [{ required: true, message: '请输入采购内容', trigger: 'blur' }],
|
||||
budget: [{ required: true, message: '请输入预算金额', trigger: 'blur' }]
|
||||
})
|
||||
|
||||
const fieldsPerm = {
|
||||
projectName: false,
|
||||
projectType: false,
|
||||
projectContent: false,
|
||||
applyDate: false,
|
||||
fundSource: false,
|
||||
budget: false,
|
||||
isCentralized: false,
|
||||
isSpecial: false,
|
||||
purchaseMode: false,
|
||||
purchaseType: false,
|
||||
remark: false
|
||||
}
|
||||
const hiddenFields = reactive({ ...fieldsPerm })
|
||||
const disabledFields = reactive(deepClone(fieldsPerm))
|
||||
|
||||
const data = reactive({
|
||||
submitBtn: true,
|
||||
elTab: null as any
|
||||
})
|
||||
|
||||
const methods = initCustomFormMethods(data, disabledFields, operType)
|
||||
|
||||
function initJobData() {
|
||||
if (props.currJob?.orderId) handleGetObj(props.currJob.orderId)
|
||||
}
|
||||
|
||||
function handleGetObj(id: string | number) {
|
||||
purchaseApply.getObj(id).then(async (resp: any) => {
|
||||
const formData = resp?.data ?? {}
|
||||
Object.assign(form, formData)
|
||||
form.runJobId = props.currJob?.id ?? ''
|
||||
await initFormPermPrint()
|
||||
})
|
||||
}
|
||||
|
||||
async function initFormPermPrint() {
|
||||
const elTab = orderVue.currElTabIsExist(props.currJob, props.currElTab?.id)
|
||||
const res = await handleCustomFormPerm(props, hiddenFields, disabledFields, elTab)
|
||||
await orderVue.currElTabIsView(methods, props.currJob, props.currElTab?.id, submitForm, res?.callback)
|
||||
await handleFormPrint(form, elTab?.type, elTab?.id, '1')
|
||||
data.elTab = elTab
|
||||
}
|
||||
|
||||
async function submitForm() {
|
||||
try {
|
||||
loading.value = true
|
||||
await purchaseApply.putObj(form)
|
||||
orderVue.currElTabIsSave(props.currJob, props.currElTab?.id, true, emits)
|
||||
useMessage().success(form.id ? '修改成功' : '保存成功')
|
||||
} catch (err: any) {
|
||||
useMessage().error(err?.msg ?? '操作失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.currJob?.id,
|
||||
() => { initJobData() }
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
initJobData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.el-dialog__footer {
|
||||
text-align: center;
|
||||
.dialog-footer {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -11,6 +11,26 @@
|
||||
:rules="dataRules"
|
||||
label-width="120px"
|
||||
v-loading="loading">
|
||||
<!-- 新增时先选取用户,选后自动带出姓名、工号 -->
|
||||
<el-form-item label="选取用户" prop="userId">
|
||||
<org-selector
|
||||
v-model:orgList="userList"
|
||||
type="user"
|
||||
:multiple="false"
|
||||
@update:orgList="handleUserChange" />
|
||||
</el-form-item>
|
||||
<el-form-item label="姓名" prop="name">
|
||||
<el-input
|
||||
v-model="form.name"
|
||||
placeholder="选择用户后自动填充"
|
||||
readonly />
|
||||
</el-form-item>
|
||||
<el-form-item label="用户工号" prop="username">
|
||||
<el-input
|
||||
v-model="form.username"
|
||||
placeholder="选择用户后自动填充"
|
||||
readonly />
|
||||
</el-form-item>
|
||||
<el-form-item label="部门" prop="deptId">
|
||||
<org-selector
|
||||
v-model:orgList="deptList"
|
||||
@@ -21,26 +41,7 @@
|
||||
<el-form-item label="部门名称" prop="deptName">
|
||||
<el-input
|
||||
v-model="form.deptName"
|
||||
placeholder="请选择部门后自动填充"
|
||||
readonly />
|
||||
</el-form-item>
|
||||
<el-form-item label="分管负责人" prop="userId">
|
||||
<org-selector
|
||||
v-model:orgList="userList"
|
||||
type="user"
|
||||
:multiple="false"
|
||||
@update:orgList="handleUserChange" />
|
||||
</el-form-item>
|
||||
<el-form-item label="姓名" prop="name">
|
||||
<el-input
|
||||
v-model="form.name"
|
||||
placeholder="请选择用户后自动填充"
|
||||
readonly />
|
||||
</el-form-item>
|
||||
<el-form-item label="用户工号" prop="username">
|
||||
<el-input
|
||||
v-model="form.username"
|
||||
placeholder="请选择用户后自动填充"
|
||||
placeholder="选择部门后自动填充"
|
||||
readonly />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
@@ -74,7 +75,7 @@ const emit = defineEmits(['refresh']);
|
||||
const dataFormRef = ref();
|
||||
const deptList = ref<any[]>([]);
|
||||
const userList = ref<any[]>([]);
|
||||
const dataForm = reactive({
|
||||
const form = reactive({
|
||||
id: '',
|
||||
deptId: '',
|
||||
deptName: '',
|
||||
@@ -87,17 +88,17 @@ const visible = ref(false);
|
||||
const loading = ref(false);
|
||||
|
||||
const dataRules = ref({
|
||||
userId: [
|
||||
{ required: true, message: '请选取用户(分管负责人)', trigger: 'change' }
|
||||
],
|
||||
deptId: [
|
||||
{ required: true, message: '请选择部门', trigger: 'change' }
|
||||
],
|
||||
userId: [
|
||||
{ required: true, message: '请选择分管负责人', trigger: 'change' }
|
||||
],
|
||||
name: [
|
||||
{ required: true, message: '姓名不能为空', trigger: 'blur' }
|
||||
{ required: true, message: '请先选取用户', trigger: 'blur' }
|
||||
],
|
||||
username: [
|
||||
{ required: true, message: '用户工号不能为空', trigger: 'blur' }
|
||||
{ required: true, message: '请先选取用户', trigger: 'blur' }
|
||||
],
|
||||
});
|
||||
|
||||
@@ -105,38 +106,38 @@ const dataRules = ref({
|
||||
const handleDeptChange = (list: any[]) => {
|
||||
if (list && list.length > 0) {
|
||||
const dept = list[0];
|
||||
dataForm.deptId = dept.deptId || dept.id || '';
|
||||
dataForm.deptName = dept.name || dept.deptName || '';
|
||||
form.deptId = dept.deptId || dept.id || '';
|
||||
form.deptName = dept.name || dept.deptName || '';
|
||||
} else {
|
||||
dataForm.deptId = '';
|
||||
dataForm.deptName = '';
|
||||
form.deptId = '';
|
||||
form.deptName = '';
|
||||
}
|
||||
};
|
||||
|
||||
// 处理用户选择变化
|
||||
// 处理用户选择变化(选取用户后自动带出姓名、工号)
|
||||
const handleUserChange = (list: any[]) => {
|
||||
if (list && list.length > 0) {
|
||||
const user = list[0];
|
||||
dataForm.userId = user.userId || user.id || '';
|
||||
dataForm.username = user.username || user.userName || '';
|
||||
dataForm.name = user.name || user.realName || '';
|
||||
form.userId = user.userId || user.id || '';
|
||||
form.username = user.username || user.userName || '';
|
||||
form.name = user.name || user.realName || '';
|
||||
} else {
|
||||
dataForm.userId = '';
|
||||
dataForm.username = '';
|
||||
dataForm.name = '';
|
||||
form.userId = '';
|
||||
form.username = '';
|
||||
form.name = '';
|
||||
}
|
||||
};
|
||||
|
||||
// 打开弹窗
|
||||
const openDialog = async (id?: string) => {
|
||||
visible.value = true;
|
||||
dataForm.id = '';
|
||||
dataForm.deptId = '';
|
||||
dataForm.deptName = '';
|
||||
dataForm.userId = '';
|
||||
dataForm.username = '';
|
||||
dataForm.name = '';
|
||||
dataForm.remark = '';
|
||||
form.id = '';
|
||||
form.deptId = '';
|
||||
form.deptName = '';
|
||||
form.userId = '';
|
||||
form.username = '';
|
||||
form.name = '';
|
||||
form.remark = '';
|
||||
deptList.value = [];
|
||||
userList.value = [];
|
||||
|
||||
@@ -148,7 +149,7 @@ const openDialog = async (id?: string) => {
|
||||
getObj({ id }).then((res: any) => {
|
||||
if (res.data && res.data.length > 0) {
|
||||
const data = res.data[0];
|
||||
Object.assign(dataForm, {
|
||||
Object.assign(form, {
|
||||
id: data.id || '',
|
||||
deptId: data.deptId || '',
|
||||
deptName: data.deptName || '',
|
||||
@@ -201,17 +202,17 @@ const onSubmit = async () => {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (dataForm.id) {
|
||||
await putObj(dataForm);
|
||||
if (form.id) {
|
||||
await putObj(form);
|
||||
useMessage().success('编辑成功');
|
||||
} else {
|
||||
await addObj(dataForm);
|
||||
await addObj(form);
|
||||
useMessage().success('新增成功');
|
||||
}
|
||||
visible.value = false;
|
||||
emit('refresh');
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg || (dataForm.id ? '编辑失败' : '新增失败'));
|
||||
useMessage().error(err.msg || (form.id ? '编辑失败' : '新增失败'));
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
<div class="card-header">
|
||||
<span class="card-title">
|
||||
<el-icon class="title-icon"><Document /></el-icon>
|
||||
业务分管管理
|
||||
业务分管部门及人员
|
||||
</span>
|
||||
<div class="header-actions">
|
||||
<el-button
|
||||
@@ -56,15 +56,15 @@
|
||||
v-auth="'purchase_purchasingBusinessDept_add'">
|
||||
新增
|
||||
</el-button>
|
||||
<el-button
|
||||
plain
|
||||
icon="UploadFilled"
|
||||
type="primary"
|
||||
class="ml10"
|
||||
@click="excelUploadRef.show()"
|
||||
v-auth="'purchase_purchasingBusinessDept_add'">
|
||||
导入
|
||||
</el-button>
|
||||
<!-- <el-button -->
|
||||
<!-- plain -->
|
||||
<!-- icon="UploadFilled" -->
|
||||
<!-- type="primary" -->
|
||||
<!-- class="ml10" -->
|
||||
<!-- @click="excelUploadRef.show()" -->
|
||||
<!-- v-auth="'purchase_purchasingBusinessDept_add'">-->
|
||||
<!-- 导入-->
|
||||
<!-- </el-button>-->
|
||||
<el-button
|
||||
plain
|
||||
:disabled="multiple"
|
||||
@@ -134,14 +134,14 @@
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" fixed="right" width="150">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
icon="Edit"
|
||||
link
|
||||
type="primary"
|
||||
v-auth="'purchase_purchasingBusinessDept_edit'"
|
||||
@click="formDialogRef.openDialog(scope.row.id)">
|
||||
编辑
|
||||
</el-button>
|
||||
<!-- <el-button -->
|
||||
<!-- icon="Edit" -->
|
||||
<!-- link -->
|
||||
<!-- type="primary" -->
|
||||
<!-- v-auth="'purchase_purchasingBusinessDept_edit'"-->
|
||||
<!-- @click="formDialogRef.openDialog(scope.row.id)">-->
|
||||
<!-- 编辑-->
|
||||
<!-- </el-button>-->
|
||||
<el-button
|
||||
icon="Delete"
|
||||
link
|
||||
|
||||
@@ -93,13 +93,13 @@
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" fixed="right" width="150">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
icon="Edit"
|
||||
link
|
||||
type="primary"
|
||||
@click="formDialogRef.openDialog('edit', scope.row)">
|
||||
编辑
|
||||
</el-button>
|
||||
<!-- <el-button -->
|
||||
<!-- icon="Edit" -->
|
||||
<!-- link -->
|
||||
<!-- type="primary" -->
|
||||
<!-- @click="formDialogRef.openDialog('edit', scope.row)">-->
|
||||
<!-- 编辑-->
|
||||
<!-- </el-button>-->
|
||||
<el-button
|
||||
icon="Delete"
|
||||
link
|
||||
|
||||
120
src/views/recruit/common/import-recruit-info.vue
Normal file
120
src/views/recruit/common/import-recruit-info.vue
Normal file
@@ -0,0 +1,120 @@
|
||||
<template>
|
||||
<el-dialog v-model="visible" :title="title" width="600" append-to-body>
|
||||
<div style="text-align: center; margin-bottom: 20px" v-if="currentType!='R10003'">
|
||||
<el-button type="success" :icon="Download" @click="handleDownloadTemplate">下载模板</el-button>
|
||||
</div>
|
||||
|
||||
<el-alert
|
||||
v-if="currentType=='R10003'"
|
||||
type="warning"
|
||||
:closable="false"
|
||||
show-icon
|
||||
style="margin-bottom: 20px;">
|
||||
<template #title>
|
||||
<span> 请从中招平台导出数据后导入</span>
|
||||
</template>
|
||||
</el-alert>
|
||||
|
||||
|
||||
<el-upload
|
||||
ref="uploadRef"
|
||||
class="upload-demo"
|
||||
:action="uploadUrl"
|
||||
:headers="headers"
|
||||
:accept="'.xls,.xlsx'"
|
||||
:on-success="handleUploadSuccess"
|
||||
:on-error="handleAvatarError"
|
||||
:limit="1"
|
||||
drag
|
||||
>
|
||||
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
|
||||
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip">只能上传 .xls 或 .xlsx 格式的 Excel 文件</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, nextTick } from 'vue';
|
||||
import { ElNotification } from 'element-plus';
|
||||
import { Download, UploadFilled } from '@element-plus/icons-vue';
|
||||
import { Session } from '/@/utils/storage';
|
||||
import { downBlobFile } from '/@/utils/other';
|
||||
|
||||
const title = ref('');
|
||||
// 响应式数据
|
||||
const visible = ref(false);
|
||||
|
||||
// 计算属性
|
||||
const headers = computed(() => {
|
||||
return {
|
||||
Authorization: 'Bearer ' + Session.getToken(),
|
||||
TENANT_ID: Session.getTenant()
|
||||
};
|
||||
});
|
||||
|
||||
const uploadUrl = ref('')
|
||||
const currentType = ref('')
|
||||
const uploadRef = ref<{ clearFiles?: () => void }>()
|
||||
const titleMap: Record<string, string> = {
|
||||
R10001: '计划专业导入',
|
||||
R10002: '地区分数导入',
|
||||
R10003: '中招平台数据导入'
|
||||
}
|
||||
// 方法
|
||||
const init = (type: any) => {
|
||||
currentType.value = type
|
||||
uploadUrl.value = '/api/recruit/file/importRecruitInfo?type=' + type
|
||||
title.value = titleMap[type] || '信息导入'
|
||||
visible.value = true
|
||||
nextTick(() => {
|
||||
uploadRef.value?.clearFiles()
|
||||
})
|
||||
}
|
||||
|
||||
// Emits
|
||||
const emit = defineEmits<{
|
||||
(e: 'refreshDataList'): void
|
||||
}>()
|
||||
|
||||
const handleUploadSuccess = () => {
|
||||
visible.value = false;
|
||||
ElNotification({
|
||||
title: '成功',
|
||||
message: '导入成功',
|
||||
type: 'success',
|
||||
});
|
||||
|
||||
emit('refreshDataList')
|
||||
};
|
||||
|
||||
const handleAvatarError = (err: any) => {
|
||||
const result = JSON.parse(err.message);
|
||||
if (result.code == '1') {
|
||||
ElNotification.error({
|
||||
title: '错误',
|
||||
message: result.msg,
|
||||
duration: 30000,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleDownloadTemplate = () => {
|
||||
downBlobFile('/recruit/file/exportRecruitTemplate', { type: currentType.value || 'planMajor' }, title.value+'模板.xlsx')
|
||||
}
|
||||
|
||||
// 暴露方法给父组件
|
||||
defineExpose({
|
||||
init
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.upload-demo {
|
||||
:deep(.el-upload-dragger) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -16,303 +16,279 @@
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="layout-padding">
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<!-- 搜索表单 -->
|
||||
<el-form :model="queryForm" inline ref="searchFormRef">
|
||||
<el-form-item label="招生计划" prop="groupId">
|
||||
<el-select v-model="queryForm.groupId" filterable clearable placeholder="请选择招生计划">
|
||||
<el-option
|
||||
v-for="item in planList"
|
||||
:key="item.id"
|
||||
:label="item.groupName"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="学院" prop="deptCode">
|
||||
<el-select v-model="queryForm.deptCode" filterable clearable placeholder="请选择">
|
||||
<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="专业序号" prop="majorCode">
|
||||
<el-input v-model="queryForm.majorCode" placeholder="专业序号" />
|
||||
</el-form-item>
|
||||
<el-form-item label="专业名称" prop="majorName">
|
||||
<el-input v-model="queryForm.majorName" placeholder="专业名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="学制" prop="learnYear">
|
||||
<el-input v-model="queryForm.learnYear" placeholder="学制" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="getDataList">查询</el-button>
|
||||
<el-button plain icon="Refresh" class="ml10" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="layout-padding">
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<!-- 搜索表单 -->
|
||||
<el-form :model="queryForm" inline ref="searchFormRef">
|
||||
<el-form-item label="招生计划" prop="groupId">
|
||||
<el-select v-model="queryForm.groupId" filterable clearable placeholder="请选择招生计划">
|
||||
<el-option v-for="item in planList" :key="item.id" :label="item.groupName" :value="item.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="学院" prop="deptCode">
|
||||
<el-select v-model="queryForm.deptCode" filterable clearable placeholder="请选择">
|
||||
<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="专业序号" prop="majorCode">
|
||||
<el-input v-model="queryForm.majorCode" placeholder="专业序号" />
|
||||
</el-form-item>
|
||||
<el-form-item label="专业名称" prop="majorName">
|
||||
<el-input v-model="queryForm.majorName" placeholder="专业名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="学制" prop="learnYear">
|
||||
<el-input v-model="queryForm.learnYear" placeholder="学制" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="getDataList">查询</el-button>
|
||||
<el-button plain icon="Refresh" class="ml10" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div class="mb15">
|
||||
<el-button
|
||||
v-if="hasAuth('recruit_recruitplanmajor_add')"
|
||||
type="primary"
|
||||
icon="FolderAdd"
|
||||
@click="addOrUpdateHandle"
|
||||
>
|
||||
新 增
|
||||
</el-button>
|
||||
</div>
|
||||
<!-- 操作按钮 -->
|
||||
<div class="mb15">
|
||||
<el-button v-if="hasAuth('recruit_recruitplanmajor_add')" type="primary" icon="FolderAdd" @click="addOrUpdateHandle"> 新 增 </el-button>
|
||||
|
||||
<!-- 表格 -->
|
||||
<el-table
|
||||
ref="tableRef"
|
||||
:data="state.dataList"
|
||||
v-loading="state.loading"
|
||||
border
|
||||
stripe
|
||||
row-key="id"
|
||||
:cell-style="tableStyle.cellStyle"
|
||||
:header-cell-style="tableStyle.headerCellStyle"
|
||||
>
|
||||
<el-table-column type="index" label="序号" width="60" align="center" />
|
||||
<el-table-column prop="groupId" label="招生计划名称" align="center" min-width="140" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
{{ getPlanName(scope.row.groupId) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="majorCode" label="专业序号" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="majorName" label="专业名称" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="deptCode" label="学院" align="center" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
{{ getDeptName(scope.row.deptCode) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="learnYear" label="学制" align="center" width="80" show-overflow-tooltip />
|
||||
<el-table-column prop="majorLevel" label="层次" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="isOrder" label="订单班|中德班|联院班" align="center" width="180" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
{{ getYesNoLabel(scope.row.isOrder) }}|{{ getYesNoLabel(scope.row.isZd) }}|{{ getYesNoLabel(scope.row.isUnion) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="sm" label="色盲不可录" align="center" width="120">
|
||||
<template #default="scope">
|
||||
<el-switch
|
||||
v-model="scope.row.sm"
|
||||
active-text="是"
|
||||
inactive-text="否"
|
||||
active-value="1"
|
||||
inactive-value="0"
|
||||
@change="changeSm(scope.row)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="stuworkMajorCode" label="正式专业代码" align="center" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
{{ getMajorCodeName(scope.row.stuworkMajorCode) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!-- <el-table-column prop="cityPlanId" label="市平台招生计划" align="center" show-overflow-tooltip>
|
||||
<el-button
|
||||
v-auth="'recruit_major_import'"
|
||||
type="primary"
|
||||
plain
|
||||
icon="UploadFilled"
|
||||
:loading="exportLoading"
|
||||
@click="handleImportDialog"
|
||||
>导入信息
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 表格 -->
|
||||
<el-table
|
||||
ref="tableRef"
|
||||
:data="state.dataList"
|
||||
v-loading="state.loading"
|
||||
border
|
||||
stripe
|
||||
row-key="id"
|
||||
:cell-style="tableStyle.cellStyle"
|
||||
:header-cell-style="tableStyle.headerCellStyle"
|
||||
>
|
||||
<el-table-column type="index" label="序号" width="60" align="center" />
|
||||
<el-table-column prop="groupId" label="招生计划名称" align="center" min-width="140" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
{{ getPlanName(scope.row.groupId) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="majorCode" label="专业序号" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="majorName" label="专业名称" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="deptCode" label="学院" align="center" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
{{ getDeptName(scope.row.deptCode) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="learnYear" label="学制" align="center" width="80" show-overflow-tooltip />
|
||||
<el-table-column prop="majorLevel" label="层次" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="isOrder" label="订单班|中德班|联院班" align="center" width="180" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
{{ getYesNoLabel(scope.row.isOrder) }}|{{ getYesNoLabel(scope.row.isZd) }}|{{ getYesNoLabel(scope.row.isUnion) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="sm" label="色盲不可录" align="center" width="120">
|
||||
<template #default="scope">
|
||||
<el-switch v-model="scope.row.sm" active-text="是" inactive-text="否" active-value="1" inactive-value="0" @change="changeSm(scope.row)" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="stuworkMajorCode" label="正式专业代码" align="center" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
{{ getMajorCodeName(scope.row.stuworkMajorCode) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!-- <el-table-column prop="cityPlanId" label="市平台招生计划" align="center" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
{{ scope.row.cityPlanName != undefined ? (scope.row.cityPlanName + '|' + scope.row.cityPlanYear) : '' }}
|
||||
</template>
|
||||
</el-table-column> -->
|
||||
<!-- <el-table-column prop="sort" label="排序" align="center" show-overflow-tooltip /> -->
|
||||
<el-table-column label="操作" width="150" align="center" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
icon="EditPen"
|
||||
@click="addOrUpdateHandle(scope.row)"
|
||||
>
|
||||
修改
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="hasAuth('recruit_recruitplanmajor_del')"
|
||||
type="danger"
|
||||
link
|
||||
icon="Delete"
|
||||
@click="deleteHandle(scope.row.id)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- <el-table-column prop="sort" label="排序" align="center" show-overflow-tooltip /> -->
|
||||
<el-table-column label="操作" width="150" align="center" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button type="primary" link icon="EditPen" @click="addOrUpdateHandle(scope.row)"> 修改 </el-button>
|
||||
<el-button v-if="hasAuth('recruit_recruitplanmajor_del')" type="danger" link icon="Delete" @click="deleteHandle(scope.row.id)">
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<pagination
|
||||
v-bind="state.pagination"
|
||||
@current-change="currentChangeHandle"
|
||||
@size-change="sizeChangeHandle"
|
||||
/>
|
||||
<!-- 分页 -->
|
||||
<pagination v-bind="state.pagination" @current-change="currentChangeHandle" @size-change="sizeChangeHandle" />
|
||||
|
||||
<!-- 弹窗, 新增 / 修改 -->
|
||||
<table-form ref="addOrUpdateRef" @refreshDataList="getDataList" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- 弹窗, 新增 / 修改 -->
|
||||
<table-form ref="addOrUpdateRef" @refreshDataList="getDataList" />
|
||||
|
||||
<import-recruit-info ref="ImportRecruitInfoRef" @refreshDataList="getDataList"></import-recruit-info>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="recruitplanmajor">
|
||||
import { ref, reactive, onMounted, nextTick, defineAsyncComponent } from 'vue'
|
||||
import { useAuth } from '/@/hooks/auth'
|
||||
import { BasicTableProps, useTable } from '/@/hooks/table'
|
||||
import { useMessage, useMessageBox } from '/@/hooks/message'
|
||||
import { useDict } from '/@/hooks/dict'
|
||||
import { getList } from '/@/api/recruit/recruitstudentplangroup'
|
||||
import { fetchList, delObj, editQuickField } from '/@/api/recruit/recruitstudentplan'
|
||||
import { getDeptList } from '/@/api/basic/basicclass'
|
||||
import { getMajorNameList } from '/@/api/basic/major'
|
||||
import { ref, reactive, onMounted, nextTick, defineAsyncComponent } from 'vue';
|
||||
import { useAuth } from '/@/hooks/auth';
|
||||
import { BasicTableProps, useTable } from '/@/hooks/table';
|
||||
import { useMessage, useMessageBox } from '/@/hooks/message';
|
||||
import { useDict } from '/@/hooks/dict';
|
||||
import { getList } from '/@/api/recruit/recruitstudentplangroup';
|
||||
import { fetchList, delObj, editQuickField } from '/@/api/recruit/recruitstudentplan';
|
||||
import { getDeptList } from '/@/api/basic/basicclass';
|
||||
import { getMajorNameList } from '/@/api/basic/major';
|
||||
|
||||
const TableForm = defineAsyncComponent(() => import('./detaiform.vue'))
|
||||
const { hasAuth } = useAuth()
|
||||
const TableForm = defineAsyncComponent(() => import('./detaiform.vue'));
|
||||
const ImportRecruitInfo = defineAsyncComponent(() => import('/@/views/recruit/common/import-recruit-info.vue'));
|
||||
const ImportRecruitInfoRef=ref<any>();
|
||||
const { hasAuth } = useAuth();
|
||||
// 消息提示 hooks
|
||||
const message = useMessage()
|
||||
const messageBox = useMessageBox()
|
||||
const message = useMessage();
|
||||
const messageBox = useMessageBox();
|
||||
|
||||
// 表格引用
|
||||
const tableRef = ref()
|
||||
const searchFormRef = ref()
|
||||
const addOrUpdateRef = ref()
|
||||
const tableRef = ref();
|
||||
const searchFormRef = ref();
|
||||
const addOrUpdateRef = ref();
|
||||
|
||||
// 字典数据
|
||||
const { yes_no_type } = useDict('yes_no_type')
|
||||
const { yes_no_type } = useDict('yes_no_type');
|
||||
|
||||
// 数据
|
||||
const planList = ref<any[]>([])
|
||||
const deptList = ref<any[]>([])
|
||||
const offcialZydmList = ref<any[]>([])
|
||||
const planList = ref<any[]>([]);
|
||||
const deptList = ref<any[]>([]);
|
||||
const offcialZydmList = ref<any[]>([]);
|
||||
|
||||
// 查询表单
|
||||
const queryForm = reactive({
|
||||
groupId: '',
|
||||
deptCode: '',
|
||||
majorCode: '',
|
||||
majorName: '',
|
||||
learnYear: ''
|
||||
})
|
||||
groupId: '',
|
||||
deptCode: '',
|
||||
majorCode: '',
|
||||
majorName: '',
|
||||
learnYear: '',
|
||||
});
|
||||
|
||||
// 获取计划名称
|
||||
const getPlanName = (groupId: string) => {
|
||||
const item = planList.value.find(item => item.id === groupId)
|
||||
return item ? item.groupName : ''
|
||||
}
|
||||
const item = planList.value.find((item) => item.id === groupId);
|
||||
return item ? item.groupName : '';
|
||||
};
|
||||
|
||||
// 获取学院名称
|
||||
const getDeptName = (deptCode: string) => {
|
||||
const item = deptList.value.find(item => item.deptCode === deptCode)
|
||||
return item ? item.deptName : ''
|
||||
}
|
||||
const item = deptList.value.find((item) => item.deptCode === deptCode);
|
||||
return item ? item.deptName : '';
|
||||
};
|
||||
|
||||
// 获取是/否标签
|
||||
const getYesNoLabel = (value: string) => {
|
||||
const item = yes_no_type.value.find((item: any) => item.value === value)
|
||||
return item ? item.label : ''
|
||||
}
|
||||
const item = yes_no_type.value.find((item: any) => item.value === value);
|
||||
return item ? item.label : '';
|
||||
};
|
||||
|
||||
// 获取专业代码名称
|
||||
const getMajorCodeName = (majorCode: string) => {
|
||||
const item = offcialZydmList.value.find(item => item.majorCode === majorCode)
|
||||
return item ? item.majorCodeAndName : ''
|
||||
}
|
||||
const item = offcialZydmList.value.find((item) => item.majorCode === majorCode);
|
||||
return item ? item.majorCodeAndName : '';
|
||||
};
|
||||
|
||||
// 表格状态
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
queryForm: queryForm,
|
||||
pageList: async (params: any) => {
|
||||
const response = await fetchList(params)
|
||||
return {
|
||||
data: {
|
||||
records: response.data.records,
|
||||
total: response.data.total
|
||||
}
|
||||
}
|
||||
},
|
||||
createdIsNeed: false
|
||||
})
|
||||
queryForm: queryForm,
|
||||
pageList: async (params: any) => {
|
||||
const response = await fetchList(params);
|
||||
return {
|
||||
data: {
|
||||
records: response.data.records,
|
||||
total: response.data.total,
|
||||
},
|
||||
};
|
||||
},
|
||||
createdIsNeed: false,
|
||||
});
|
||||
|
||||
// 使用 table hook
|
||||
const { getDataList, currentChangeHandle, sizeChangeHandle, tableStyle } = useTable(state)
|
||||
const { getDataList, currentChangeHandle, sizeChangeHandle, tableStyle } = useTable(state);
|
||||
|
||||
// 初始化
|
||||
const init = async () => {
|
||||
try {
|
||||
// 查询二级学院信息
|
||||
const deptData = await getDeptList()
|
||||
deptList.value = deptData.data || []
|
||||
|
||||
// 获取招生计划列表
|
||||
const planData = await getList()
|
||||
planList.value = planData.data || []
|
||||
if (planList.value.length > 0) {
|
||||
queryForm.groupId = planList.value[0].id
|
||||
}
|
||||
|
||||
// 获取专业名称列表
|
||||
const majorData = await getMajorNameList()
|
||||
offcialZydmList.value = majorData.data || []
|
||||
|
||||
getDataList()
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
|
||||
}
|
||||
}
|
||||
try {
|
||||
// 查询二级学院信息
|
||||
const deptData = await getDeptList();
|
||||
deptList.value = deptData.data || [];
|
||||
|
||||
// 获取招生计划列表
|
||||
const planData = await getList();
|
||||
planList.value = planData.data || [];
|
||||
if (planList.value.length > 0) {
|
||||
queryForm.groupId = planList.value[0].id;
|
||||
}
|
||||
|
||||
// 获取专业名称列表
|
||||
const majorData = await getMajorNameList();
|
||||
offcialZydmList.value = majorData.data || [];
|
||||
|
||||
getDataList();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
// 修改开关
|
||||
const changeSm = async (row: any) => {
|
||||
try {
|
||||
let parmas={id:row.id,sm:row.sm}
|
||||
await editQuickField(parmas)
|
||||
message.success('修改成功')
|
||||
} catch (error: any) {
|
||||
console.log(error)
|
||||
|
||||
}
|
||||
}
|
||||
try {
|
||||
let parmas = { id: row.id, sm: row.sm };
|
||||
await editQuickField(parmas);
|
||||
message.success('修改成功');
|
||||
} catch (error: any) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
// 新增 / 修改
|
||||
const addOrUpdateHandle = (row?: any) => {
|
||||
nextTick(() => {
|
||||
addOrUpdateRef.value?.init(row.id || null)
|
||||
})
|
||||
}
|
||||
nextTick(() => {
|
||||
addOrUpdateRef.value?.init(row.id || null);
|
||||
});
|
||||
};
|
||||
|
||||
// 删除
|
||||
const deleteHandle = async (id: string) => {
|
||||
try {
|
||||
await messageBox.confirm('是否确认删除本条数据?请谨慎操作')
|
||||
await delObj(id)
|
||||
message.success('删除成功')
|
||||
getDataList()
|
||||
} catch {
|
||||
// 用户取消
|
||||
}
|
||||
}
|
||||
try {
|
||||
await messageBox.confirm('是否确认删除本条数据?请谨慎操作');
|
||||
await delObj(id);
|
||||
message.success('删除成功');
|
||||
getDataList();
|
||||
} catch {
|
||||
// 用户取消
|
||||
}
|
||||
};
|
||||
|
||||
// 重置查询
|
||||
const resetQuery = () => {
|
||||
searchFormRef.value?.resetFields()
|
||||
queryForm.groupId = ''
|
||||
queryForm.deptCode = ''
|
||||
queryForm.majorCode = ''
|
||||
queryForm.majorName = ''
|
||||
queryForm.learnYear = ''
|
||||
if (planList.value.length > 0) {
|
||||
queryForm.groupId = planList.value[0].id
|
||||
}
|
||||
getDataList()
|
||||
}
|
||||
searchFormRef.value?.resetFields();
|
||||
queryForm.groupId = '';
|
||||
queryForm.deptCode = '';
|
||||
queryForm.majorCode = '';
|
||||
queryForm.majorName = '';
|
||||
queryForm.learnYear = '';
|
||||
if (planList.value.length > 0) {
|
||||
queryForm.groupId = planList.value[0].id;
|
||||
}
|
||||
getDataList();
|
||||
};
|
||||
|
||||
const exportLoading = ref(false);
|
||||
|
||||
const handleImportDialog = () => {
|
||||
ImportRecruitInfoRef.value?.init("R10001");
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
init()
|
||||
})
|
||||
init();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
<style lang="scss" scoped></style>
|
||||
|
||||
@@ -29,6 +29,15 @@
|
||||
>
|
||||
新 增
|
||||
</el-button>
|
||||
<el-button
|
||||
v-auth="'recruit_areascore_import'"
|
||||
type="primary"
|
||||
plain
|
||||
icon="UploadFilled"
|
||||
:loading="exportLoading"
|
||||
@click="handleImportDialog"
|
||||
>导入信息
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 表格 -->
|
||||
@@ -83,6 +92,9 @@
|
||||
|
||||
<!-- 弹窗, 新增 / 修改 -->
|
||||
<table-form ref="addOrUpdateRef" @refreshDataList="getDataList" />
|
||||
|
||||
<import-recruit-info ref="ImportRecruitInfoRef" @refreshDataList="getDataList"></import-recruit-info>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -94,6 +106,8 @@ import { BasicTableProps, useTable } from '/@/hooks/table'
|
||||
import { useMessage, useMessageBox } from '/@/hooks/message'
|
||||
import { getList } from '/@/api/recruit/recruitstudentplangroup'
|
||||
import { fetchList, delObj } from '/@/api/recruit/recruitstudentplancorrectscoreconfig'
|
||||
const ImportRecruitInfo = defineAsyncComponent(() => import('/@/views/recruit/common/import-recruit-info.vue'));
|
||||
const ImportRecruitInfoRef=ref<any>();
|
||||
|
||||
const TableForm = defineAsyncComponent(() => import('./detaiform.vue'))
|
||||
const { hasAuth } = useAuth()
|
||||
@@ -183,6 +197,12 @@ const resetQuery = () => {
|
||||
getDataList()
|
||||
}
|
||||
|
||||
const exportLoading = ref(false);
|
||||
|
||||
const handleImportDialog = () => {
|
||||
ImportRecruitInfoRef.value?.init("R10002");
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
init()
|
||||
})
|
||||
|
||||
@@ -281,19 +281,28 @@
|
||||
@click="handleAddData">新增
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="hasAuth('zipExport')"
|
||||
type="warning"
|
||||
plain
|
||||
icon="Download"
|
||||
@click="downZip()">招生名单打包导出
|
||||
</el-button>
|
||||
<el-button
|
||||
class="ml10"
|
||||
type="warning"
|
||||
plain
|
||||
icon="Download"
|
||||
@click="handleExport()">名单导出
|
||||
v-auth="'recruit_zzpt_import'"
|
||||
type="primary"
|
||||
plain
|
||||
icon="UploadFilled"
|
||||
:loading="exportLoading"
|
||||
@click="handleImportDialog"
|
||||
>导入中招平台数据
|
||||
</el-button>
|
||||
<!-- <el-button-->
|
||||
<!-- v-if="hasAuth('zipExport')"-->
|
||||
<!-- type="warning"-->
|
||||
<!-- plain-->
|
||||
<!-- icon="Download"-->
|
||||
<!-- @click="downZip()">招生名单打包导出-->
|
||||
<!-- </el-button>-->
|
||||
<!-- <el-button -->
|
||||
<!-- class="ml10"-->
|
||||
<!-- type="warning"-->
|
||||
<!-- plain-->
|
||||
<!-- icon="Download"-->
|
||||
<!-- @click="handleExport()">名单导出-->
|
||||
<!-- </el-button>-->
|
||||
</div>
|
||||
</el-row>
|
||||
|
||||
@@ -634,7 +643,9 @@
|
||||
<AdmissionNoticeDialog ref="admissionNoticeDialogRef" @refresh="getDataList"></AdmissionNoticeDialog>
|
||||
|
||||
<InterviewForm ref="interviewFormRef" @refresh="getDataList"></InterviewForm>
|
||||
</div>
|
||||
|
||||
<import-recruit-info ref="ImportRecruitInfoRef" @refreshDataList="getDataList"></import-recruit-info>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -681,6 +692,8 @@ const InterviewForm = defineAsyncComponent(() => import('/@/views/recruit/recrui
|
||||
const PayQrcodeDialog = defineAsyncComponent(() => import('./PayQrcodeDialog.vue'))
|
||||
const AdmissionNoticeDialog = defineAsyncComponent(() => import('./AdmissionNoticeDialog.vue'))
|
||||
const ActionDropdown = defineAsyncComponent(() => import('/@/components/tools/action-dropdown.vue'))
|
||||
const ImportRecruitInfo = defineAsyncComponent(() => import('/@/views/recruit/common/import-recruit-info.vue'));
|
||||
const ImportRecruitInfoRef=ref<any>();
|
||||
const { hasAuth } = useAuth()
|
||||
// 消息提示 hooks
|
||||
const message = useMessage()
|
||||
@@ -1131,6 +1144,10 @@ watch(() => dataForm.groupId, () => {
|
||||
}
|
||||
})
|
||||
|
||||
const handleImportDialog = () => {
|
||||
ImportRecruitInfoRef.value?.init("R10003");
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
init()
|
||||
})
|
||||
|
||||
@@ -1,28 +1,59 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
title="查看详情"
|
||||
v-model="visible"
|
||||
:close-on-click-modal="false"
|
||||
<el-dialog
|
||||
title="查看详情"
|
||||
v-model="visible"
|
||||
:close-on-click-modal="false"
|
||||
draggable
|
||||
width="800px">
|
||||
<div v-loading="loading" class="detail-container" v-if="detailData">
|
||||
<el-descriptions :column="2" border>
|
||||
width="960px">
|
||||
<div class="detail-container">
|
||||
<!-- 活动主信息(来自列表行) -->
|
||||
<el-descriptions v-if="mainInfo.activityTheme" :column="2" border class="mb16">
|
||||
<el-descriptions-item label="活动主题" :span="2">
|
||||
{{ detailData.activityTheme || '-' }}
|
||||
{{ mainInfo.activityTheme || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="活动说明" :span="2">
|
||||
{{ detailData.remarks || '-' }}
|
||||
{{ mainInfo.remarks || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="活动兼报数">
|
||||
{{ detailData.maxSub !== undefined && detailData.maxSub !== null ? detailData.maxSub : '-' }}
|
||||
{{ mainInfo.maxSub !== undefined && mainInfo.maxSub !== null ? mainInfo.maxSub : '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="开始时间">
|
||||
{{ parseTime(detailData.startTime, '{y}-{m}-{d}') }}
|
||||
{{ mainInfo.startTime ? parseTime(mainInfo.startTime, '{y}-{m}-{d}') : '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="结束时间">
|
||||
{{ parseTime(detailData.endTime, '{y}-{m}-{d}') }}
|
||||
{{ mainInfo.endTime ? parseTime(mainInfo.endTime, '{y}-{m}-{d}') : '-' }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<!-- 子项目列表(接口:getActivityInfoSubList) -->
|
||||
<div class="sub-title">子项目列表</div>
|
||||
<el-table
|
||||
:data="subList"
|
||||
v-loading="loading"
|
||||
border
|
||||
size="small"
|
||||
max-height="400"
|
||||
style="width: 100%">
|
||||
<el-table-column type="index" label="序号" width="60" align="center" />
|
||||
<el-table-column prop="subTitle" label="子项目名称" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column prop="deptName" label="学院" width="110" show-overflow-tooltip />
|
||||
<el-table-column prop="classNo" label="班号" width="80" align="center" />
|
||||
<el-table-column prop="classMasterName" label="班主任" width="90" show-overflow-tooltip />
|
||||
<el-table-column prop="startTime" label="开始时间" width="155" align="center">
|
||||
<template #default="scope">
|
||||
{{ scope.row.startTime ? parseTime(scope.row.startTime, '{y}-{m}-{d} {h}:{i}') : '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="endTime" label="结束时间" width="155" align="center">
|
||||
<template #default="scope">
|
||||
{{ scope.row.endTime ? parseTime(scope.row.endTime, '{y}-{m}-{d} {h}:{i}') : '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="maxNum" label="人数限制" width="88" align="center" />
|
||||
<el-table-column prop="applyNums" label="已报名" width="78" align="center" />
|
||||
<el-table-column prop="position" label="地点" min-width="100" show-overflow-tooltip />
|
||||
<el-table-column prop="projectDescription" label="项目描述" min-width="180" show-overflow-tooltip />
|
||||
</el-table>
|
||||
<el-empty v-if="!loading && subList.length === 0" description="暂无子项目" :image-size="80" />
|
||||
</div>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
@@ -33,47 +64,40 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="ActivityInfoDetailDialog">
|
||||
import { ref } from 'vue'
|
||||
import { getDetail } from '/@/api/stuwork/activityinfo'
|
||||
import { ref, reactive } from 'vue'
|
||||
import { getActivityInfoSubList } from '/@/api/stuwork/activityinfosub'
|
||||
import { parseTime } from '/@/utils/formatTime'
|
||||
import { useMessage } from '/@/hooks/message'
|
||||
|
||||
// 定义变量内容
|
||||
const visible = ref(false)
|
||||
const loading = ref(false)
|
||||
const detailData = ref<any>({})
|
||||
const mainInfo = reactive<Record<string, any>>({})
|
||||
const subList = ref<any[]>([])
|
||||
|
||||
// 打开弹窗
|
||||
const openDialog = async (id: string) => {
|
||||
/**
|
||||
* 打开弹窗:使用接口 getActivityInfoSubList(activityInfoId) 获取详情子项目列表
|
||||
* @param activityInfoId 活动信息ID
|
||||
* @param row 列表行数据,用于展示活动主题等主信息
|
||||
*/
|
||||
const openDialog = async (activityInfoId: string, row?: any) => {
|
||||
visible.value = true
|
||||
loading.value = true
|
||||
detailData.value = {}
|
||||
|
||||
subList.value = []
|
||||
Object.keys(mainInfo).forEach((k) => delete mainInfo[k])
|
||||
if (row && typeof row === 'object') {
|
||||
Object.assign(mainInfo, row)
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await getDetail(id)
|
||||
if (res.data) {
|
||||
// 根据接口文档,返回的数据可能是 { records: [...], total: ... } 格式
|
||||
// 如果是列表格式,取第一条;如果是对象,直接使用
|
||||
if (res.data.records && Array.isArray(res.data.records) && res.data.records.length > 0) {
|
||||
detailData.value = res.data.records[0]
|
||||
} else if (res.data.records && Array.isArray(res.data.records)) {
|
||||
// 列表为空
|
||||
useMessage().warning('未找到详情数据')
|
||||
visible.value = false
|
||||
} else {
|
||||
// 直接是对象
|
||||
detailData.value = res.data
|
||||
}
|
||||
}
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg || '获取详情失败')
|
||||
visible.value = false
|
||||
const res = await getActivityInfoSubList(activityInfoId)
|
||||
const data = res.data
|
||||
subList.value = Array.isArray(data) ? data : []
|
||||
} catch (_err) {
|
||||
subList.value = []
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 暴露方法
|
||||
defineExpose({
|
||||
openDialog
|
||||
})
|
||||
@@ -81,7 +105,15 @@ defineExpose({
|
||||
|
||||
<style scoped lang="scss">
|
||||
.detail-container {
|
||||
padding: 20px 0;
|
||||
padding: 8px 0;
|
||||
}
|
||||
.mb16 {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.sub-title {
|
||||
margin-bottom: 8px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -250,9 +250,9 @@ const {
|
||||
tableStyle: _tableStyle
|
||||
} = useTable(state)
|
||||
|
||||
// 查看详情
|
||||
// 查看详情(接口:getActivityInfoSubList,传入活动ID与行数据用于展示)
|
||||
const handleView = (row: any) => {
|
||||
detailDialogRef.value?.openDialog(row.id)
|
||||
detailDialogRef.value?.openDialog(row.id, row)
|
||||
}
|
||||
|
||||
// 编辑
|
||||
|
||||
@@ -48,6 +48,17 @@
|
||||
style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item label="默认扣分值" prop="score">
|
||||
<el-input-number
|
||||
v-model="form.score"
|
||||
:min="0"
|
||||
:max="999"
|
||||
:precision="0"
|
||||
placeholder="请输入默认扣分值"
|
||||
style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item label="备注" prop="remarks">
|
||||
<el-input
|
||||
@@ -86,12 +97,13 @@ const loading = ref(false)
|
||||
const operType = ref('add')
|
||||
const categoryList = ref<any[]>([])
|
||||
|
||||
// 提交表单数据
|
||||
// 提交表单数据(与接口文档一致:edit 含 score 默认扣分值)
|
||||
const form = reactive({
|
||||
id: '',
|
||||
categortyId: '',
|
||||
pointName: '',
|
||||
standard: '',
|
||||
score: undefined as number | undefined,
|
||||
remarks: ''
|
||||
})
|
||||
|
||||
@@ -117,6 +129,7 @@ const openDialog = async (type: string = 'add', row?: any) => {
|
||||
form.categortyId = ''
|
||||
form.pointName = ''
|
||||
form.standard = ''
|
||||
form.score = undefined
|
||||
form.remarks = ''
|
||||
|
||||
// 编辑时填充数据
|
||||
@@ -125,20 +138,21 @@ const openDialog = async (type: string = 'add', row?: any) => {
|
||||
form.categortyId = row.categortyId || ''
|
||||
form.pointName = row.pointName || ''
|
||||
form.standard = row.standard || ''
|
||||
form.score = row.score !== undefined && row.score !== null ? Number(row.score) : undefined
|
||||
form.remarks = row.remarks || ''
|
||||
|
||||
// 如果需要获取详情
|
||||
if (row.id && (!row.pointName || !row.standard)) {
|
||||
// 如果需要获取详情(含 score)
|
||||
if (row.id && (!row.pointName || row.standard === undefined || row.score === undefined)) {
|
||||
loading.value = true
|
||||
getDetail(row.id).then((res: any) => {
|
||||
if (res.data) {
|
||||
form.categortyId = res.data.categortyId || ''
|
||||
form.pointName = res.data.pointName || ''
|
||||
form.standard = res.data.standard || ''
|
||||
form.score = res.data.score !== undefined && res.data.score !== null ? Number(res.data.score) : undefined
|
||||
form.remarks = res.data.remarks || ''
|
||||
}
|
||||
}).catch((err) => {
|
||||
}).finally(() => {
|
||||
}).catch(() => {}).finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
@@ -155,10 +169,12 @@ const onSubmit = async () => {
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
// 与接口文档一致:categortyId, pointName, standard, score, remarks
|
||||
const submitData = {
|
||||
categortyId: form.categortyId,
|
||||
pointName: form.pointName,
|
||||
standard: form.standard || '',
|
||||
score: form.score !== undefined && form.score !== null ? Number(form.score) : 0,
|
||||
remarks: form.remarks || ''
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,21 @@
|
||||
:inline="true"
|
||||
@keyup.enter="getDataList"
|
||||
class="search-form">
|
||||
<el-form-item label="考核项" prop="categortyId">
|
||||
<el-select
|
||||
v-model="state.queryForm.categortyId"
|
||||
placeholder="请选择考核项"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 200px">
|
||||
<el-option
|
||||
v-for="item in categoryList"
|
||||
:key="item.id"
|
||||
:label="item.category"
|
||||
:value="item.id">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="指标名称" prop="pointName">
|
||||
<el-input
|
||||
v-model="state.queryForm.pointName"
|
||||
@@ -155,9 +170,10 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="AssessmentPoint">
|
||||
import { reactive, ref } from 'vue'
|
||||
import { reactive, ref, onMounted } from 'vue'
|
||||
import { BasicTableProps, useTable } from "/@/hooks/table";
|
||||
import { fetchList, delObj } from "/@/api/stuwork/assessmentpoint";
|
||||
import { getList as getAssessmentCategoryList } from "/@/api/stuwork/assessmentcategory";
|
||||
import { useMessage, useMessageBox } from "/@/hooks/message";
|
||||
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
|
||||
import FormDialog from './form.vue'
|
||||
@@ -169,6 +185,7 @@ const formDialogRef = ref()
|
||||
const searchFormRef = ref()
|
||||
const columnControlRef = ref()
|
||||
const showSearch = ref(true)
|
||||
const categoryList = ref<any[]>([])
|
||||
|
||||
// 表格列配置
|
||||
const tableColumns = [
|
||||
@@ -194,9 +211,10 @@ const tableStyle = {
|
||||
headerCellStyle: { background: '#f5f7fa', color: '#606266', fontWeight: 'bold' }
|
||||
}
|
||||
|
||||
// 配置 useTable
|
||||
// 配置 useTable(分页接口支持 categortyId、pointName,与考核项对应)
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
queryForm: {
|
||||
categortyId: '',
|
||||
pointName: ''
|
||||
},
|
||||
pageList: fetchList,
|
||||
@@ -215,13 +233,28 @@ const {
|
||||
tableStyle: _tableStyle
|
||||
} = useTable(state)
|
||||
|
||||
// 获取考核项列表(与表单考核项对应,接口:assessmentcategory/list)
|
||||
const getCategoryList = async () => {
|
||||
try {
|
||||
const res = await getAssessmentCategoryList()
|
||||
categoryList.value = res.data && Array.isArray(res.data) ? res.data : []
|
||||
} catch (err) {
|
||||
categoryList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
// 重置
|
||||
const handleReset = () => {
|
||||
searchFormRef.value?.resetFields()
|
||||
state.queryForm.categortyId = ''
|
||||
state.queryForm.pointName = ''
|
||||
getDataList()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getCategoryList()
|
||||
})
|
||||
|
||||
// 编辑
|
||||
const handleEdit = (row: any) => {
|
||||
formDialogRef.value?.openDialog('edit', row)
|
||||
|
||||
@@ -69,8 +69,7 @@
|
||||
v-model="form.score"
|
||||
:precision="0"
|
||||
:step="1"
|
||||
:min="0"
|
||||
placeholder="请输入分数"
|
||||
placeholder="请输入分数(可为负数)"
|
||||
style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
@@ -12,6 +12,53 @@
|
||||
label-width="100px"
|
||||
v-loading="loading">
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="学年" prop="schoolYear">
|
||||
<el-select
|
||||
v-model="form.schoolYear"
|
||||
placeholder="请选择学年"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in schoolYearList"
|
||||
:key="item.year"
|
||||
:label="item.year"
|
||||
:value="item.year" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="学期" prop="schoolTerm">
|
||||
<el-select
|
||||
v-model="form.schoolTerm"
|
||||
placeholder="请选择学期"
|
||||
clearable
|
||||
style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in schoolTermList"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="班号" prop="classCode">
|
||||
<el-select
|
||||
v-model="form.classCode"
|
||||
placeholder="请选择班号"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in classList"
|
||||
:key="item.classCode"
|
||||
:label="item.classNo"
|
||||
:value="item.classCode" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item label="标题" prop="title">
|
||||
<el-input
|
||||
@@ -52,9 +99,12 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="ClassPlanFormDialog">
|
||||
import { ref, reactive, nextTick } from 'vue'
|
||||
import { ref, reactive, nextTick, onMounted } from 'vue'
|
||||
import { useMessage } from '/@/hooks/message'
|
||||
import { addObj, editObj, getDetail } from '/@/api/stuwork/classplan'
|
||||
import { queryAllSchoolYear } from '/@/api/basic/basicyear'
|
||||
import { getClassListByRole } from '/@/api/basic/basicclass'
|
||||
import { getDicts } from '/@/api/admin/dict'
|
||||
import Editor from '/@/components/Editor/index.vue'
|
||||
|
||||
const emit = defineEmits(['refresh'])
|
||||
@@ -64,10 +114,16 @@ const dataFormRef = ref()
|
||||
const visible = ref(false)
|
||||
const loading = ref(false)
|
||||
const operType = ref('add') // add 或 edit
|
||||
const schoolYearList = ref<any[]>([])
|
||||
const schoolTermList = ref<any[]>([])
|
||||
const classList = ref<any[]>([])
|
||||
|
||||
// 提交表单数据
|
||||
const form = reactive({
|
||||
id: '',
|
||||
schoolYear: '',
|
||||
schoolTerm: '',
|
||||
classCode: '',
|
||||
title: '',
|
||||
content: '',
|
||||
remarks: ''
|
||||
@@ -75,6 +131,15 @@ const form = reactive({
|
||||
|
||||
// 定义校验规则
|
||||
const dataRules = {
|
||||
schoolYear: [
|
||||
{ required: true, message: '请选择学年', trigger: 'change' }
|
||||
],
|
||||
schoolTerm: [
|
||||
{ required: true, message: '请选择学期', trigger: 'change' }
|
||||
],
|
||||
classCode: [
|
||||
{ required: true, message: '请选择班号', trigger: 'change' }
|
||||
],
|
||||
title: [
|
||||
{ required: true, message: '请输入标题', trigger: 'blur' }
|
||||
],
|
||||
@@ -92,6 +157,9 @@ const openDialog = async (type: string = 'add', row?: any) => {
|
||||
nextTick(() => {
|
||||
dataFormRef.value?.resetFields()
|
||||
form.id = ''
|
||||
form.schoolYear = ''
|
||||
form.schoolTerm = ''
|
||||
form.classCode = ''
|
||||
form.title = ''
|
||||
form.content = ''
|
||||
form.remarks = ''
|
||||
@@ -99,6 +167,9 @@ const openDialog = async (type: string = 'add', row?: any) => {
|
||||
// 编辑时填充数据
|
||||
if (type === 'edit' && row) {
|
||||
form.id = row.id
|
||||
form.schoolYear = row.schoolYear || ''
|
||||
form.schoolTerm = row.schoolTerm || ''
|
||||
form.classCode = row.classCode || ''
|
||||
form.title = row.title || ''
|
||||
form.content = row.content || ''
|
||||
form.remarks = row.remarks || ''
|
||||
@@ -108,6 +179,9 @@ const openDialog = async (type: string = 'add', row?: any) => {
|
||||
loading.value = true
|
||||
getDetail(row.id).then((res: any) => {
|
||||
if (res.data) {
|
||||
form.schoolYear = res.data.schoolYear || form.schoolYear
|
||||
form.schoolTerm = res.data.schoolTerm || form.schoolTerm
|
||||
form.classCode = res.data.classCode || form.classCode
|
||||
form.content = res.data.content || ''
|
||||
form.remarks = res.data.remarks || ''
|
||||
}
|
||||
@@ -119,6 +193,43 @@ const openDialog = async (type: string = 'add', row?: any) => {
|
||||
})
|
||||
}
|
||||
|
||||
// 学年列表(班级管理-学年接口)
|
||||
const getSchoolYearList = async () => {
|
||||
try {
|
||||
const res = await queryAllSchoolYear()
|
||||
schoolYearList.value = res?.data && Array.isArray(res.data) ? res.data : []
|
||||
} catch (err) {
|
||||
schoolYearList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
// 学期字典(系统通用)
|
||||
const getSchoolTermDict = async () => {
|
||||
try {
|
||||
const res = await getDicts('school_term')
|
||||
if (res?.data && Array.isArray(res.data)) {
|
||||
schoolTermList.value = res.data.map((item: any) => ({
|
||||
label: item.label ?? item.dictLabel ?? item.name,
|
||||
value: item.value ?? item.dictValue ?? item.code
|
||||
}))
|
||||
} else {
|
||||
schoolTermList.value = []
|
||||
}
|
||||
} catch (err) {
|
||||
schoolTermList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
// 班级列表(班级管理目录下)
|
||||
const getClassListData = async () => {
|
||||
try {
|
||||
const res = await getClassListByRole()
|
||||
classList.value = res?.data && Array.isArray(res.data) ? res.data : []
|
||||
} catch (err) {
|
||||
classList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
const onSubmit = async () => {
|
||||
if (!dataFormRef.value) return
|
||||
@@ -129,6 +240,9 @@ const onSubmit = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const submitData = {
|
||||
schoolYear: form.schoolYear,
|
||||
schoolTerm: form.schoolTerm,
|
||||
classCode: form.classCode,
|
||||
title: form.title,
|
||||
content: form.content,
|
||||
remarks: form.remarks
|
||||
@@ -154,6 +268,13 @@ const onSubmit = async () => {
|
||||
})
|
||||
}
|
||||
|
||||
// 初始化:加载学年、学期、班级
|
||||
onMounted(() => {
|
||||
getSchoolYearList()
|
||||
getSchoolTermDict()
|
||||
getClassListData()
|
||||
})
|
||||
|
||||
// 暴露方法
|
||||
defineExpose({
|
||||
openDialog
|
||||
|
||||
@@ -79,7 +79,7 @@
|
||||
<script setup lang="ts" name="ClassroomBaseArrangeDialog">
|
||||
import { ref, reactive, nextTick, onMounted } from 'vue'
|
||||
import { useMessage } from '/@/hooks/message'
|
||||
import { editObj } from '/@/api/stuwork/classroombase'
|
||||
import { addClassRoomAssign } from '/@/api/stuwork/teachclassroomassign'
|
||||
import { queryAllClass } from '/@/api/basic/basicclass'
|
||||
import { getClassRoomList } from '/@/api/stuwork/teachclassroom'
|
||||
import { getBuildingList } from '/@/api/stuwork/teachbuilding'
|
||||
@@ -150,17 +150,16 @@ const onSubmit = async () => {
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
await editObj({
|
||||
id: form.id,
|
||||
await addClassRoomAssign({
|
||||
buildingNo: form.buildingNo,
|
||||
classCode: form.classCode,
|
||||
position: form.position
|
||||
position: form.position,
|
||||
classCode: form.classCode
|
||||
})
|
||||
useMessage().success('教室安排成功')
|
||||
visible.value = false
|
||||
emit('refresh')
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg || '教室安排失败')
|
||||
} catch (_err) {
|
||||
// 错误由 request 拦截器统一提示
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
327
src/views/stuwork/classroombase/assets.vue
Normal file
327
src/views/stuwork/classroombase/assets.vue
Normal file
@@ -0,0 +1,327 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
title="教室公物编辑"
|
||||
v-model="visible"
|
||||
:width="800"
|
||||
:close-on-click-modal="false"
|
||||
draggable>
|
||||
<el-form
|
||||
ref="dataFormRef"
|
||||
:model="form"
|
||||
:rules="dataRules"
|
||||
label-width="120px"
|
||||
v-loading="loading">
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="楼号" prop="buildingNo">
|
||||
<el-input
|
||||
v-model="form.buildingNo"
|
||||
placeholder="楼号"
|
||||
disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="学院" prop="deptName">
|
||||
<el-input
|
||||
v-model="form.deptName"
|
||||
placeholder="学院"
|
||||
disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="班级" prop="classCode">
|
||||
<el-input
|
||||
v-model="form.classNo"
|
||||
placeholder="班级"
|
||||
disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="教室位置" prop="position">
|
||||
<el-input
|
||||
v-model="form.position"
|
||||
placeholder="教室位置"
|
||||
disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="讲台类型" prop="platformType">
|
||||
<el-select
|
||||
v-model="form.platformType"
|
||||
placeholder="请选择讲台类型"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in platformTypeList"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="投影类型" prop="tyType">
|
||||
<el-select
|
||||
v-model="form.tyType"
|
||||
placeholder="请选择投影类型"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in tyTypeList"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="电视机" prop="tvType">
|
||||
<el-select
|
||||
v-model="form.tvType"
|
||||
placeholder="请选择电视机"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in tvTypeList"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="方凳数量" prop="chairCnt">
|
||||
<el-input-number
|
||||
v-model="form.chairCnt"
|
||||
:min="0"
|
||||
placeholder="请输入方凳数量"
|
||||
style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="课桌数量" prop="tableCnt">
|
||||
<el-input-number
|
||||
v-model="form.tableCnt"
|
||||
:min="0"
|
||||
placeholder="请输入课桌数量"
|
||||
style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item label="备注" prop="remarks">
|
||||
<el-input
|
||||
v-model="form.remarks"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="请输入备注" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="visible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="onSubmit" :disabled="loading">确 认</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="ClassroomAssetsDialog">
|
||||
import { ref, reactive, nextTick, onMounted } from 'vue'
|
||||
import { useMessage } from '/@/hooks/message'
|
||||
import { editAssets } from '/@/api/stuwork/classassets'
|
||||
import { getDicts } from '/@/api/admin/dict'
|
||||
|
||||
const emit = defineEmits(['refresh'])
|
||||
|
||||
// 定义变量内容
|
||||
const dataFormRef = ref()
|
||||
const visible = ref(false)
|
||||
const loading = ref(false)
|
||||
const platformTypeList = ref<any[]>([])
|
||||
const tyTypeList = ref<any[]>([])
|
||||
const tvTypeList = ref<any[]>([])
|
||||
|
||||
// 提交表单数据
|
||||
const form = reactive({
|
||||
id: '',
|
||||
buildingNo: '',
|
||||
deptName: '',
|
||||
deptCode: '',
|
||||
classCode: '',
|
||||
classNo: '',
|
||||
position: '',
|
||||
platformType: '',
|
||||
tyType: '',
|
||||
tvType: '',
|
||||
chairCnt: 0,
|
||||
tableCnt: 0,
|
||||
remarks: ''
|
||||
})
|
||||
|
||||
// 定义校验规则
|
||||
const dataRules = {
|
||||
platformType: [
|
||||
{ required: true, message: '请选择讲台类型', trigger: 'change' }
|
||||
],
|
||||
tyType: [
|
||||
{ required: true, message: '请选择投影类型', trigger: 'change' }
|
||||
],
|
||||
tvType: [
|
||||
{ required: true, message: '请选择电视机', trigger: 'change' }
|
||||
],
|
||||
chairCnt: [
|
||||
{ required: true, message: '请填写方凳数量', trigger: 'blur' }
|
||||
],
|
||||
tableCnt: [
|
||||
{ required: true, message: '请填写课桌数量', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
|
||||
// 打开弹窗
|
||||
const openDialog = async (row: any) => {
|
||||
visible.value = true
|
||||
resetForm()
|
||||
|
||||
// 填充现有数据
|
||||
if (row) {
|
||||
form.id = row.id || ''
|
||||
form.buildingNo = row.buildingNo || ''
|
||||
form.deptName = row.deptName || ''
|
||||
form.deptCode = row.deptCode || ''
|
||||
form.classCode = row.classCode || ''
|
||||
form.classNo = row.classNo || ''
|
||||
form.position = row.position || ''
|
||||
form.platformType = row.platformType || ''
|
||||
form.tyType = row.tyType || ''
|
||||
form.tvType = row.tvType || ''
|
||||
form.chairCnt = row.chairCnt || 0
|
||||
form.tableCnt = row.tableCnt || 0
|
||||
form.remarks = row.remarks || ''
|
||||
}
|
||||
|
||||
nextTick(() => {
|
||||
dataFormRef.value?.clearValidate()
|
||||
})
|
||||
}
|
||||
|
||||
// 重置表单
|
||||
const resetForm = () => {
|
||||
form.id = ''
|
||||
form.buildingNo = ''
|
||||
form.deptName = ''
|
||||
form.deptCode = ''
|
||||
form.classCode = ''
|
||||
form.classNo = ''
|
||||
form.position = ''
|
||||
form.platformType = ''
|
||||
form.tyType = ''
|
||||
form.tvType = ''
|
||||
form.chairCnt = 0
|
||||
form.tableCnt = 0
|
||||
form.remarks = ''
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
const onSubmit = async () => {
|
||||
if (!dataFormRef.value) return
|
||||
|
||||
await dataFormRef.value.validate(async (valid: boolean) => {
|
||||
if (!valid) return
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
const submitData = {
|
||||
id: form.id,
|
||||
buildingNo: form.buildingNo,
|
||||
deptName: form.deptName,
|
||||
deptCode: form.deptCode,
|
||||
classCode: form.classCode,
|
||||
classNo: form.classNo,
|
||||
position: form.position,
|
||||
platformType: form.platformType,
|
||||
tyType: form.tyType,
|
||||
tvType: form.tvType,
|
||||
chairCnt: form.chairCnt,
|
||||
tableCnt: form.tableCnt,
|
||||
remarks: form.remarks
|
||||
}
|
||||
await editAssets(submitData)
|
||||
useMessage().success('保存成功')
|
||||
visible.value = false
|
||||
emit('refresh')
|
||||
} catch (_err) {
|
||||
// 错误由 request 拦截器统一提示
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 获取讲台类型字典
|
||||
const getPlatformTypeDict = async () => {
|
||||
try {
|
||||
const res = await getDicts('platform_type')
|
||||
if (res.data) {
|
||||
platformTypeList.value = Array.isArray(res.data) ? res.data.map((item: any) => ({
|
||||
label: item.label || item.dictLabel || item.name,
|
||||
value: item.value || item.dictValue || item.code
|
||||
})) : []
|
||||
}
|
||||
} catch (err) {
|
||||
platformTypeList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
// 获取投影类型字典
|
||||
const getTyTypeDict = async () => {
|
||||
try {
|
||||
const res = await getDicts('ty_type')
|
||||
if (res.data) {
|
||||
tyTypeList.value = Array.isArray(res.data) ? res.data.map((item: any) => ({
|
||||
label: item.label || item.dictLabel || item.name,
|
||||
value: item.value || item.dictValue || item.code
|
||||
})) : []
|
||||
}
|
||||
} catch (err) {
|
||||
tyTypeList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
// 获取电视机类型字典
|
||||
const getTvTypeDict = async () => {
|
||||
try {
|
||||
const res = await getDicts('tv_type')
|
||||
if (res.data) {
|
||||
tvTypeList.value = Array.isArray(res.data) ? res.data.map((item: any) => ({
|
||||
label: item.label || item.dictLabel || item.name,
|
||||
value: item.value || item.dictValue || item.code
|
||||
})) : []
|
||||
}
|
||||
} catch (err) {
|
||||
tvTypeList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
getPlatformTypeDict()
|
||||
getTyTypeDict()
|
||||
getTvTypeDict()
|
||||
})
|
||||
|
||||
// 暴露方法
|
||||
defineExpose({
|
||||
openDialog
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
</style>
|
||||
@@ -181,19 +181,43 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
<el-table-column label="操作" width="120" align="center" fixed="right">
|
||||
<el-table-column label="操作" width="380" align="center" fixed="right">
|
||||
<template #header>
|
||||
<el-icon><Setting /></el-icon>
|
||||
<span style="margin-left: 4px">操作</span>
|
||||
</template>
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
v-if="!scope.row.classCode || !scope.row.position"
|
||||
icon="Setting"
|
||||
link
|
||||
type="primary"
|
||||
@click="handleArrange(scope.row)">
|
||||
教室安排
|
||||
</el-button>
|
||||
<el-button
|
||||
icon="Close"
|
||||
link
|
||||
type="danger"
|
||||
@click="handleCancelArrange(scope.row)">
|
||||
取消教室安排
|
||||
</el-button>
|
||||
<el-button
|
||||
icon="Collection"
|
||||
link
|
||||
type="success"
|
||||
@click="handleAssets(scope.row)"
|
||||
class="ml10">
|
||||
教室公物
|
||||
</el-button>
|
||||
<el-button
|
||||
icon="Lock"
|
||||
link
|
||||
type="warning"
|
||||
@click="handlePassword(scope.row)"
|
||||
class="ml10">
|
||||
门锁密码
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<template #empty>
|
||||
@@ -213,6 +237,10 @@
|
||||
|
||||
<!-- 教室安排表单弹窗 -->
|
||||
<arrange-dialog ref="arrangeDialogRef" @refresh="getDataList" />
|
||||
<!-- 教室公物编辑弹窗 -->
|
||||
<assets-dialog ref="assetsDialogRef" @refresh="getDataList" />
|
||||
<!-- 门锁密码编辑弹窗 -->
|
||||
<password-dialog ref="passwordDialogRef" @refresh="getDataList" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -226,7 +254,10 @@ import { getDicts } from "/@/api/admin/dict";
|
||||
import { useMessage, useMessageBox } from "/@/hooks/message";
|
||||
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
|
||||
import ArrangeDialog from './arrange.vue'
|
||||
import { List, OfficeBuilding, CircleCheck, Location, UserFilled, Collection, Setting, Menu, Calendar, Search, Document } from '@element-plus/icons-vue'
|
||||
import AssetsDialog from './assets.vue'
|
||||
import PasswordDialog from './password.vue'
|
||||
import { delClassRoomAssign } from '/@/api/stuwork/teachclassroomassign'
|
||||
import { List, OfficeBuilding, CircleCheck, Location, UserFilled, Collection, Setting, Menu, Calendar, Search, Document, Close, Lock } from '@element-plus/icons-vue'
|
||||
import { useTableColumnControl } from '/@/hooks/tableColumn'
|
||||
|
||||
// 定义变量内容
|
||||
@@ -240,6 +271,8 @@ const platformTypeList = ref<any[]>([])
|
||||
const tyTypeList = ref<any[]>([])
|
||||
const tvTypeList = ref<any[]>([])
|
||||
const arrangeDialogRef = ref()
|
||||
const assetsDialogRef = ref()
|
||||
const passwordDialogRef = ref()
|
||||
|
||||
// 表格列配置
|
||||
const tableColumns = [
|
||||
@@ -416,6 +449,33 @@ const handleArrange = (row: any) => {
|
||||
arrangeDialogRef.value?.openDialog(row)
|
||||
}
|
||||
|
||||
// 取消教室安排
|
||||
const handleCancelArrange = async (row: any) => {
|
||||
const { confirm } = useMessageBox()
|
||||
try {
|
||||
await confirm('确定要取消教室安排吗?')
|
||||
await delClassRoomAssign(row)
|
||||
useMessage().success('取消成功')
|
||||
getDataList()
|
||||
} catch (err: any) {
|
||||
if (err !== 'cancel') {
|
||||
if (!err?._messageShown) {
|
||||
useMessage().error(err?.msg || '取消失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 教室公物
|
||||
const handleAssets = (row: any) => {
|
||||
assetsDialogRef.value?.openDialog(row)
|
||||
}
|
||||
|
||||
// 门锁密码
|
||||
const handlePassword = (row: any) => {
|
||||
passwordDialogRef.value?.openDialog(row)
|
||||
}
|
||||
|
||||
// 获取系部列表
|
||||
const getDeptListData = async () => {
|
||||
try {
|
||||
|
||||
156
src/views/stuwork/classroombase/password.vue
Normal file
156
src/views/stuwork/classroombase/password.vue
Normal file
@@ -0,0 +1,156 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
title="门锁密码"
|
||||
v-model="visible"
|
||||
:width="500"
|
||||
:close-on-click-modal="false"
|
||||
draggable>
|
||||
<el-form
|
||||
ref="dataFormRef"
|
||||
:model="form"
|
||||
:rules="dataRules"
|
||||
label-width="120px"
|
||||
v-loading="loading">
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item label="楼号">
|
||||
<el-input
|
||||
v-model="form.buildingNo"
|
||||
placeholder="楼号"
|
||||
disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item label="教室位置">
|
||||
<el-input
|
||||
v-model="form.position"
|
||||
placeholder="教室位置"
|
||||
disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item label="门锁密码" prop="password">
|
||||
<el-input
|
||||
v-model="form.password"
|
||||
type="password"
|
||||
show-password
|
||||
placeholder="请输入门锁密码"
|
||||
clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="visible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="onSubmit" :disabled="loading">确 认</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="ClassroomPasswordDialog">
|
||||
import { ref, reactive, nextTick } from 'vue'
|
||||
import { useMessage } from '/@/hooks/message'
|
||||
import { editAssets } from '/@/api/stuwork/classassets'
|
||||
|
||||
const emit = defineEmits(['refresh'])
|
||||
|
||||
// 定义变量内容
|
||||
const dataFormRef = ref()
|
||||
const visible = ref(false)
|
||||
const loading = ref(false)
|
||||
|
||||
// 提交表单数据
|
||||
const form = reactive({
|
||||
id: '',
|
||||
buildingNo: '',
|
||||
deptName: '',
|
||||
deptCode: '',
|
||||
classCode: '',
|
||||
classNo: '',
|
||||
position: '',
|
||||
password: ''
|
||||
})
|
||||
|
||||
// 定义校验规则
|
||||
const dataRules = {
|
||||
password: [
|
||||
{ required: true, message: '请输入门锁密码', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
|
||||
// 打开弹窗
|
||||
const openDialog = async (row: any) => {
|
||||
visible.value = true
|
||||
resetForm()
|
||||
|
||||
// 填充现有数据
|
||||
if (row) {
|
||||
form.id = row.id || ''
|
||||
form.buildingNo = row.buildingNo || ''
|
||||
form.deptName = row.deptName || ''
|
||||
form.deptCode = row.deptCode || ''
|
||||
form.classCode = row.classCode || ''
|
||||
form.classNo = row.classNo || ''
|
||||
form.position = row.position || ''
|
||||
form.password = row.password || ''
|
||||
}
|
||||
|
||||
nextTick(() => {
|
||||
dataFormRef.value?.clearValidate()
|
||||
})
|
||||
}
|
||||
|
||||
// 重置表单
|
||||
const resetForm = () => {
|
||||
form.id = ''
|
||||
form.buildingNo = ''
|
||||
form.deptName = ''
|
||||
form.deptCode = ''
|
||||
form.classCode = ''
|
||||
form.classNo = ''
|
||||
form.position = ''
|
||||
form.password = ''
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
const onSubmit = async () => {
|
||||
if (!dataFormRef.value) return
|
||||
|
||||
await dataFormRef.value.validate(async (valid: boolean) => {
|
||||
if (!valid) return
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
// 只提交必要的字段用于修改门锁密码
|
||||
const submitData = {
|
||||
id: form.id,
|
||||
buildingNo: form.buildingNo,
|
||||
deptName: form.deptName,
|
||||
deptCode: form.deptCode,
|
||||
classCode: form.classCode,
|
||||
classNo: form.classNo,
|
||||
position: form.position,
|
||||
password: form.password
|
||||
}
|
||||
await editAssets(submitData)
|
||||
useMessage().success('保存成功')
|
||||
visible.value = false
|
||||
emit('refresh')
|
||||
} catch (_err) {
|
||||
// 错误由 request 拦截器统一提示
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 暴露方法
|
||||
defineExpose({
|
||||
openDialog
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
</style>
|
||||
@@ -495,7 +495,9 @@ const confirmCheck = async () => {
|
||||
checkDialogVisible.value = false
|
||||
getDataList()
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg || '考核失败')
|
||||
if (!err?._messageShown) {
|
||||
useMessage().error(err?.msg || '考核失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -512,7 +514,9 @@ const handleDelete = async (ids: string[]) => {
|
||||
getDataList()
|
||||
useMessage().success('删除成功')
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg || '删除失败')
|
||||
if (!err?._messageShown) {
|
||||
useMessage().error(err?.msg || '删除失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -45,40 +45,6 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item label="学院" prop="deptCode">
|
||||
<el-select
|
||||
v-model="form.deptCode"
|
||||
placeholder="请选择学院"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in deptList"
|
||||
:key="item.deptCode"
|
||||
:label="item.deptName"
|
||||
:value="item.deptCode">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item label="班级" prop="classNo">
|
||||
<el-select
|
||||
v-model="form.classNo"
|
||||
placeholder="请选择班级"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in classList"
|
||||
:key="item.classCode"
|
||||
:label="item.classNo"
|
||||
:value="item.classNo">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
@@ -95,8 +61,6 @@ import { ref, reactive, nextTick, onMounted } from 'vue'
|
||||
import { useMessage } from '/@/hooks/message'
|
||||
import { initObj } from '/@/api/stuwork/classtheme'
|
||||
import { queryAllSchoolYear } from '/@/api/basic/basicyear'
|
||||
import { getClassListByRole } from '/@/api/basic/basicclass'
|
||||
import { getDeptList } from '/@/api/basic/basicclass'
|
||||
import { getDicts } from '/@/api/admin/dict'
|
||||
|
||||
const emit = defineEmits(['refresh'])
|
||||
@@ -107,15 +71,11 @@ const visible = ref(false)
|
||||
const loading = ref(false)
|
||||
const schoolYearList = ref<any[]>([])
|
||||
const schoolTermList = ref<any[]>([])
|
||||
const deptList = ref<any[]>([])
|
||||
const classList = ref<any[]>([])
|
||||
|
||||
// 提交表单数据
|
||||
const form = reactive({
|
||||
schoolYear: '',
|
||||
schoolTerm: '',
|
||||
deptCode: '',
|
||||
classNo: ''
|
||||
schoolTerm: ''
|
||||
})
|
||||
|
||||
// 定义校验规则
|
||||
@@ -125,12 +85,6 @@ const dataRules = {
|
||||
],
|
||||
schoolTerm: [
|
||||
{ required: true, message: '请选择学期', trigger: 'change' }
|
||||
],
|
||||
deptCode: [
|
||||
{ required: true, message: '请选择学院', trigger: 'change' }
|
||||
],
|
||||
classNo: [
|
||||
{ required: true, message: '请选择班级', trigger: 'change' }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -141,8 +95,6 @@ const openDialog = () => {
|
||||
dataFormRef.value?.resetFields()
|
||||
form.schoolYear = ''
|
||||
form.schoolTerm = ''
|
||||
form.deptCode = ''
|
||||
form.classNo = ''
|
||||
})
|
||||
}
|
||||
|
||||
@@ -192,34 +144,10 @@ const getSchoolTermDict = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 获取学院列表
|
||||
const getDeptListData = async () => {
|
||||
try {
|
||||
const res = await getDeptList()
|
||||
if (res.data && Array.isArray(res.data)) {
|
||||
deptList.value = res.data
|
||||
}
|
||||
} catch (err) {
|
||||
}
|
||||
}
|
||||
|
||||
// 获取班级列表
|
||||
const getClassListData = async () => {
|
||||
try {
|
||||
const res = await getClassListByRole()
|
||||
if (res.data && Array.isArray(res.data)) {
|
||||
classList.value = res.data
|
||||
}
|
||||
} catch (err) {
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
getSchoolYearList()
|
||||
getSchoolTermDict()
|
||||
getDeptListData()
|
||||
getClassListData()
|
||||
})
|
||||
|
||||
// 暴露方法
|
||||
|
||||
@@ -17,7 +17,19 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="宿舍号" prop="roomNo">
|
||||
<el-input v-model="form.roomNo" placeholder="请输入宿舍号" />
|
||||
<el-select
|
||||
v-model="form.roomNo"
|
||||
placeholder="请选择宿舍号"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in roomList"
|
||||
:key="item.roomNo"
|
||||
:label="item.roomNo"
|
||||
:value="item.roomNo">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="记录日期" prop="recordDate">
|
||||
<el-date-picker
|
||||
@@ -50,6 +62,7 @@ import { ref, reactive, nextTick, onMounted } from 'vue'
|
||||
import { useMessage } from '/@/hooks/message'
|
||||
import { addObj, putObj, getObj } from '/@/api/stuwork/dormhygienedaily'
|
||||
import { getBuildingList } from '/@/api/stuwork/dormbuilding'
|
||||
import { getRoomList } from '/@/api/stuwork/dormroom'
|
||||
|
||||
const emit = defineEmits(['refresh'])
|
||||
|
||||
@@ -59,6 +72,7 @@ const visible = ref(false)
|
||||
const loading = ref(false)
|
||||
const operType = ref('add') // add 或 edit
|
||||
const buildingList = ref<any[]>([])
|
||||
const roomList = ref<any[]>([])
|
||||
|
||||
// 提交表单数据
|
||||
const form = reactive({
|
||||
@@ -75,7 +89,7 @@ const dataRules = {
|
||||
{ required: true, message: '请选择楼号', trigger: 'change' }
|
||||
],
|
||||
roomNo: [
|
||||
{ required: true, message: '请输入宿舍号', trigger: 'blur' }
|
||||
{ required: true, message: '请选择宿舍号', trigger: 'change' }
|
||||
],
|
||||
recordDate: [
|
||||
{ required: true, message: '请选择记录日期', trigger: 'change' }
|
||||
@@ -159,9 +173,22 @@ const getBuildingListData = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 获取宿舍号列表
|
||||
const getRoomListData = async () => {
|
||||
try {
|
||||
const res = await getRoomList()
|
||||
if (res.data) {
|
||||
roomList.value = Array.isArray(res.data) ? res.data : []
|
||||
}
|
||||
} catch (err) {
|
||||
roomList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
getBuildingListData()
|
||||
getRoomListData()
|
||||
})
|
||||
|
||||
// 暴露方法给父组件
|
||||
|
||||
@@ -2,7 +2,19 @@
|
||||
<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="roomNo">
|
||||
<el-input v-model="form.roomNo" placeholder="请输入房间号" />
|
||||
<el-select
|
||||
v-model="form.roomNo"
|
||||
placeholder="请选择房间号"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in roomList"
|
||||
:key="item.roomNo"
|
||||
:label="item.roomNo"
|
||||
:value="item.roomNo">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="整改时间" prop="reformDate">
|
||||
<el-date-picker
|
||||
@@ -31,9 +43,10 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="DormReformFormDialog">
|
||||
import { ref, reactive, nextTick } from 'vue'
|
||||
import { ref, reactive, nextTick, onMounted } from 'vue'
|
||||
import { useMessage } from '/@/hooks/message'
|
||||
import { addObj, putObj } from '/@/api/stuwork/dormreform'
|
||||
import { getRoomList } from '/@/api/stuwork/dormroom'
|
||||
|
||||
const emit = defineEmits(['refresh'])
|
||||
|
||||
@@ -42,6 +55,7 @@ const dataFormRef = ref()
|
||||
const visible = ref(false)
|
||||
const loading = ref(false)
|
||||
const operType = ref('add') // add 或 edit
|
||||
const roomList = ref<any[]>([])
|
||||
|
||||
// 提交表单数据
|
||||
const form = reactive({
|
||||
@@ -54,7 +68,7 @@ const form = reactive({
|
||||
// 定义校验规则
|
||||
const dataRules = {
|
||||
roomNo: [
|
||||
{ required: true, message: '请输入房间号', trigger: 'blur' }
|
||||
{ required: true, message: '请选择房间号', trigger: 'change' }
|
||||
],
|
||||
reformDate: [
|
||||
{ required: true, message: '请选择整改时间', trigger: 'change' }
|
||||
@@ -90,6 +104,23 @@ const openDialog = (type: string = 'add', row?: any) => {
|
||||
})
|
||||
}
|
||||
|
||||
// 获取房间号列表
|
||||
const getRoomListData = async () => {
|
||||
try {
|
||||
const res = await getRoomList()
|
||||
if (res.data) {
|
||||
roomList.value = Array.isArray(res.data) ? res.data : []
|
||||
}
|
||||
} catch (err) {
|
||||
roomList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
getRoomListData()
|
||||
})
|
||||
|
||||
// 提交表单
|
||||
const onSubmit = async () => {
|
||||
if (!dataFormRef.value) return
|
||||
|
||||
@@ -89,9 +89,11 @@
|
||||
style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in bedNoList"
|
||||
:key="item"
|
||||
:label="item"
|
||||
:value="item">
|
||||
:key="item.bedNo"
|
||||
:value="item.bedNo">
|
||||
<span :class="{ 'bed-option-occupied': item.haveStudent }">
|
||||
{{ item.bedNo }}{{ item.haveStudent ? ' (有人)' : '' }}
|
||||
</span>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
@@ -138,7 +140,8 @@ const loading = ref(false)
|
||||
const classList = ref<any[]>([])
|
||||
const studentList = ref<any[]>([])
|
||||
const roomList = ref<any[]>([])
|
||||
const bedNoList = ref<string[]>([])
|
||||
// 床位列表:支持 haveStudent 标记(true=有人,false=无人)
|
||||
const bedNoList = ref<Array<{ bedNo: string; haveStudent: boolean }>>([])
|
||||
|
||||
// 提交表单数据
|
||||
const form = reactive({
|
||||
@@ -206,28 +209,33 @@ const handleStudentChange = (stuNo: string) => {
|
||||
}
|
||||
}
|
||||
|
||||
// 房间号变化时获取床位号列表
|
||||
// 房间号变化时获取床位号列表(支持 haveStudent:true=有人,false=无人)
|
||||
const handleRoomChange = async (roomNo: string) => {
|
||||
if (!roomNo) {
|
||||
bedNoList.value = []
|
||||
form.bedNo = ''
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
const toBedItem = (item: any): { bedNo: string; haveStudent: boolean } => {
|
||||
if (typeof item === 'number' || typeof item === 'string') {
|
||||
return { bedNo: String(item), haveStudent: false }
|
||||
}
|
||||
return {
|
||||
bedNo: String(item?.bedNo ?? item?.value ?? item ?? ''),
|
||||
haveStudent: !!(item && item.haveStudent === true)
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fearchRoomStuNum(roomNo)
|
||||
if (res.data) {
|
||||
// 根据返回的数据结构处理床位号列表
|
||||
// 假设返回的是数字数组或对象数组
|
||||
if (Array.isArray(res.data)) {
|
||||
bedNoList.value = res.data.map((item: any) => {
|
||||
if (typeof item === 'number' || typeof item === 'string') {
|
||||
return String(item)
|
||||
}
|
||||
return String(item.bedNo || item.value || item)
|
||||
})
|
||||
bedNoList.value = res.data.map(toBedItem).filter((b) => b.bedNo)
|
||||
} else if (res.data.bedNos && Array.isArray(res.data.bedNos)) {
|
||||
bedNoList.value = res.data.bedNos.map((item: any) => String(item))
|
||||
bedNoList.value = res.data.bedNos.map((item: any) =>
|
||||
toBedItem(typeof item === 'object' ? item : { bedNo: String(item), haveStudent: false })
|
||||
)
|
||||
} else {
|
||||
bedNoList.value = []
|
||||
}
|
||||
@@ -321,3 +329,8 @@ defineExpose({
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.bed-option-occupied {
|
||||
color: var(--el-color-warning);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -211,9 +211,13 @@
|
||||
<el-icon><component :is="columnConfigMap[col.prop || '']?.icon || OfficeBuilding" /></el-icon>
|
||||
<span style="margin-left: 4px">{{ col.label }}</span>
|
||||
</template>
|
||||
<!-- 床位号列特殊模板 -->
|
||||
<!-- 床位号列:haveStudent 为 true 时变色标记有人 -->
|
||||
<template v-if="col.prop === 'bedNo'" #default="scope">
|
||||
<el-tag v-if="scope.row.bedNo" size="small" type="info" effect="plain">
|
||||
<el-tag
|
||||
v-if="scope.row.bedNo"
|
||||
size="small"
|
||||
:type="scope.row.haveStudent ? 'warning' : 'info'"
|
||||
effect="plain">
|
||||
{{ scope.row.bedNo }}
|
||||
</el-tag>
|
||||
<span v-else>-</span>
|
||||
@@ -274,6 +278,12 @@
|
||||
|
||||
<!-- 转宿弹窗 -->
|
||||
<TransferDialog ref="transferDialogRef" @refresh="getDataList" />
|
||||
|
||||
<!-- 宿舍互换弹窗 -->
|
||||
<SwapDialog ref="swapDialogRef" @refresh="getDataList" />
|
||||
|
||||
<!-- 打印宿舍卡弹窗 -->
|
||||
<PrintCardDialog ref="printCardDialogRef" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -281,7 +291,7 @@
|
||||
import { reactive, ref, onMounted, computed, nextTick } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { BasicTableProps, useTable } from "/@/hooks/table";
|
||||
import { fetchList, delObjs } from "/@/api/stuwork/dormroomstudent";
|
||||
import { fetchList, delObjs, exportEmptyPeopleRoomExcel } from "/@/api/stuwork/dormroomstudent";
|
||||
import { getDeptList } from "/@/api/basic/basicclass";
|
||||
import { getBuildingList } from "/@/api/stuwork/dormbuilding";
|
||||
import { fetchDormRoomTreeList } from "/@/api/stuwork/dormroom";
|
||||
@@ -289,6 +299,8 @@ import { useMessage, useMessageBox } from "/@/hooks/message";
|
||||
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
|
||||
import FormDialog from './form.vue';
|
||||
import TransferDialog from './transfer.vue';
|
||||
import SwapDialog from './swap.vue';
|
||||
import PrintCardDialog from './printCard.vue';
|
||||
import TreeSelect from '/@/components/TreeSelect/index.vue';
|
||||
import { List, OfficeBuilding, House, Grid, UserFilled, Phone, CreditCard, Avatar, User, Setting, Menu, Search, Document } from '@element-plus/icons-vue'
|
||||
import { useTableColumnControl } from '/@/hooks/tableColumn'
|
||||
@@ -301,6 +313,8 @@ const route = useRoute()
|
||||
const formDialogRef = ref()
|
||||
const columnControlRef = ref<any>()
|
||||
const transferDialogRef = ref()
|
||||
const swapDialogRef = ref()
|
||||
const printCardDialogRef = ref()
|
||||
const searchFormRef = ref()
|
||||
const showSearch = ref(true)
|
||||
const deptList = ref<any[]>([])
|
||||
@@ -425,29 +439,91 @@ const handleDormDataTypeChange = (dormdataType: string) => {
|
||||
getDormRoomTreeListData(dormdataType)
|
||||
}
|
||||
|
||||
// 打印宿舍卡
|
||||
// 打印宿舍卡(按房间号查询后打印)
|
||||
const handlePrintCard = () => {
|
||||
useMessage().warning('功能开发中')
|
||||
printCardDialogRef.value?.openDialog()
|
||||
}
|
||||
|
||||
// 宿舍互换
|
||||
// 宿舍互换(两名学生互换宿舍)
|
||||
const handleRoomSwap = () => {
|
||||
useMessage().warning('功能开发中')
|
||||
const query = {
|
||||
deptCode: searchForm.deptCode,
|
||||
buildingNo: searchForm.buildingNo,
|
||||
gender: searchForm.gender,
|
||||
roomNo: searchForm.roomNo || searchForm.roomNoInput,
|
||||
classNo: searchForm.classNo,
|
||||
stuNo: searchForm.stuNo,
|
||||
realName: searchForm.realName
|
||||
}
|
||||
swapDialogRef.value?.openDialog(query)
|
||||
}
|
||||
|
||||
// 导出
|
||||
const handleExport = () => {
|
||||
useMessage().warning('功能开发中')
|
||||
// 导出:空 n 人宿舍导出(按当前筛选条件传参)
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
const params = {
|
||||
deptCode: searchForm.deptCode,
|
||||
buildingNo: searchForm.buildingNo,
|
||||
gender: searchForm.gender,
|
||||
dormdataType: searchForm.dormdataType,
|
||||
roomNo: searchForm.roomNo || searchForm.roomNoInput,
|
||||
classNo: searchForm.classNo,
|
||||
stuNo: searchForm.stuNo,
|
||||
realName: searchForm.realName
|
||||
}
|
||||
const res = await exportEmptyPeopleRoomExcel(params)
|
||||
const blob = res instanceof Blob ? res : new Blob([res as BlobPart], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
|
||||
const url = window.URL.createObjectURL(blob)
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.download = `空宿舍导出_${Date.now()}.xlsx`
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
window.URL.revokeObjectURL(url)
|
||||
useMessage().success('导出成功')
|
||||
} catch (err: any) {
|
||||
useMessage().error(err?.msg || '导出失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 名单导出
|
||||
const handleExportList = () => {
|
||||
useMessage().warning('功能开发中')
|
||||
// 名单导出:与导出共用空 n 人宿舍导出接口,文件名区分
|
||||
const handleExportList = async () => {
|
||||
try {
|
||||
const params = {
|
||||
deptCode: searchForm.deptCode,
|
||||
buildingNo: searchForm.buildingNo,
|
||||
gender: searchForm.gender,
|
||||
dormdataType: searchForm.dormdataType,
|
||||
roomNo: searchForm.roomNo || searchForm.roomNoInput,
|
||||
classNo: searchForm.classNo,
|
||||
stuNo: searchForm.stuNo,
|
||||
realName: searchForm.realName
|
||||
}
|
||||
const res = await exportEmptyPeopleRoomExcel(params)
|
||||
const blob = res instanceof Blob ? res : new Blob([res as BlobPart], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
|
||||
const url = window.URL.createObjectURL(blob)
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.download = `住宿学生名单_${Date.now()}.xlsx`
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
window.URL.revokeObjectURL(url)
|
||||
useMessage().success('导出成功')
|
||||
} catch (err: any) {
|
||||
useMessage().error(err?.msg || '导出失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 编辑(与转宿共用接口 edit,修改房间/床位/是否舍长)
|
||||
const handleEdit = (row: any) => {
|
||||
transferDialogRef.value?.openDialog(row)
|
||||
}
|
||||
|
||||
// 转宿
|
||||
const handleTransfer = (row: any) => {
|
||||
transferDialogRef.value.openDialog(row)
|
||||
transferDialogRef.value?.openDialog(row)
|
||||
}
|
||||
|
||||
// 退宿
|
||||
|
||||
152
src/views/stuwork/dormroomstudent/printCard.vue
Normal file
152
src/views/stuwork/dormroomstudent/printCard.vue
Normal file
@@ -0,0 +1,152 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
title="打印宿舍卡"
|
||||
v-model="visible"
|
||||
:close-on-click-modal="false"
|
||||
width="640px"
|
||||
destroy-on-close
|
||||
@closed="onClosed">
|
||||
<div v-loading="loading">
|
||||
<el-form :inline="true" class="mb16">
|
||||
<el-form-item label="房间号">
|
||||
<el-input v-model="roomNo" placeholder="请输入房间号" clearable style="width: 180px" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleFetch">查询并预览</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div v-if="printData.length" ref="printAreaRef" class="print-area">
|
||||
<div v-for="(room, idx) in printData" :key="idx" class="room-card mb16">
|
||||
<div class="room-title">房间号:{{ room.roomNo }}</div>
|
||||
<table class="print-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>姓名</th>
|
||||
<th>学号</th>
|
||||
<th>床位</th>
|
||||
<th>是否舍长</th>
|
||||
<th>班级</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(s, i) in (room.dormRoomStudentVOList || [])" :key="i">
|
||||
<td>{{ s.realName }}</td>
|
||||
<td>{{ s.stuNo }}</td>
|
||||
<td>{{ s.bedNo }}</td>
|
||||
<td>{{ s.isLeader === '1' ? '是' : '否' }}</td>
|
||||
<td>{{ s.className || s.classNo }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="visible = false">关 闭</el-button>
|
||||
<el-button type="primary" @click="handlePrint" :disabled="!printData.length">打 印</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="DormRoomStudentPrintCard">
|
||||
import { ref } from 'vue'
|
||||
import { useMessage } from '/@/hooks/message'
|
||||
import { printDormRoomData } from '/@/api/stuwork/dormroomstudent'
|
||||
|
||||
const visible = ref(false)
|
||||
const loading = ref(false)
|
||||
const roomNo = ref('')
|
||||
const printData = ref<any[]>([])
|
||||
const printAreaRef = ref<HTMLElement>()
|
||||
|
||||
const openDialog = (initialRoomNo?: string) => {
|
||||
visible.value = true
|
||||
roomNo.value = initialRoomNo || ''
|
||||
printData.value = []
|
||||
}
|
||||
|
||||
const onClosed = () => {
|
||||
printData.value = []
|
||||
}
|
||||
|
||||
const handleFetch = async () => {
|
||||
const no = (roomNo.value || '').trim()
|
||||
if (!no) {
|
||||
useMessage().warning('请输入房间号')
|
||||
return
|
||||
}
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await printDormRoomData(no)
|
||||
const data = res?.data ?? res
|
||||
printData.value = Array.isArray(data) ? data : (data ? [data] : [])
|
||||
if (!printData.value.length) {
|
||||
useMessage().info('该房间暂无数据')
|
||||
}
|
||||
} catch (err: any) {
|
||||
useMessage().error(err?.msg || '查询失败')
|
||||
printData.value = []
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handlePrint = () => {
|
||||
if (!printData.value.length) return
|
||||
const win = window.open('', '_blank')
|
||||
if (!win) {
|
||||
useMessage().error('无法打开打印窗口')
|
||||
return
|
||||
}
|
||||
const el = printAreaRef.value
|
||||
if (el) {
|
||||
win.document.write(`
|
||||
<!DOCTYPE html><html><head><meta charset="utf-8"><title>宿舍卡</title>
|
||||
<style>
|
||||
table { border-collapse: collapse; width: 100%; }
|
||||
th, td { border: 1px solid #333; padding: 6px 8px; text-align: center; }
|
||||
.room-title { font-weight: bold; margin-bottom: 8px; }
|
||||
</style>
|
||||
</head><body>${el.innerHTML}</body></html>
|
||||
`)
|
||||
win.document.close()
|
||||
win.focus()
|
||||
setTimeout(() => {
|
||||
win.print()
|
||||
win.close()
|
||||
}, 300)
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({ openDialog })
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.print-area {
|
||||
max-height: 60vh;
|
||||
overflow: auto;
|
||||
}
|
||||
.room-card {
|
||||
padding: 12px;
|
||||
border: 1px solid var(--el-border-color);
|
||||
border-radius: 4px;
|
||||
}
|
||||
.room-title {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.print-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
th,
|
||||
td {
|
||||
border: 1px solid var(--el-border-color);
|
||||
padding: 6px 8px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
.mb16 {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
</style>
|
||||
136
src/views/stuwork/dormroomstudent/swap.vue
Normal file
136
src/views/stuwork/dormroomstudent/swap.vue
Normal file
@@ -0,0 +1,136 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
title="宿舍互换"
|
||||
v-model="visible"
|
||||
:close-on-click-modal="false"
|
||||
draggable
|
||||
width="520px">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
label-width="100px"
|
||||
:validate-on-rule-change="false"
|
||||
v-loading="loading">
|
||||
<el-form-item label="学生一" prop="sourceSutNo">
|
||||
<el-select
|
||||
v-model="form.sourceSutNo"
|
||||
placeholder="请选择学生(学号)"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 100%"
|
||||
@change="onSourceChange">
|
||||
<el-option
|
||||
v-for="item in studentOptions"
|
||||
:key="item.stuNo"
|
||||
:label="`${item.realName}(${item.stuNo})${item.roomNo || ''}`"
|
||||
:value="item.stuNo" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="学生二" prop="targetStuNO">
|
||||
<el-select
|
||||
v-model="form.targetStuNO"
|
||||
placeholder="请选择学生(学号)"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 100%"
|
||||
@change="onTargetChange">
|
||||
<el-option
|
||||
v-for="item in studentOptions"
|
||||
:key="item.stuNo"
|
||||
:label="`${item.realName}(${item.stuNo})${item.roomNo || ''}`"
|
||||
:value="item.stuNo" />
|
||||
</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" :loading="submitting">确 认 互 换</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="DormRoomStudentSwapDialog">
|
||||
import { ref, reactive } from 'vue'
|
||||
import { useMessage } from '/@/hooks/message'
|
||||
import { exchangeRoom } from '/@/api/stuwork/dormroomstudent'
|
||||
import { fetchList } from '/@/api/stuwork/dormroomstudent'
|
||||
|
||||
const emit = defineEmits(['refresh'])
|
||||
|
||||
const formRef = ref()
|
||||
const visible = ref(false)
|
||||
const loading = ref(false)
|
||||
const submitting = ref(false)
|
||||
const studentOptions = ref<any[]>([])
|
||||
|
||||
const form = reactive({
|
||||
sourceSutNo: '',
|
||||
targetStuNO: ''
|
||||
})
|
||||
|
||||
const rules = {
|
||||
sourceSutNo: [{ required: true, message: '请选择学生一', trigger: 'change' }],
|
||||
targetStuNO: [{ required: true, message: '请选择学生二', trigger: 'change' }]
|
||||
}
|
||||
|
||||
const onSourceChange = () => {
|
||||
if (form.sourceSutNo && form.targetStuNO && form.sourceSutNo === form.targetStuNO) {
|
||||
form.targetStuNO = ''
|
||||
}
|
||||
}
|
||||
const onTargetChange = () => {
|
||||
if (form.sourceSutNo && form.targetStuNO && form.sourceSutNo === form.targetStuNO) {
|
||||
form.sourceSutNo = ''
|
||||
}
|
||||
}
|
||||
|
||||
const openDialog = async (queryParams?: any) => {
|
||||
visible.value = true
|
||||
form.sourceSutNo = ''
|
||||
form.targetStuNO = ''
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await fetchList({
|
||||
current: 1,
|
||||
size: 500,
|
||||
...queryParams
|
||||
})
|
||||
const list = res?.data?.records ?? res?.records ?? []
|
||||
studentOptions.value = Array.isArray(list) ? list : []
|
||||
} catch (err) {
|
||||
studentOptions.value = []
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const onSubmit = async () => {
|
||||
if (!formRef.value) return
|
||||
await formRef.value.validate(async (valid: boolean) => {
|
||||
if (!valid) return
|
||||
if (form.sourceSutNo === form.targetStuNO) {
|
||||
useMessage().warning('请选择两名不同学生')
|
||||
return
|
||||
}
|
||||
submitting.value = true
|
||||
try {
|
||||
await exchangeRoom({
|
||||
sourceSutNo: form.sourceSutNo,
|
||||
targetStuNO: form.targetStuNO
|
||||
})
|
||||
useMessage().success('互换成功')
|
||||
visible.value = false
|
||||
emit('refresh')
|
||||
} catch (err: any) {
|
||||
console.error(err)
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
defineExpose({ openDialog })
|
||||
</script>
|
||||
@@ -41,9 +41,11 @@
|
||||
style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in bedNoList"
|
||||
:key="item"
|
||||
:label="item"
|
||||
:value="item">
|
||||
:key="item.bedNo"
|
||||
:value="item.bedNo">
|
||||
<span :class="{ 'bed-option-occupied': item.haveStudent }">
|
||||
{{ item.bedNo }}{{ item.haveStudent ? ' (有人)' : '' }}
|
||||
</span>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
@@ -95,7 +97,8 @@ const dataFormRef = ref()
|
||||
const visible = ref(false)
|
||||
const loading = ref(false)
|
||||
const roomList = ref<any[]>([])
|
||||
const bedNoList = ref<string[]>([])
|
||||
// 床位列表:支持 haveStudent 标记(true=有人,false=无人)
|
||||
const bedNoList = ref<Array<{ bedNo: string; haveStudent: boolean }>>([])
|
||||
|
||||
// 提交表单数据
|
||||
const form = reactive({
|
||||
@@ -119,26 +122,33 @@ const dataRules = {
|
||||
]
|
||||
}
|
||||
|
||||
// 房间号变化时获取床位号列表
|
||||
// 房间号变化时获取床位号列表(支持 haveStudent:true=有人,false=无人)
|
||||
const handleRoomChange = async (roomNo: string) => {
|
||||
if (!roomNo) {
|
||||
bedNoList.value = []
|
||||
form.bedNo = ''
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
const toBedItem = (item: any): { bedNo: string; haveStudent: boolean } => {
|
||||
if (typeof item === 'number' || typeof item === 'string') {
|
||||
return { bedNo: String(item), haveStudent: false }
|
||||
}
|
||||
return {
|
||||
bedNo: String(item?.bedNo ?? item?.value ?? item ?? ''),
|
||||
haveStudent: !!(item && item.haveStudent === true)
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fearchRoomStuNum(roomNo)
|
||||
if (res.data) {
|
||||
if (Array.isArray(res.data)) {
|
||||
bedNoList.value = res.data.map((item: any) => {
|
||||
if (typeof item === 'number' || typeof item === 'string') {
|
||||
return String(item)
|
||||
}
|
||||
return String(item.bedNo || item.value || item)
|
||||
})
|
||||
bedNoList.value = res.data.map(toBedItem).filter((b) => b.bedNo)
|
||||
} else if (res.data.bedNos && Array.isArray(res.data.bedNos)) {
|
||||
bedNoList.value = res.data.bedNos.map((item: any) => String(item))
|
||||
bedNoList.value = res.data.bedNos.map((item: any) =>
|
||||
toBedItem(typeof item === 'object' ? item : { bedNo: String(item), haveStudent: false })
|
||||
)
|
||||
} else {
|
||||
bedNoList.value = []
|
||||
}
|
||||
@@ -157,20 +167,20 @@ const openDialog = async (row: any) => {
|
||||
visible.value = true
|
||||
|
||||
// 重置表单数据
|
||||
nextTick(() => {
|
||||
dataFormRef.value?.resetFields()
|
||||
form.id = row.id || ''
|
||||
form.roomNo = row.roomNo || ''
|
||||
await nextTick()
|
||||
dataFormRef.value?.resetFields()
|
||||
form.id = row.id || ''
|
||||
form.roomNo = row.roomNo || ''
|
||||
form.bedNo = row.bedNo || ''
|
||||
form.stuNo = row.stuNo || ''
|
||||
form.isLeader = row.isLeader || '0'
|
||||
bedNoList.value = []
|
||||
|
||||
// 如果有房间号,先拉取床位列表再回填床位号(避免 handleRoomChange 清空 bedNo)
|
||||
if (form.roomNo) {
|
||||
await handleRoomChange(form.roomNo)
|
||||
form.bedNo = row.bedNo || ''
|
||||
form.stuNo = row.stuNo || ''
|
||||
form.isLeader = row.isLeader || '0'
|
||||
bedNoList.value = []
|
||||
|
||||
// 如果有房间号,获取床位号列表
|
||||
if (form.roomNo) {
|
||||
handleRoomChange(form.roomNo)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
@@ -193,7 +203,8 @@ const onSubmit = async () => {
|
||||
visible.value = false
|
||||
emit('refresh')
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg || '转宿失败')
|
||||
// 统一交给全局拦截器处理错误提示,避免在这里重复弹出一次
|
||||
console.error('转宿失败', err)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
@@ -223,3 +234,8 @@ defineExpose({
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.bed-option-occupied {
|
||||
color: var(--el-color-warning);
|
||||
}
|
||||
</style>
|
||||
|
||||
216
src/views/stuwork/dormsignrecord/form.vue
Normal file
216
src/views/stuwork/dormsignrecord/form.vue
Normal file
@@ -0,0 +1,216 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
title="新增宿舍点名"
|
||||
v-model="visible"
|
||||
:width="600"
|
||||
:close-on-click-modal="false"
|
||||
draggable>
|
||||
<el-form
|
||||
ref="dataFormRef"
|
||||
:model="form"
|
||||
:rules="dataRules"
|
||||
label-width="100px"
|
||||
v-loading="loading">
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item label="点名类型" prop="type">
|
||||
<el-select
|
||||
v-model="form.type"
|
||||
placeholder="请选择点名类型"
|
||||
clearable
|
||||
style="width: 100%">
|
||||
<el-option label="普通住宿点名" value="1" />
|
||||
<el-option label="留宿点名" value="2" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item label="楼号" prop="buildId">
|
||||
<el-select
|
||||
v-model="form.buildId"
|
||||
placeholder="请选择楼号"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in buildingList"
|
||||
:key="item.buildingNo"
|
||||
:label="item.buildingNo"
|
||||
:value="item.buildingNo" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item label="房间号" prop="roomNo">
|
||||
<el-input
|
||||
v-model="form.roomNo"
|
||||
placeholder="请输入房间号"
|
||||
style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item label="学生列表" prop="list">
|
||||
<el-input
|
||||
v-model="studentNosInput"
|
||||
type="textarea"
|
||||
:rows="5"
|
||||
placeholder="请输入学号,多个学号用逗号或换行分隔"
|
||||
style="width: 100%" />
|
||||
<div class="form-tip">提示:多个学号可用逗号或换行分隔</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="visible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="onSubmit" :disabled="loading">确 认</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="DormSignRecordFormDialog">
|
||||
import { ref, reactive, nextTick, computed, onMounted } from 'vue'
|
||||
import { useMessage } from '/@/hooks/message'
|
||||
import { addObj } from '/@/api/stuwork/dormsignrecord'
|
||||
import { getBuildingList } from '/@/api/stuwork/dormbuilding'
|
||||
|
||||
const emit = defineEmits(['refresh'])
|
||||
|
||||
// 定义变量内容
|
||||
const dataFormRef = ref()
|
||||
const visible = ref(false)
|
||||
const loading = ref(false)
|
||||
const buildingList = ref<any[]>([])
|
||||
|
||||
// 提交表单数据
|
||||
const form = reactive({
|
||||
type: '',
|
||||
buildId: '',
|
||||
roomNo: '',
|
||||
list: [] as Array<{ stuNo: string }>
|
||||
})
|
||||
|
||||
// 学生学号输入(用于显示和编辑)
|
||||
const studentNosInput = ref('')
|
||||
|
||||
// 定义校验规则
|
||||
const dataRules = {
|
||||
type: [
|
||||
{ required: true, message: '请选择点名类型', trigger: 'change' }
|
||||
],
|
||||
buildId: [
|
||||
{ required: true, message: '请选择楼号', trigger: 'change' }
|
||||
],
|
||||
roomNo: [
|
||||
{ required: true, message: '请输入房间号', trigger: 'blur' }
|
||||
],
|
||||
list: [
|
||||
{ required: true, message: '请输入至少一个学号', trigger: 'change' },
|
||||
{
|
||||
validator: (rule: any, value: any, callback: any) => {
|
||||
if (!value || value.length === 0) {
|
||||
callback(new Error('请输入至少一个学号'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
},
|
||||
trigger: 'change'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
// 打开弹窗
|
||||
const openDialog = async () => {
|
||||
visible.value = true
|
||||
|
||||
// 重置表单数据
|
||||
nextTick(() => {
|
||||
dataFormRef.value?.resetFields()
|
||||
form.type = ''
|
||||
form.buildId = ''
|
||||
form.roomNo = ''
|
||||
form.list = []
|
||||
studentNosInput.value = ''
|
||||
})
|
||||
}
|
||||
|
||||
// 解析学号输入(支持逗号和换行分隔)
|
||||
const parseStudentNos = (input: string): string[] => {
|
||||
if (!input || !input.trim()) return []
|
||||
return input
|
||||
.split(/[,\n]/)
|
||||
.map(s => s.trim())
|
||||
.filter(s => s.length > 0)
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
const onSubmit = async () => {
|
||||
if (!dataFormRef.value) return
|
||||
|
||||
// 解析学号列表
|
||||
const stuNos = parseStudentNos(studentNosInput.value)
|
||||
if (stuNos.length === 0) {
|
||||
useMessage().warning('请输入至少一个学号')
|
||||
return
|
||||
}
|
||||
|
||||
form.list = stuNos.map(stuNo => ({ stuNo }))
|
||||
|
||||
await dataFormRef.value.validate(async (valid: boolean) => {
|
||||
if (!valid) return
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
await addObj({
|
||||
type: form.type,
|
||||
buildId: form.buildId,
|
||||
roomNo: form.roomNo,
|
||||
list: form.list
|
||||
})
|
||||
useMessage().success('新增成功')
|
||||
visible.value = false
|
||||
emit('refresh')
|
||||
} catch (err: any) {
|
||||
if (!err?._messageShown) {
|
||||
useMessage().error(err?.msg || '新增失败')
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 获取楼号列表
|
||||
const getBuildingListData = async () => {
|
||||
try {
|
||||
const res = await getBuildingList()
|
||||
buildingList.value = res?.data && Array.isArray(res.data) ? res.data : []
|
||||
} catch (err) {
|
||||
buildingList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
getBuildingListData()
|
||||
})
|
||||
|
||||
// 暴露方法
|
||||
defineExpose({
|
||||
openDialog
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.mb20 {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-tip {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
margin-top: 4px;
|
||||
}
|
||||
</style>
|
||||
367
src/views/stuwork/dormsignrecord/index.vue
Normal file
367
src/views/stuwork/dormsignrecord/index.vue
Normal file
@@ -0,0 +1,367 @@
|
||||
<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="state.queryForm"
|
||||
ref="searchFormRef"
|
||||
:inline="true"
|
||||
@keyup.enter="getDataList"
|
||||
class="search-form">
|
||||
<el-form-item label="学院" prop="deptCode">
|
||||
<el-select
|
||||
v-model="state.queryForm.deptCode"
|
||||
placeholder="请选择学院"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 200px">
|
||||
<el-option
|
||||
v-for="item in deptList"
|
||||
:key="item.deptCode"
|
||||
:label="item.deptName"
|
||||
:value="item.deptCode">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="楼号" prop="buildNo">
|
||||
<el-select
|
||||
v-model="state.queryForm.buildNo"
|
||||
placeholder="请选择楼号"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 200px">
|
||||
<el-option
|
||||
v-for="item in buildingList"
|
||||
:key="item.buildingNo"
|
||||
:label="item.buildingNo"
|
||||
:value="item.buildingNo">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="房间号" prop="roomNo">
|
||||
<el-input
|
||||
v-model="state.queryForm.roomNo"
|
||||
placeholder="请输入房间号"
|
||||
clearable
|
||||
style="width: 200px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="学号" prop="stuNo">
|
||||
<el-input
|
||||
v-model="state.queryForm.stuNo"
|
||||
placeholder="请输入学号"
|
||||
clearable
|
||||
style="width: 200px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="点名类型" prop="type">
|
||||
<el-select
|
||||
v-model="state.queryForm.type"
|
||||
placeholder="请选择点名类型"
|
||||
clearable
|
||||
style="width: 200px">
|
||||
<el-option label="普通住宿点名" value="1" />
|
||||
<el-option label="留宿点名" value="2" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="考勤状态" prop="sign">
|
||||
<el-select
|
||||
v-model="state.queryForm.sign"
|
||||
placeholder="请选择考勤状态"
|
||||
clearable
|
||||
style="width: 200px">
|
||||
<el-option label="未到" :value="0" />
|
||||
<el-option label="已到" :value="1" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="考勤日期" prop="date">
|
||||
<el-date-picker
|
||||
v-model="state.queryForm.date"
|
||||
type="date"
|
||||
placeholder="请选择考勤日期"
|
||||
format="YYYY-MM-DD"
|
||||
value-format="YYYY-MM-DD"
|
||||
clearable
|
||||
style="width: 200px" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" plain icon="Search" @click="getDataList">查询</el-button>
|
||||
<el-button icon="Refresh" @click="handleReset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-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="FolderAdd"
|
||||
type="primary"
|
||||
@click="formDialogRef.openDialog()">
|
||||
新增点名
|
||||
</el-button>
|
||||
<el-button
|
||||
icon="RefreshLeft"
|
||||
type="warning"
|
||||
class="ml10"
|
||||
@click="handleInit">
|
||||
初始化
|
||||
</el-button>
|
||||
<right-toolbar
|
||||
v-model:showSearch="showSearch"
|
||||
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-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 #header>
|
||||
<el-icon><List /></el-icon>
|
||||
</template>
|
||||
<template #default="{ $index }">
|
||||
{{ $index + 1 + ((state.pagination?.current || 1) - 1) * (state.pagination?.size || 10) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<template v-for="col in visibleColumnsSorted" :key="col.prop || col.label">
|
||||
<el-table-column
|
||||
v-if="checkColumnVisible(col.prop || '') && col.prop !== '操作'"
|
||||
:prop="col.prop"
|
||||
:label="col.label"
|
||||
:width="col.width"
|
||||
:min-width="col.minWidth"
|
||||
:show-overflow-tooltip="col.showOverflowTooltip !== false"
|
||||
:align="col.align || 'center'">
|
||||
<template #header>
|
||||
<el-icon v-if="col.icon"><component :is="col.icon" /></el-icon>
|
||||
<span :style="{ marginLeft: col.icon ? '4px' : '0' }">{{ col.label }}</span>
|
||||
</template>
|
||||
<!-- 点名类型列特殊模板 -->
|
||||
<template v-if="col.prop === 'type'" #default="scope">
|
||||
<el-tag size="small" :type="scope.row.type === '1' ? 'primary' : 'success'" effect="plain">
|
||||
{{ scope.row.type === '1' ? '普通住宿点名' : '留宿点名' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
<!-- 考勤状态列特殊模板 -->
|
||||
<template v-else-if="col.prop === 'sign'" #default="scope">
|
||||
<el-tag size="small" :type="scope.row.sign === '1' || scope.row.sign === 1 ? 'success' : 'danger'" effect="plain">
|
||||
{{ scope.row.sign === '1' || scope.row.sign === 1 ? '已到' : '未到' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
<!-- 是否扫过脸列特殊模板 -->
|
||||
<template v-else-if="col.prop === 'isFace'" #default="scope">
|
||||
<el-tag size="small" :type="scope.row.isFace === '1' || scope.row.isFace === 1 ? 'success' : 'info'" effect="plain">
|
||||
{{ scope.row.isFace === '1' || scope.row.isFace === 1 ? '是' : '否' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
<!-- 是否请假列特殊模板 -->
|
||||
<template v-else-if="col.prop === 'isApply'" #default="scope">
|
||||
<el-tag size="small" :type="scope.row.isApply === '1' || scope.row.isApply === 1 ? 'warning' : 'info'" effect="plain">
|
||||
{{ scope.row.isApply === '1' || scope.row.isApply === 1 ? '是' : '否' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="pagination-wrapper">
|
||||
<pagination
|
||||
@size-change="sizeChangeHandle"
|
||||
@current-change="currentChangeHandle"
|
||||
v-bind="state.pagination" />
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<!-- 新增点名弹窗 -->
|
||||
<form-dialog ref="formDialogRef" @refresh="getDataList" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="DormSignRecord">
|
||||
import { reactive, ref, onMounted } from 'vue'
|
||||
import { BasicTableProps, useTable } from "/@/hooks/table";
|
||||
import { fetchList, initDormStuInfoForAttendance } from "/@/api/stuwork/dormsignrecord";
|
||||
import { useMessage, useMessageBox } from "/@/hooks/message";
|
||||
import { getDeptList } from "/@/api/basic/basicclass";
|
||||
import { getBuildingList } from "/@/api/stuwork/dormbuilding";
|
||||
import FormDialog from './form.vue'
|
||||
import { Search, Document, List, Menu, RefreshLeft, OfficeBuilding, Grid, User, CreditCard, House, Check, Calendar, UserFilled } from '@element-plus/icons-vue'
|
||||
import RightToolbar from '/@/components/RightToolbar/index.vue'
|
||||
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
|
||||
import { useTableColumnControl } from '/@/hooks/tableColumn'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
const route = useRoute()
|
||||
const formDialogRef = ref()
|
||||
const showSearch = ref(true)
|
||||
const searchFormRef = ref()
|
||||
const columnControlRef = ref<any>()
|
||||
const deptList = ref<any[]>([])
|
||||
const buildingList = ref<any[]>([])
|
||||
|
||||
// 表格列配置
|
||||
const tableColumns = [
|
||||
{ prop: 'deptName', label: '学院', minWidth: 120, icon: OfficeBuilding },
|
||||
{ prop: 'className', label: '班级', minWidth: 120, icon: Grid },
|
||||
{ prop: 'stuName', label: '姓名', width: 100, icon: User },
|
||||
{ prop: 'stuNo', label: '学号', width: 120, icon: CreditCard },
|
||||
{ prop: 'roomNo', label: '房间号', width: 100, icon: House },
|
||||
{ prop: 'buildId', label: '楼号', width: 80, icon: OfficeBuilding },
|
||||
{ prop: 'type', label: '点名类型', width: 120, icon: Document },
|
||||
{ prop: 'sign', label: '考勤状态', width: 100, icon: Check },
|
||||
{ prop: 'date', label: '考勤日期', width: 120, icon: Calendar },
|
||||
{ prop: 'isFace', label: '是否扫过脸', width: 100, icon: UserFilled },
|
||||
{ prop: 'isApply', label: '是否请假', width: 100, icon: Document }
|
||||
]
|
||||
|
||||
// 使用表格列控制
|
||||
const {
|
||||
visibleColumns,
|
||||
visibleColumnsSorted,
|
||||
checkColumnVisible,
|
||||
handleColumnChange,
|
||||
handleColumnOrderChange,
|
||||
loadSavedConfig
|
||||
} = useTableColumnControl(tableColumns, { storageKey: route.path })
|
||||
|
||||
// 立即加载配置
|
||||
loadSavedConfig()
|
||||
|
||||
// 配置 useTable
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
queryForm: {
|
||||
deptCode: '',
|
||||
buildNo: '',
|
||||
roomNo: '',
|
||||
stuNo: '',
|
||||
type: '',
|
||||
sign: '',
|
||||
date: ''
|
||||
},
|
||||
pageList: async (params: any) => {
|
||||
const res = await fetchList(params)
|
||||
const data = res?.data
|
||||
return {
|
||||
data: {
|
||||
records: data?.records ?? [],
|
||||
total: data?.total ?? 0
|
||||
}
|
||||
}
|
||||
},
|
||||
props: {
|
||||
item: 'records',
|
||||
totalCount: 'total'
|
||||
},
|
||||
createdIsNeed: true,
|
||||
isPage: true
|
||||
})
|
||||
|
||||
// table hook
|
||||
const {
|
||||
getDataList,
|
||||
currentChangeHandle,
|
||||
sizeChangeHandle,
|
||||
tableStyle
|
||||
} = useTable(state)
|
||||
|
||||
// 重置
|
||||
const handleReset = () => {
|
||||
searchFormRef.value?.resetFields()
|
||||
state.queryForm.deptCode = ''
|
||||
state.queryForm.buildNo = ''
|
||||
state.queryForm.roomNo = ''
|
||||
state.queryForm.stuNo = ''
|
||||
state.queryForm.type = ''
|
||||
state.queryForm.sign = ''
|
||||
state.queryForm.date = ''
|
||||
getDataList()
|
||||
}
|
||||
|
||||
// 初始化
|
||||
const handleInit = async () => {
|
||||
const { confirm } = useMessageBox()
|
||||
try {
|
||||
await confirm('确定要初始化宿舍学生信息用于考勤吗?')
|
||||
await initDormStuInfoForAttendance()
|
||||
useMessage().success('初始化成功')
|
||||
getDataList()
|
||||
} catch (err: any) {
|
||||
if (err !== 'cancel') {
|
||||
if (!err?._messageShown) {
|
||||
useMessage().error(err?.msg || '初始化失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 获取学院列表
|
||||
const getDeptListData = async () => {
|
||||
try {
|
||||
const res = await getDeptList()
|
||||
deptList.value = res?.data && Array.isArray(res.data) ? res.data : []
|
||||
} catch (err) {
|
||||
deptList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
// 获取楼号列表
|
||||
const getBuildingListData = async () => {
|
||||
try {
|
||||
const res = await getBuildingList()
|
||||
buildingList.value = res?.data && Array.isArray(res.data) ? res.data : []
|
||||
} catch (err) {
|
||||
buildingList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
getDeptListData()
|
||||
getBuildingListData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '/@/assets/styles/modern-page.scss';
|
||||
</style>
|
||||
@@ -184,11 +184,24 @@ const TimeRuleTableComponent = defineComponent({
|
||||
default: ({ row }: any) => {
|
||||
const timeList = getTimeList(row)
|
||||
return h('div', { class: 'time-slots-container' },
|
||||
timeList.map((timeSlot: any, index: number) =>
|
||||
h('div', { key: index, class: 'time-slot-item' }, [
|
||||
timeList.map((timeSlot: any, index: number) => {
|
||||
// 通过数组索引更新,确保响应式
|
||||
const updateStartTime = (val: string) => {
|
||||
const list = getTimeList(row)
|
||||
if (list[index]) {
|
||||
list[index].startTime = val || ''
|
||||
}
|
||||
}
|
||||
const updateEndTime = (val: string) => {
|
||||
const list = getTimeList(row)
|
||||
if (list[index]) {
|
||||
list[index].endTime = val || ''
|
||||
}
|
||||
}
|
||||
return h('div', { key: index, class: 'time-slot-item' }, [
|
||||
h(ElTimePicker, {
|
||||
modelValue: timeSlot.startTime,
|
||||
'onUpdate:modelValue': (val: string) => { timeSlot.startTime = val },
|
||||
modelValue: timeSlot.startTime || '',
|
||||
'onUpdate:modelValue': updateStartTime,
|
||||
format: 'HH:mm',
|
||||
valueFormat: 'HH:mm',
|
||||
placeholder: '开始时间',
|
||||
@@ -196,8 +209,8 @@ const TimeRuleTableComponent = defineComponent({
|
||||
}),
|
||||
h('span', { style: { margin: '0 10px' } }, '至'),
|
||||
h(ElTimePicker, {
|
||||
modelValue: timeSlot.endTime,
|
||||
'onUpdate:modelValue': (val: string) => { timeSlot.endTime = val },
|
||||
modelValue: timeSlot.endTime || '',
|
||||
'onUpdate:modelValue': updateEndTime,
|
||||
format: 'HH:mm',
|
||||
valueFormat: 'HH:mm',
|
||||
placeholder: '结束时间',
|
||||
@@ -213,7 +226,7 @@ const TimeRuleTableComponent = defineComponent({
|
||||
style: { marginLeft: '10px' }
|
||||
})
|
||||
])
|
||||
)
|
||||
})
|
||||
)
|
||||
}
|
||||
}),
|
||||
|
||||
144
src/views/stuwork/filemanager/folder.vue
Normal file
144
src/views/stuwork/filemanager/folder.vue
Normal file
@@ -0,0 +1,144 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
:title="form.id ? '编辑文件夹' : '新建文件夹'"
|
||||
v-model="visible"
|
||||
:width="500"
|
||||
:close-on-click-modal="false"
|
||||
draggable>
|
||||
<el-form
|
||||
ref="dataFormRef"
|
||||
:model="form"
|
||||
:rules="dataRules"
|
||||
label-width="100px"
|
||||
v-loading="loading">
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item label="文件夹名称" prop="classification">
|
||||
<el-input
|
||||
v-model="form.classification"
|
||||
placeholder="请输入文件夹名称"
|
||||
style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="visible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="onSubmit" :disabled="loading">确 认</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="FileManagerFolderDialog">
|
||||
import { ref, reactive, nextTick } from 'vue'
|
||||
import { useMessage } from '/@/hooks/message'
|
||||
import { editFile, getDetail } from '/@/api/stuwork/filemanager'
|
||||
|
||||
const emit = defineEmits(['refresh'])
|
||||
|
||||
// 定义变量内容
|
||||
const dataFormRef = ref()
|
||||
const visible = ref(false)
|
||||
const loading = ref(false)
|
||||
const operType = ref('add') // add 或 edit
|
||||
|
||||
// 提交表单数据
|
||||
const form = reactive({
|
||||
id: '',
|
||||
classification: '',
|
||||
parentId: null as string | null
|
||||
})
|
||||
|
||||
// 定义校验规则
|
||||
const dataRules = {
|
||||
classification: [
|
||||
{ required: true, message: '请输入文件夹名称', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
|
||||
// 打开弹窗
|
||||
const openDialog = async (type: string = 'add', row?: any) => {
|
||||
visible.value = true
|
||||
operType.value = type
|
||||
|
||||
// 重置表单数据
|
||||
nextTick(() => {
|
||||
dataFormRef.value?.resetFields()
|
||||
form.id = ''
|
||||
form.classification = ''
|
||||
form.parentId = row?.parentId ?? null
|
||||
|
||||
// 编辑时填充数据
|
||||
if (type === 'edit' && row) {
|
||||
form.id = row.id
|
||||
form.classification = row.classification || ''
|
||||
form.parentId = row.parentId || null
|
||||
|
||||
// 如果需要获取详情
|
||||
if (row.id && !row.classification) {
|
||||
loading.value = true
|
||||
getDetail(row.id).then((res: any) => {
|
||||
if (res.data) {
|
||||
form.classification = res.data.classification || ''
|
||||
form.parentId = res.data.parentId || form.parentId
|
||||
}
|
||||
}).finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 提交表单(新增文件夹需要通过新增文件接口,但只传 classification 和 parentId)
|
||||
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') {
|
||||
// 新增文件夹:使用 addObj,但只传 classification 和 parentId
|
||||
const { addObj } = await import('/@/api/stuwork/filemanager')
|
||||
await addObj({
|
||||
classification: form.classification,
|
||||
parentId: form.parentId || '-1',
|
||||
level: '0' // 文件夹层级为 0
|
||||
})
|
||||
useMessage().success('新建成功')
|
||||
} else {
|
||||
// 编辑文件夹:使用 editFile 接口
|
||||
await editFile({
|
||||
id: form.id,
|
||||
parentId: form.parentId || '-1',
|
||||
classification: form.classification
|
||||
})
|
||||
useMessage().success('编辑成功')
|
||||
}
|
||||
visible.value = false
|
||||
emit('refresh')
|
||||
} catch (err: any) {
|
||||
if (!err?._messageShown) {
|
||||
useMessage().error(err?.msg || (operType.value === 'add' ? '新建失败' : '编辑失败'))
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 暴露方法
|
||||
defineExpose({
|
||||
openDialog
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.mb20 {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
</style>
|
||||
181
src/views/stuwork/filemanager/form.vue
Normal file
181
src/views/stuwork/filemanager/form.vue
Normal file
@@ -0,0 +1,181 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
:title="form.id ? '编辑文件' : '上传文件'"
|
||||
v-model="visible"
|
||||
:width="600"
|
||||
:close-on-click-modal="false"
|
||||
draggable>
|
||||
<el-form
|
||||
ref="dataFormRef"
|
||||
:model="form"
|
||||
:rules="dataRules"
|
||||
label-width="100px"
|
||||
v-loading="loading">
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item label="文件名称" prop="fileName">
|
||||
<el-input
|
||||
v-model="form.fileName"
|
||||
placeholder="请输入文件名称"
|
||||
style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item label="文件上传" prop="fileUrl">
|
||||
<Upload
|
||||
v-model="form.fileUrl"
|
||||
:limit="1"
|
||||
uploadFileUrl="/stuwork/file/upload"
|
||||
type="default" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item label="备注" prop="remarks">
|
||||
<el-input
|
||||
v-model="form.remarks"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="请输入备注"
|
||||
:maxlength="250"
|
||||
show-word-limit
|
||||
style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="visible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="onSubmit" :disabled="loading">确 认</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="FileManagerFormDialog">
|
||||
import { ref, reactive, nextTick } from 'vue'
|
||||
import { useMessage } from '/@/hooks/message'
|
||||
import { addObj, editObj, getDetail } from '/@/api/stuwork/filemanager'
|
||||
import Upload from '/@/components/Upload/index.vue'
|
||||
|
||||
const emit = defineEmits(['refresh'])
|
||||
|
||||
// 定义变量内容
|
||||
const dataFormRef = ref()
|
||||
const visible = ref(false)
|
||||
const loading = ref(false)
|
||||
const operType = ref('add') // add 或 edit
|
||||
|
||||
// 提交表单数据
|
||||
const form = reactive({
|
||||
id: '',
|
||||
fileName: '',
|
||||
fileUrl: '',
|
||||
remarks: '',
|
||||
parentId: null as string | null,
|
||||
level: '1'
|
||||
})
|
||||
|
||||
// 定义校验规则
|
||||
const dataRules = {
|
||||
fileName: [
|
||||
{ required: true, message: '请输入文件名称', trigger: 'blur' }
|
||||
],
|
||||
fileUrl: [
|
||||
{ required: true, message: '请上传文件', trigger: 'change' }
|
||||
]
|
||||
}
|
||||
|
||||
// 打开弹窗
|
||||
const openDialog = async (type: string = 'add', row?: any) => {
|
||||
visible.value = true
|
||||
operType.value = type
|
||||
|
||||
// 重置表单数据
|
||||
nextTick(() => {
|
||||
dataFormRef.value?.resetFields()
|
||||
form.id = ''
|
||||
form.fileName = ''
|
||||
form.fileUrl = ''
|
||||
form.remarks = ''
|
||||
form.parentId = row?.parentId ?? null
|
||||
form.level = row?.level || '1'
|
||||
|
||||
// 编辑时填充数据
|
||||
if (type === 'edit' && row) {
|
||||
form.id = row.id
|
||||
form.fileName = row.fileName || ''
|
||||
form.fileUrl = row.fileUrl || ''
|
||||
form.remarks = row.remarks || ''
|
||||
form.parentId = row.parentId || null
|
||||
form.level = row.level || '1'
|
||||
|
||||
// 如果需要获取详情
|
||||
if (row.id && !row.fileUrl) {
|
||||
loading.value = true
|
||||
getDetail(row.id).then((res: any) => {
|
||||
if (res.data) {
|
||||
form.fileName = res.data.fileName || form.fileName
|
||||
form.fileUrl = res.data.fileUrl || ''
|
||||
form.remarks = res.data.remarks || ''
|
||||
form.parentId = res.data.parentId || form.parentId
|
||||
form.level = res.data.level || form.level
|
||||
}
|
||||
}).finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
const onSubmit = async () => {
|
||||
if (!dataFormRef.value) return
|
||||
|
||||
await dataFormRef.value.validate(async (valid: boolean) => {
|
||||
if (!valid) return
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
const submitData = {
|
||||
fileName: form.fileName,
|
||||
fileUrl: form.fileUrl,
|
||||
remarks: form.remarks,
|
||||
parentId: form.parentId || '-1',
|
||||
level: form.level
|
||||
}
|
||||
|
||||
if (operType.value === 'add') {
|
||||
await addObj(submitData)
|
||||
useMessage().success('新增成功')
|
||||
} else {
|
||||
await editObj({
|
||||
id: form.id,
|
||||
...submitData
|
||||
})
|
||||
useMessage().success('编辑成功')
|
||||
}
|
||||
visible.value = false
|
||||
emit('refresh')
|
||||
} catch (err: any) {
|
||||
if (!err?._messageShown) {
|
||||
useMessage().error(err?.msg || (operType.value === 'add' ? '新增失败' : '编辑失败'))
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 暴露方法
|
||||
defineExpose({
|
||||
openDialog
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.mb20 {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
</style>
|
||||
345
src/views/stuwork/filemanager/index.vue
Normal file
345
src/views/stuwork/filemanager/index.vue
Normal file
@@ -0,0 +1,345 @@
|
||||
<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="handleAddFolder">
|
||||
新建文件夹
|
||||
</el-button>
|
||||
<el-button
|
||||
icon="FolderAdd"
|
||||
type="success"
|
||||
class="ml10"
|
||||
@click="handleAddFile">
|
||||
上传文件
|
||||
</el-button>
|
||||
<el-button
|
||||
icon="Refresh"
|
||||
class="ml10"
|
||||
@click="getDataList">
|
||||
刷新
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 面包屑导航 -->
|
||||
<div class="breadcrumb-wrapper mb16" v-if="breadcrumbList.length > 0">
|
||||
<el-breadcrumb separator="/">
|
||||
<el-breadcrumb-item>
|
||||
<el-button link @click="handleBreadcrumbClick(null)">
|
||||
<el-icon><Folder /></el-icon>
|
||||
根目录
|
||||
</el-button>
|
||||
</el-breadcrumb-item>
|
||||
<el-breadcrumb-item v-for="(item, index) in breadcrumbList" :key="item.id">
|
||||
<el-button
|
||||
link
|
||||
@click="handleBreadcrumbClick(item)"
|
||||
:disabled="index === breadcrumbList.length - 1">
|
||||
{{ item.classification || item.fileName }}
|
||||
</el-button>
|
||||
</el-breadcrumb-item>
|
||||
</el-breadcrumb>
|
||||
</div>
|
||||
|
||||
<!-- 表格 -->
|
||||
<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 #header>
|
||||
<el-icon><List /></el-icon>
|
||||
</template>
|
||||
<template #default="{ $index }">
|
||||
{{ $index + 1 }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="classification" label="文件夹名称" min-width="200" show-overflow-tooltip>
|
||||
<template #header>
|
||||
<el-icon><Folder /></el-icon>
|
||||
<span style="margin-left: 4px">文件夹名称</span>
|
||||
</template>
|
||||
<template #default="scope">
|
||||
<span v-if="scope.row.classification">{{ scope.row.classification }}</span>
|
||||
<span v-else class="text-gray-400">-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="fileName" label="文件名称" min-width="200" show-overflow-tooltip>
|
||||
<template #header>
|
||||
<el-icon><Document /></el-icon>
|
||||
<span style="margin-left: 4px">文件名称</span>
|
||||
</template>
|
||||
<template #default="scope">
|
||||
<span v-if="scope.row.fileName">{{ scope.row.fileName }}</span>
|
||||
<span v-else class="text-gray-400">-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="level" label="层级" width="80" align="center">
|
||||
<template #header>
|
||||
<el-icon><Grid /></el-icon>
|
||||
<span style="margin-left: 4px">层级</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="remarks" label="备注" min-width="150" show-overflow-tooltip>
|
||||
<template #header>
|
||||
<el-icon><EditPen /></el-icon>
|
||||
<span style="margin-left: 4px">备注</span>
|
||||
</template>
|
||||
<template #default="scope">
|
||||
<span v-if="scope.row.remarks">{{ scope.row.remarks }}</span>
|
||||
<span v-else class="text-gray-400">-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="创建时间" width="180" align="center">
|
||||
<template #header>
|
||||
<el-icon><Clock /></el-icon>
|
||||
<span style="margin-left: 4px">创建时间</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="250" align="center" fixed="right">
|
||||
<template #header>
|
||||
<el-icon><Setting /></el-icon>
|
||||
<span style="margin-left: 4px">操作</span>
|
||||
</template>
|
||||
<template #default="scope">
|
||||
<!-- 文件夹:进入、编辑、删除 -->
|
||||
<template v-if="scope.row.classification">
|
||||
<el-button
|
||||
icon="FolderOpened"
|
||||
link
|
||||
type="primary"
|
||||
@click="handleEnterFolder(scope.row)">
|
||||
进入
|
||||
</el-button>
|
||||
<el-button
|
||||
icon="Edit"
|
||||
link
|
||||
type="primary"
|
||||
@click="handleEditFolder(scope.row)">
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
icon="Delete"
|
||||
link
|
||||
type="danger"
|
||||
@click="handleDelete(scope.row)">
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
<!-- 文件:下载、编辑、删除 -->
|
||||
<template v-else>
|
||||
<el-button
|
||||
icon="Download"
|
||||
link
|
||||
type="primary"
|
||||
@click="handleDownload(scope.row)">
|
||||
下载
|
||||
</el-button>
|
||||
<el-button
|
||||
icon="Edit"
|
||||
link
|
||||
type="primary"
|
||||
@click="handleEditFile(scope.row)">
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
icon="Delete"
|
||||
link
|
||||
type="danger"
|
||||
@click="handleDelete(scope.row)">
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<!-- 新增/编辑文件弹窗 -->
|
||||
<form-dialog ref="formDialogRef" @refresh="getDataList" />
|
||||
|
||||
<!-- 新增/编辑文件夹弹窗 -->
|
||||
<folder-dialog ref="folderDialogRef" @refresh="getDataList" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="FileManager">
|
||||
import { reactive, ref, onMounted, computed } from 'vue'
|
||||
import { BasicTableProps, useTable } from "/@/hooks/table";
|
||||
import { fetchList, delObj } from "/@/api/stuwork/filemanager";
|
||||
import { useMessage, useMessageBox } from "/@/hooks/message";
|
||||
import FormDialog from './form.vue'
|
||||
import FolderDialog from './folder.vue'
|
||||
import { List, Document, Folder, Grid, EditPen, Clock, Setting, FolderOpened, Download } from '@element-plus/icons-vue'
|
||||
|
||||
// 定义变量内容
|
||||
const formDialogRef = ref()
|
||||
const folderDialogRef = ref()
|
||||
const currentParentId = ref<string | null>(null)
|
||||
const breadcrumbList = ref<any[]>([])
|
||||
|
||||
// 配置 useTable(接口返回数组,非分页结构)
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
queryForm: {
|
||||
parentId: null as string | null
|
||||
},
|
||||
pageList: async (params: any) => {
|
||||
const res = await fetchList(params)
|
||||
// 数据链是 data.data.records,需要正确解析
|
||||
let list: any[] = []
|
||||
if (res?.data) {
|
||||
// 如果 res.data 是数组,直接使用
|
||||
if (Array.isArray(res.data)) {
|
||||
list = res.data
|
||||
}
|
||||
// 如果 res.data.data 存在且是对象,尝试取 records
|
||||
else if (res.data.data && res.data.data.records && Array.isArray(res.data.data.records)) {
|
||||
list = res.data.data.records
|
||||
}
|
||||
// 如果 res.data.records 存在且是数组,直接使用
|
||||
else if (res.data.records && Array.isArray(res.data.records)) {
|
||||
list = res.data.records
|
||||
}
|
||||
}
|
||||
return {
|
||||
data: list // isPage: false 时,直接返回数组
|
||||
}
|
||||
},
|
||||
props: {
|
||||
item: 'records',
|
||||
totalCount: 'total'
|
||||
},
|
||||
createdIsNeed: true,
|
||||
isPage: false // 不分页
|
||||
})
|
||||
|
||||
// table hook
|
||||
const {
|
||||
getDataList,
|
||||
tableStyle
|
||||
} = useTable(state)
|
||||
|
||||
// 进入文件夹
|
||||
const handleEnterFolder = (row: any) => {
|
||||
currentParentId.value = row.id
|
||||
breadcrumbList.value.push(row)
|
||||
state.queryForm.parentId = row.id
|
||||
getDataList()
|
||||
}
|
||||
|
||||
// 面包屑点击
|
||||
const handleBreadcrumbClick = (item: any | null) => {
|
||||
if (item === null) {
|
||||
// 返回根目录
|
||||
currentParentId.value = null
|
||||
breadcrumbList.value = []
|
||||
state.queryForm.parentId = null
|
||||
} else {
|
||||
// 返回指定目录
|
||||
const index = breadcrumbList.value.findIndex(b => b.id === item.id)
|
||||
if (index >= 0) {
|
||||
breadcrumbList.value = breadcrumbList.value.slice(0, index + 1)
|
||||
currentParentId.value = item.id
|
||||
state.queryForm.parentId = item.id
|
||||
}
|
||||
}
|
||||
getDataList()
|
||||
}
|
||||
|
||||
// 新增文件夹
|
||||
const handleAddFolder = () => {
|
||||
folderDialogRef.value?.openDialog('add', { parentId: currentParentId.value })
|
||||
}
|
||||
|
||||
// 新增文件
|
||||
const handleAddFile = () => {
|
||||
formDialogRef.value?.openDialog('add', { parentId: currentParentId.value })
|
||||
}
|
||||
|
||||
// 编辑文件夹
|
||||
const handleEditFolder = (row: any) => {
|
||||
folderDialogRef.value?.openDialog('edit', row)
|
||||
}
|
||||
|
||||
// 编辑文件
|
||||
const handleEditFile = (row: any) => {
|
||||
formDialogRef.value?.openDialog('edit', row)
|
||||
}
|
||||
|
||||
// 下载文件
|
||||
const handleDownload = (row: any) => {
|
||||
if (!row.fileUrl) {
|
||||
useMessage().warning('文件地址无效')
|
||||
return
|
||||
}
|
||||
// fileUrl 可能是完整 URL 或相对路径
|
||||
if (row.fileUrl.startsWith('http://') || row.fileUrl.startsWith('https://')) {
|
||||
window.open(row.fileUrl, '_blank')
|
||||
} else {
|
||||
// 相对路径,需要拼接 baseURL
|
||||
const baseURL = import.meta.env.VITE_API_URL || ''
|
||||
const url = baseURL + (row.fileUrl.startsWith('/') ? row.fileUrl : '/' + row.fileUrl)
|
||||
window.open(url, '_blank')
|
||||
}
|
||||
}
|
||||
|
||||
// 删除
|
||||
const handleDelete = async (row: any) => {
|
||||
const { confirm } = useMessageBox()
|
||||
try {
|
||||
const name = row.classification || row.fileName || '该项'
|
||||
await confirm(`确定要删除${name}吗?`)
|
||||
await delObj([row.id])
|
||||
useMessage().success('删除成功')
|
||||
getDataList()
|
||||
} catch (err: any) {
|
||||
if (err !== 'cancel') {
|
||||
useMessage().error(err?.msg || '删除失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
getDataList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '/@/assets/styles/modern-page.scss';
|
||||
|
||||
.breadcrumb-wrapper {
|
||||
padding: 12px 16px;
|
||||
background: #f5f7fa;
|
||||
border-radius: 4px;
|
||||
|
||||
:deep(.el-breadcrumb) {
|
||||
.el-breadcrumb__item {
|
||||
.el-button {
|
||||
padding: 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mb16 {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
</style>
|
||||
227
src/views/stuwork/gradustu/analyse.vue
Normal file
227
src/views/stuwork/gradustu/analyse.vue
Normal file
@@ -0,0 +1,227 @@
|
||||
<template>
|
||||
<div class="modern-page-container">
|
||||
<div class="page-wrapper">
|
||||
<!-- 筛选 -->
|
||||
<el-card 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-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>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="loadStatistics">查询统计</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<!-- 汇总卡片 -->
|
||||
<el-row :gutter="16" class="summary-row">
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" class="stat-card">
|
||||
<div class="stat-label">毕业生总数</div>
|
||||
<div class="stat-value">{{ summary.total }}</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-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-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-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 按学院统计表格 -->
|
||||
<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>
|
||||
</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">
|
||||
<template #default="scope">
|
||||
<span :class="completionRateClass(scope.row.completionRate)">
|
||||
{{ scope.row.completionRate }}
|
||||
</span>
|
||||
</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 { fetchListForAnalyse } from '/@/api/stuwork/gradustu'
|
||||
import { Search, Document } from '@element-plus/icons-vue'
|
||||
|
||||
const queryForm = reactive({
|
||||
graduYear: ''
|
||||
})
|
||||
|
||||
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 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 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++
|
||||
})
|
||||
return Object.values(map).map((row) => ({
|
||||
...row,
|
||||
completionRate: row.total > 0 ? ((row.confirmed / row.total) * 100).toFixed(1) + '%' : '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('请选择毕业年份')
|
||||
return
|
||||
}
|
||||
loading.value = true
|
||||
try {
|
||||
const list = await fetchListForAnalyse(queryForm.graduYear)
|
||||
rawList.value = list
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg || '获取数据失败')
|
||||
rawList.value = []
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
queryForm.graduYear = String(new Date().getFullYear())
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '/@/assets/styles/modern-page.scss';
|
||||
|
||||
.summary-row {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.stat-card {
|
||||
text-align: center;
|
||||
.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);
|
||||
}
|
||||
}
|
||||
.rate-high {
|
||||
color: var(--el-color-success);
|
||||
font-weight: 500;
|
||||
}
|
||||
.rate-mid {
|
||||
color: var(--el-color-warning);
|
||||
}
|
||||
.rate-low {
|
||||
color: var(--el-color-danger);
|
||||
}
|
||||
</style>
|
||||
342
src/views/stuwork/gradustu/index.vue
Normal file
342
src/views/stuwork/gradustu/index.vue
Normal file
@@ -0,0 +1,342 @@
|
||||
<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="graduYear">
|
||||
<el-select
|
||||
v-model="searchForm.graduYear"
|
||||
placeholder="请选择毕业年份"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 140px">
|
||||
<el-option
|
||||
v-for="y in graduYearOptions"
|
||||
:key="y"
|
||||
:label="y + '年'"
|
||||
:value="y" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="毕业状态" prop="status">
|
||||
<el-select
|
||||
v-model="searchForm.status"
|
||||
placeholder="请选择"
|
||||
clearable
|
||||
style="width: 140px">
|
||||
<el-option label="待确认" value="0" />
|
||||
<el-option label="确认毕业" value="1" />
|
||||
<el-option label="不可毕业" value="-1" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="毕业类型" prop="type">
|
||||
<el-select
|
||||
v-model="searchForm.type"
|
||||
placeholder="请选择"
|
||||
clearable
|
||||
style="width: 120px">
|
||||
<el-option label="段段清" value="1" />
|
||||
<el-option label="正常毕业" value="2" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="学号" prop="stuNo">
|
||||
<el-input
|
||||
v-model="searchForm.stuNo"
|
||||
placeholder="请输入学号"
|
||||
clearable
|
||||
style="width: 140px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="姓名" prop="realName">
|
||||
<el-input
|
||||
v-model="searchForm.realName"
|
||||
placeholder="请输入姓名"
|
||||
clearable
|
||||
style="width: 120px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="学院" prop="deptCode">
|
||||
<el-select
|
||||
v-model="searchForm.deptCode"
|
||||
placeholder="请选择学院"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 160px">
|
||||
<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="班号" prop="classNo">
|
||||
<el-input
|
||||
v-model="searchForm.classNo"
|
||||
placeholder="请输入班号"
|
||||
clearable
|
||||
style="width: 120px" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<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-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"
|
||||
:loading="makeLoading"
|
||||
@click="handleMakeGraduStu">
|
||||
生成毕业生信息
|
||||
</el-button>
|
||||
<right-toolbar
|
||||
v-model:showSearch="showSearch"
|
||||
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-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 #header>
|
||||
<el-icon><List /></el-icon>
|
||||
</template>
|
||||
<template #default="{ $index }">
|
||||
{{ $index + 1 + ((state.pagination?.current || 1) - 1) * (state.pagination?.size || 10) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<template v-for="col in visibleColumnsSorted" :key="col.prop || col.label">
|
||||
<el-table-column
|
||||
v-if="checkColumnVisible(col.prop || '') && col.prop !== '操作'"
|
||||
:prop="col.prop"
|
||||
:label="col.label"
|
||||
:min-width="col.minWidth"
|
||||
:width="col.width"
|
||||
show-overflow-tooltip
|
||||
align="center">
|
||||
<template #header>
|
||||
<el-icon v-if="col.icon"><component :is="col.icon" /></el-icon>
|
||||
<span :style="{ marginLeft: col.icon ? '4px' : '0' }">{{ col.label }}</span>
|
||||
</template>
|
||||
<template v-if="col.prop === 'status'" #default="scope">
|
||||
<el-tag :type="statusTagType(scope.row.status)" size="small">
|
||||
{{ formatStatus(scope.row.status) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
<template v-else-if="col.prop === 'type'" #default="scope">
|
||||
{{ scope.row.type === '1' ? '段段清' : scope.row.type === '2' ? '正常毕业' : scope.row.type || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
<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="Gradustu">
|
||||
import { reactive, ref, computed, onMounted } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { BasicTableProps, useTable } from '/@/hooks/table'
|
||||
import { fetchList, makeGraduStu } from '/@/api/stuwork/gradustu'
|
||||
import { useMessage, useMessageBox } from '/@/hooks/message'
|
||||
import { getDeptList } from '/@/api/basic/basicclass'
|
||||
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
|
||||
import {
|
||||
List,
|
||||
Calendar,
|
||||
UserFilled,
|
||||
Document,
|
||||
Setting,
|
||||
Menu,
|
||||
Search,
|
||||
Grid
|
||||
} from '@element-plus/icons-vue'
|
||||
import { useTableColumnControl } from '/@/hooks/tableColumn'
|
||||
|
||||
const route = useRoute()
|
||||
const searchFormRef = ref()
|
||||
const columnControlRef = ref<any>()
|
||||
const showSearch = ref(true)
|
||||
const makeLoading = ref(false)
|
||||
const deptList = ref<any[]>([])
|
||||
|
||||
// 毕业年份:当前年前后各 5 年
|
||||
const graduYearOptions = computed(() => {
|
||||
const y = new Date().getFullYear()
|
||||
return Array.from({ length: 11 }, (_, i) => y - 5 + i)
|
||||
})
|
||||
|
||||
// 表格列配置(与其它页面一致,icon 写在列上)
|
||||
const tableColumns = [
|
||||
{ prop: 'stuNo', label: '学号', icon: UserFilled },
|
||||
{ prop: 'realName', label: '姓名', icon: UserFilled },
|
||||
{ prop: 'classNo', label: '班号', icon: Grid },
|
||||
{ prop: 'majorName', label: '专业名称', icon: Document, minWidth: 140 },
|
||||
{ prop: 'graduYear', label: '毕业年份', icon: Calendar },
|
||||
{ prop: 'status', label: '毕业状态', icon: Document },
|
||||
{ prop: 'type', label: '毕业类型', icon: Document },
|
||||
{ prop: 'scoreCondition', label: '学分情况', icon: Document, minWidth: 100 },
|
||||
{ prop: 'skillCondition', label: '技能情况', icon: Document, minWidth: 100 },
|
||||
{ prop: 'conductCondition', label: '操行情况', icon: Document, minWidth: 100 },
|
||||
{ prop: 'specialGradu', label: '特殊毕业', icon: Document, minWidth: 100 }
|
||||
]
|
||||
|
||||
const {
|
||||
visibleColumns,
|
||||
visibleColumnsSorted,
|
||||
checkColumnVisible,
|
||||
handleColumnChange,
|
||||
handleColumnOrderChange
|
||||
} = useTableColumnControl(tableColumns, route.path)
|
||||
|
||||
// 搜索表单
|
||||
const searchForm = reactive({
|
||||
graduYear: '',
|
||||
status: '',
|
||||
type: '',
|
||||
stuNo: '',
|
||||
realName: '',
|
||||
deptCode: '',
|
||||
classNo: ''
|
||||
})
|
||||
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
queryForm: searchForm,
|
||||
pageList: fetchList,
|
||||
props: {
|
||||
item: 'records',
|
||||
totalCount: 'total'
|
||||
},
|
||||
createdIsNeed: true
|
||||
})
|
||||
|
||||
const {
|
||||
getDataList,
|
||||
currentChangeHandle,
|
||||
sizeChangeHandle,
|
||||
tableStyle
|
||||
} = useTable(state)
|
||||
|
||||
const formatStatus = (status: string) => {
|
||||
const map: Record<string, string> = { '0': '待确认', '1': '确认毕业', '-1': '不可毕业' }
|
||||
return map[status] || status || '-'
|
||||
}
|
||||
|
||||
const statusTagType = (status: string) => {
|
||||
const map: Record<string, string> = { '0': 'warning', '1': 'success', '-1': 'danger' }
|
||||
return map[status] || 'info'
|
||||
}
|
||||
|
||||
const handleSearch = () => {
|
||||
getDataList()
|
||||
}
|
||||
|
||||
const handleReset = () => {
|
||||
searchFormRef.value?.resetFields()
|
||||
searchForm.graduYear = ''
|
||||
searchForm.status = ''
|
||||
searchForm.type = ''
|
||||
searchForm.stuNo = ''
|
||||
searchForm.realName = ''
|
||||
searchForm.deptCode = ''
|
||||
searchForm.classNo = ''
|
||||
getDataList()
|
||||
}
|
||||
|
||||
const handleMakeGraduStu = async () => {
|
||||
try {
|
||||
await useMessageBox().confirm('确定要生成毕业生信息吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
makeLoading.value = true
|
||||
try {
|
||||
await makeGraduStu({ type: '0' })
|
||||
useMessage().success('生成成功')
|
||||
getDataList()
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg || '生成失败')
|
||||
} finally {
|
||||
makeLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const loadDeptList = async () => {
|
||||
try {
|
||||
const res = await getDeptList()
|
||||
deptList.value = Array.isArray(res.data) ? res.data : []
|
||||
} catch (err) {
|
||||
deptList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadDeptList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '/@/assets/styles/modern-page.scss';
|
||||
</style>
|
||||
253
src/views/stuwork/moralplan/form.vue
Normal file
253
src/views/stuwork/moralplan/form.vue
Normal file
@@ -0,0 +1,253 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
:title="form.id ? '编辑' : '新增'"
|
||||
v-model="visible"
|
||||
:width="800"
|
||||
:close-on-click-modal="false"
|
||||
draggable>
|
||||
<el-form
|
||||
ref="dataFormRef"
|
||||
:model="form"
|
||||
:rules="dataRules"
|
||||
label-width="100px"
|
||||
v-loading="loading">
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="学年" prop="schoolYear">
|
||||
<el-select
|
||||
v-model="form.schoolYear"
|
||||
placeholder="请选择学年"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in schoolYearList"
|
||||
:key="item.year"
|
||||
:label="item.year"
|
||||
:value="item.year" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="学期" prop="schoolTerm">
|
||||
<el-select
|
||||
v-model="form.schoolTerm"
|
||||
placeholder="请选择学期"
|
||||
clearable
|
||||
style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in schoolTermList"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item label="标题" prop="title">
|
||||
<el-input
|
||||
v-model="form.title"
|
||||
placeholder="请输入标题"
|
||||
style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="作者" prop="author">
|
||||
<el-input
|
||||
v-model="form.author"
|
||||
placeholder="请输入作者"
|
||||
style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item label="内容" prop="content">
|
||||
<Editor
|
||||
v-model:getHtml="form.content"
|
||||
:height="'400'"
|
||||
placeholder="请输入内容" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="visible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="onSubmit" :disabled="loading">确 认</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="MoralPlanFormDialog">
|
||||
import { ref, reactive, nextTick, onMounted } from 'vue'
|
||||
import { useMessage } from '/@/hooks/message'
|
||||
import { addObj, editObj, getDetail } from '/@/api/stuwork/moralplan'
|
||||
import { queryAllSchoolYear } from '/@/api/basic/basicyear'
|
||||
import { getDicts } from '/@/api/admin/dict'
|
||||
import Editor from '/@/components/Editor/index.vue'
|
||||
|
||||
const emit = defineEmits(['refresh'])
|
||||
|
||||
// 定义变量内容
|
||||
const dataFormRef = ref()
|
||||
const visible = ref(false)
|
||||
const loading = ref(false)
|
||||
const operType = ref('add') // add 或 edit
|
||||
const schoolYearList = ref<any[]>([])
|
||||
const schoolTermList = ref<any[]>([])
|
||||
|
||||
// 提交表单数据
|
||||
const form = reactive({
|
||||
id: '',
|
||||
schoolYear: '',
|
||||
schoolTerm: '',
|
||||
title: '',
|
||||
content: '',
|
||||
author: ''
|
||||
})
|
||||
|
||||
// 定义校验规则
|
||||
const dataRules = {
|
||||
schoolYear: [
|
||||
{ required: true, message: '请选择学年', trigger: 'change' }
|
||||
],
|
||||
schoolTerm: [
|
||||
{ required: true, message: '请选择学期', trigger: 'change' }
|
||||
],
|
||||
title: [
|
||||
{ required: true, message: '请输入标题', trigger: 'blur' }
|
||||
],
|
||||
content: [
|
||||
{ required: true, message: '请输入内容', trigger: 'blur' }
|
||||
],
|
||||
author: [
|
||||
{ required: true, message: '请输入作者', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
|
||||
// 打开弹窗
|
||||
const openDialog = async (type: string = 'add', row?: any) => {
|
||||
visible.value = true
|
||||
operType.value = type
|
||||
|
||||
// 重置表单数据
|
||||
nextTick(() => {
|
||||
dataFormRef.value?.resetFields()
|
||||
form.id = ''
|
||||
form.schoolYear = ''
|
||||
form.schoolTerm = ''
|
||||
form.title = ''
|
||||
form.content = ''
|
||||
form.author = ''
|
||||
|
||||
// 编辑时填充数据
|
||||
if (type === 'edit' && row) {
|
||||
form.id = row.id
|
||||
form.schoolYear = row.schoolYear || ''
|
||||
form.schoolTerm = row.schoolTerm || ''
|
||||
form.title = row.title || ''
|
||||
form.content = row.content || ''
|
||||
form.author = row.author || ''
|
||||
|
||||
// 如果需要获取详情
|
||||
if (row.id && !row.content) {
|
||||
loading.value = true
|
||||
getDetail(row.id).then((res: any) => {
|
||||
if (res.data) {
|
||||
form.schoolYear = res.data.schoolYear || form.schoolYear
|
||||
form.schoolTerm = res.data.schoolTerm || form.schoolTerm
|
||||
form.title = res.data.title || ''
|
||||
form.content = res.data.content || ''
|
||||
form.author = res.data.author || ''
|
||||
}
|
||||
}).finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 学年列表(班级管理-学年接口)
|
||||
const getSchoolYearList = async () => {
|
||||
try {
|
||||
const res = await queryAllSchoolYear()
|
||||
schoolYearList.value = res?.data && Array.isArray(res.data) ? res.data : []
|
||||
} catch (err) {
|
||||
schoolYearList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
// 学期字典(系统通用)
|
||||
const getSchoolTermDict = async () => {
|
||||
try {
|
||||
const res = await getDicts('school_term')
|
||||
if (res?.data && Array.isArray(res.data)) {
|
||||
schoolTermList.value = res.data.map((item: any) => ({
|
||||
label: item.label ?? item.dictLabel ?? item.name,
|
||||
value: item.value ?? item.dictValue ?? item.code
|
||||
}))
|
||||
} else {
|
||||
schoolTermList.value = []
|
||||
}
|
||||
} catch (err) {
|
||||
schoolTermList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
const onSubmit = async () => {
|
||||
if (!dataFormRef.value) return
|
||||
|
||||
await dataFormRef.value.validate(async (valid: boolean) => {
|
||||
if (!valid) return
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
const submitData = {
|
||||
schoolYear: form.schoolYear,
|
||||
schoolTerm: form.schoolTerm,
|
||||
title: form.title,
|
||||
content: form.content,
|
||||
author: form.author
|
||||
}
|
||||
|
||||
if (operType.value === 'add') {
|
||||
await addObj(submitData)
|
||||
useMessage().success('新增成功')
|
||||
} else {
|
||||
await editObj({
|
||||
id: form.id,
|
||||
...submitData
|
||||
})
|
||||
useMessage().success('编辑成功')
|
||||
}
|
||||
visible.value = false
|
||||
emit('refresh')
|
||||
} catch (err: any) {
|
||||
if (!err?._messageShown) {
|
||||
useMessage().error(err?.msg || (operType.value === 'add' ? '新增失败' : '编辑失败'))
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 初始化:加载学年、学期
|
||||
onMounted(() => {
|
||||
getSchoolYearList()
|
||||
getSchoolTermDict()
|
||||
})
|
||||
|
||||
// 暴露方法
|
||||
defineExpose({
|
||||
openDialog
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.mb20 {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
</style>
|
||||
356
src/views/stuwork/moralplan/index.vue
Normal file
356
src/views/stuwork/moralplan/index.vue
Normal file
@@ -0,0 +1,356 @@
|
||||
<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="state.queryForm"
|
||||
ref="searchFormRef"
|
||||
:inline="true"
|
||||
@keyup.enter="getDataList"
|
||||
class="search-form">
|
||||
<el-form-item label="学年" prop="schoolYear">
|
||||
<el-select
|
||||
v-model="state.queryForm.schoolYear"
|
||||
placeholder="请选择学年"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 200px">
|
||||
<el-option
|
||||
v-for="item in schoolYearList"
|
||||
:key="item.year"
|
||||
:label="item.year"
|
||||
:value="item.year">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="学期" prop="schoolTerm">
|
||||
<el-select
|
||||
v-model="state.queryForm.schoolTerm"
|
||||
placeholder="请选择学期"
|
||||
clearable
|
||||
style="width: 200px">
|
||||
<el-option
|
||||
v-for="item in schoolTermList"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="标题" prop="title">
|
||||
<el-input
|
||||
v-model="state.queryForm.title"
|
||||
placeholder="请输入标题"
|
||||
clearable
|
||||
style="width: 200px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="内容" prop="content">
|
||||
<el-input
|
||||
v-model="state.queryForm.content"
|
||||
placeholder="请输入内容"
|
||||
clearable
|
||||
style="width: 200px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="作者" prop="author">
|
||||
<el-input
|
||||
v-model="state.queryForm.author"
|
||||
placeholder="请输入作者"
|
||||
clearable
|
||||
style="width: 200px" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" plain icon="Search" @click="getDataList">查询</el-button>
|
||||
<el-button icon="Refresh" @click="handleReset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-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="FolderAdd"
|
||||
type="primary"
|
||||
@click="formDialogRef.openDialog()">
|
||||
新增
|
||||
</el-button>
|
||||
<right-toolbar
|
||||
v-model:showSearch="showSearch"
|
||||
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-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 #header>
|
||||
<el-icon><List /></el-icon>
|
||||
</template>
|
||||
<template #default="{ $index }">
|
||||
{{ $index + 1 + ((state.pagination?.current || 1) - 1) * (state.pagination?.size || 10) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<template v-for="col in visibleColumnsSorted" :key="col.prop || col.label">
|
||||
<el-table-column
|
||||
v-if="checkColumnVisible(col.prop || '') && col.prop !== '操作'"
|
||||
:prop="col.prop"
|
||||
:label="col.label"
|
||||
:width="col.width"
|
||||
:min-width="col.minWidth"
|
||||
:show-overflow-tooltip="col.showOverflowTooltip !== false"
|
||||
:align="col.align || 'center'">
|
||||
<template #header>
|
||||
<el-icon v-if="col.icon"><component :is="col.icon" /></el-icon>
|
||||
<span :style="{ marginLeft: col.icon ? '4px' : '0' }">{{ col.label }}</span>
|
||||
</template>
|
||||
<!-- 学期列特殊模板 -->
|
||||
<template v-if="col.prop === 'schoolTerm'" #default="scope">
|
||||
<el-tag size="small" type="primary" effect="plain">
|
||||
{{ formatSchoolTerm(scope.row.schoolTerm) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
<!-- 内容列特殊模板(可能较长,截断显示) -->
|
||||
<template v-else-if="col.prop === 'content'" #default="scope">
|
||||
<span :title="scope.row.content">{{ scope.row.content ? (scope.row.content.length > 50 ? scope.row.content.substring(0, 50) + '...' : scope.row.content) : '-' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
<el-table-column label="操作" width="150" align="center" fixed="right">
|
||||
<template #header>
|
||||
<el-icon><Setting /></el-icon>
|
||||
<span style="margin-left: 4px">操作</span>
|
||||
</template>
|
||||
<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>
|
||||
|
||||
<!-- 新增/编辑表单弹窗 -->
|
||||
<form-dialog ref="formDialogRef" @refresh="getDataList" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="MoralPlan">
|
||||
import { reactive, ref, onMounted } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { BasicTableProps, useTable } from "/@/hooks/table";
|
||||
import { fetchList, delObj } from "/@/api/stuwork/moralplan";
|
||||
import { queryAllSchoolYear } from "/@/api/basic/basicyear";
|
||||
import { getDicts } from "/@/api/admin/dict";
|
||||
import { useMessage, useMessageBox } from "/@/hooks/message";
|
||||
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
|
||||
import FormDialog from './form.vue'
|
||||
import { List, Calendar, Clock, Document, User, Setting, Menu, Search } from '@element-plus/icons-vue'
|
||||
import { useTableColumnControl } from '/@/hooks/tableColumn'
|
||||
|
||||
// 定义变量内容
|
||||
const route = useRoute()
|
||||
const formDialogRef = ref()
|
||||
const columnControlRef = ref<any>()
|
||||
const searchFormRef = ref()
|
||||
const showSearch = ref(true)
|
||||
const schoolYearList = ref<any[]>([])
|
||||
const schoolTermList = ref<any[]>([])
|
||||
|
||||
// 表格列配置
|
||||
const tableColumns = [
|
||||
{ prop: 'schoolYear', label: '学年' },
|
||||
{ prop: 'schoolTerm', label: '学期' },
|
||||
{ prop: 'title', label: '标题', minWidth: 200 },
|
||||
{ prop: 'content', label: '内容', minWidth: 250 },
|
||||
{ prop: 'author', label: '作者' },
|
||||
{ prop: 'createTime', label: '创建时间', width: 180 },
|
||||
{ prop: 'updateTime', label: '更新时间', width: 180 }
|
||||
]
|
||||
|
||||
// 列配置映射(用于图标)
|
||||
const columnConfigMap: Record<string, { icon: any }> = {
|
||||
schoolYear: { icon: Calendar },
|
||||
schoolTerm: { icon: Clock },
|
||||
title: { icon: Document },
|
||||
content: { icon: Document },
|
||||
author: { icon: User },
|
||||
createTime: { icon: Clock },
|
||||
updateTime: { icon: Clock }
|
||||
}
|
||||
|
||||
// 使用表格列控制hook
|
||||
const {
|
||||
visibleColumns,
|
||||
visibleColumnsSorted,
|
||||
checkColumnVisible,
|
||||
handleColumnChange,
|
||||
handleColumnOrderChange
|
||||
} = useTableColumnControl(tableColumns, { storageKey: route.path })
|
||||
|
||||
// 配置 useTable
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
queryForm: {
|
||||
schoolYear: '',
|
||||
schoolTerm: '',
|
||||
title: '',
|
||||
content: '',
|
||||
author: ''
|
||||
},
|
||||
pageList: fetchList,
|
||||
props: {
|
||||
item: 'records',
|
||||
totalCount: 'total'
|
||||
},
|
||||
createdIsNeed: true
|
||||
})
|
||||
|
||||
// table hook
|
||||
const {
|
||||
getDataList,
|
||||
currentChangeHandle,
|
||||
sizeChangeHandle,
|
||||
tableStyle
|
||||
} = useTable(state)
|
||||
|
||||
// 格式化学期
|
||||
const formatSchoolTerm = (value: string | number) => {
|
||||
if (value === null || value === undefined || value === '') {
|
||||
return '-'
|
||||
}
|
||||
const dictItem = schoolTermList.value.find(item => item.value == value)
|
||||
return dictItem ? dictItem.label : value
|
||||
}
|
||||
|
||||
// 重置
|
||||
const handleReset = () => {
|
||||
searchFormRef.value?.resetFields()
|
||||
state.queryForm.schoolYear = ''
|
||||
state.queryForm.schoolTerm = ''
|
||||
state.queryForm.title = ''
|
||||
state.queryForm.content = ''
|
||||
state.queryForm.author = ''
|
||||
getDataList()
|
||||
}
|
||||
|
||||
// 编辑
|
||||
const handleEdit = (row: any) => {
|
||||
formDialogRef.value?.openDialog('edit', row)
|
||||
}
|
||||
|
||||
// 删除
|
||||
const handleDelete = async (row: any) => {
|
||||
const { confirm } = useMessageBox()
|
||||
try {
|
||||
await confirm('确定要删除该德育计划吗?')
|
||||
await delObj([row.id])
|
||||
useMessage().success('删除成功')
|
||||
getDataList()
|
||||
} catch (err: any) {
|
||||
if (err !== 'cancel') {
|
||||
useMessage().error(err.msg || '删除失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 获取学年列表
|
||||
const getSchoolYearList = async () => {
|
||||
try {
|
||||
const res = await queryAllSchoolYear()
|
||||
if (res.data && Array.isArray(res.data)) {
|
||||
schoolYearList.value = res.data
|
||||
} else {
|
||||
schoolYearList.value = []
|
||||
}
|
||||
} catch (err) {
|
||||
schoolYearList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
// 获取学期字典
|
||||
const getSchoolTermDict = async () => {
|
||||
try {
|
||||
const res = await getDicts('school_term')
|
||||
if (res.data && Array.isArray(res.data)) {
|
||||
schoolTermList.value = res.data.map((item: any) => ({
|
||||
label: item.label || item.dictLabel || item.name,
|
||||
value: item.value || item.dictValue || item.code
|
||||
}))
|
||||
} else {
|
||||
schoolTermList.value = []
|
||||
}
|
||||
} catch (err) {
|
||||
schoolTermList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
getSchoolYearList()
|
||||
getSchoolTermDict()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '/@/assets/styles/modern-page.scss';
|
||||
</style>
|
||||
270
src/views/stuwork/onlinebooks/form.vue
Normal file
270
src/views/stuwork/onlinebooks/form.vue
Normal file
@@ -0,0 +1,270 @@
|
||||
<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="120px" v-loading="loading">
|
||||
<el-form-item label="学院" prop="deptCode">
|
||||
<el-select
|
||||
v-model="form.deptCode"
|
||||
placeholder="请选择学院"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in deptList"
|
||||
:key="item.deptCode"
|
||||
:label="item.deptName"
|
||||
:value="item.deptCode">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="类别编码" prop="categoryCode">
|
||||
<el-select
|
||||
v-model="form.categoryCode"
|
||||
placeholder="请选择类别"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in categoryList"
|
||||
:key="item.categoryCode"
|
||||
:label="item.categoryName"
|
||||
:value="item.categoryCode">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="封面" prop="bookImg">
|
||||
<upload-img
|
||||
v-model="form.bookImg"
|
||||
:width="'150px'"
|
||||
:height="'200px'"
|
||||
upload-file-url="/admin/sys-file/upload" />
|
||||
</el-form-item>
|
||||
<el-form-item label="书名" prop="bookName">
|
||||
<el-input
|
||||
v-model="form.bookName"
|
||||
placeholder="请输入书名"
|
||||
clearable
|
||||
style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="作者" prop="bookAuthor">
|
||||
<el-input
|
||||
v-model="form.bookAuthor"
|
||||
placeholder="请输入作者"
|
||||
clearable
|
||||
style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="链接" prop="webUrl">
|
||||
<el-input
|
||||
v-model="form.webUrl"
|
||||
placeholder="请输入链接,例如:https://www.example.com"
|
||||
clearable
|
||||
style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remarks">
|
||||
<el-input
|
||||
v-model="form.remarks"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="请输入备注"
|
||||
maxlength="250"
|
||||
show-word-limit
|
||||
style="width: 100%" />
|
||||
</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="OnlineBooksFormDialog">
|
||||
import { ref, reactive, nextTick, onMounted } from 'vue'
|
||||
import { useMessage } from '/@/hooks/message'
|
||||
import { addObj, editObj, getDetail } from '/@/api/stuwork/onlinebooks'
|
||||
import request from '/@/utils/request'
|
||||
import UploadImg from '/@/components/Upload/Image.vue'
|
||||
|
||||
const emit = defineEmits(['refresh'])
|
||||
|
||||
// 定义变量内容
|
||||
const dataFormRef = ref()
|
||||
const visible = ref(false)
|
||||
const loading = ref(false)
|
||||
const operType = ref('add') // add 或 edit
|
||||
const deptList = ref<any[]>([])
|
||||
const categoryList = ref<any[]>([])
|
||||
|
||||
// 提交表单数据
|
||||
const form = reactive({
|
||||
id: '',
|
||||
deptCode: '',
|
||||
categoryCode: '',
|
||||
bookImg: '',
|
||||
bookName: '',
|
||||
bookAuthor: '',
|
||||
webUrl: '',
|
||||
remarks: ''
|
||||
})
|
||||
|
||||
// URL验证函数
|
||||
const validateUrl = (rule: any, value: any, callback: any) => {
|
||||
if (!value) {
|
||||
callback(new Error('链接不能为空'))
|
||||
return
|
||||
}
|
||||
const urlPattern = /^(https?:\/\/)?([a-zA-Z0-9\-]+\.)+[a-zA-Z]{2,}(:\d+)?(\/[^\s]*)?$/i
|
||||
if (urlPattern.test(value)) {
|
||||
callback()
|
||||
} else {
|
||||
callback(new Error('请输入合法的网址,例如:https://www.baidu.com'))
|
||||
}
|
||||
}
|
||||
|
||||
// 定义校验规则
|
||||
const dataRules = {
|
||||
deptCode: [
|
||||
{ required: true, message: '请选择学院', trigger: 'change' }
|
||||
],
|
||||
categoryCode: [
|
||||
{ required: true, message: '请选择类别', trigger: 'change' }
|
||||
],
|
||||
bookName: [
|
||||
{ required: true, message: '请输入书名', trigger: 'blur' }
|
||||
],
|
||||
bookAuthor: [
|
||||
{ required: true, message: '请输入作者', trigger: 'blur' }
|
||||
],
|
||||
webUrl: [
|
||||
{ validator: validateUrl, trigger: 'blur' }
|
||||
],
|
||||
remarks: [
|
||||
{ max: 250, message: '备注不能超过250个字符', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
|
||||
// 打开弹窗
|
||||
const openDialog = async (type: string = 'add', row?: any) => {
|
||||
visible.value = true
|
||||
operType.value = type
|
||||
|
||||
// 重置表单数据
|
||||
nextTick(() => {
|
||||
dataFormRef.value?.resetFields()
|
||||
form.id = ''
|
||||
form.deptCode = ''
|
||||
form.categoryCode = ''
|
||||
form.bookImg = ''
|
||||
form.bookName = ''
|
||||
form.bookAuthor = ''
|
||||
form.webUrl = ''
|
||||
form.remarks = ''
|
||||
|
||||
// 编辑时填充数据
|
||||
if (type === 'edit' && row) {
|
||||
loading.value = true
|
||||
getDetail(row.id)
|
||||
.then((res: any) => {
|
||||
if (res.data) {
|
||||
form.id = res.data.id || row.id
|
||||
form.deptCode = res.data.deptCode || ''
|
||||
form.categoryCode = res.data.categoryCode || ''
|
||||
form.bookImg = res.data.bookImg || ''
|
||||
form.bookName = res.data.bookName || ''
|
||||
form.bookAuthor = res.data.bookAuthor || ''
|
||||
form.webUrl = res.data.webUrl || ''
|
||||
form.remarks = res.data.remarks || ''
|
||||
}
|
||||
})
|
||||
.catch((err: any) => {
|
||||
useMessage().error(err.msg || '获取详情失败')
|
||||
visible.value = false
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
const onSubmit = async () => {
|
||||
if (!dataFormRef.value) return
|
||||
|
||||
await dataFormRef.value.validate(async (valid: boolean) => {
|
||||
if (!valid) return
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
const submitData: any = {
|
||||
deptCode: form.deptCode,
|
||||
categoryCode: form.categoryCode,
|
||||
bookImg: form.bookImg,
|
||||
bookName: form.bookName,
|
||||
bookAuthor: form.bookAuthor,
|
||||
webUrl: form.webUrl,
|
||||
remarks: form.remarks
|
||||
}
|
||||
|
||||
if (operType.value === 'add') {
|
||||
await addObj(submitData)
|
||||
useMessage().success('新增成功')
|
||||
} else {
|
||||
submitData.id = form.id
|
||||
await editObj(submitData)
|
||||
useMessage().success('编辑成功')
|
||||
}
|
||||
visible.value = false
|
||||
emit('refresh')
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg || (operType.value === 'add' ? '新增失败' : '编辑失败'))
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 获取学院列表
|
||||
const getDeptList = async () => {
|
||||
try {
|
||||
const res = await request({
|
||||
url: '/basic/basicdept/getDeptList',
|
||||
method: 'get',
|
||||
params: { secondFlag: 1 }
|
||||
})
|
||||
if (res.data) {
|
||||
deptList.value = Array.isArray(res.data) ? res.data : []
|
||||
}
|
||||
} catch (err) {
|
||||
deptList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
// 获取类别列表
|
||||
const getCategoryList = async () => {
|
||||
try {
|
||||
const res = await request({
|
||||
url: '/stuwork/onlinebookscategory/list',
|
||||
method: 'get'
|
||||
})
|
||||
if (res.data) {
|
||||
categoryList.value = Array.isArray(res.data) ? res.data : []
|
||||
}
|
||||
} catch (err) {
|
||||
categoryList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
getDeptList()
|
||||
getCategoryList()
|
||||
})
|
||||
|
||||
// 暴露方法给父组件
|
||||
defineExpose({
|
||||
openDialog
|
||||
})
|
||||
</script>
|
||||
|
||||
347
src/views/stuwork/onlinebooks/index.vue
Normal file
347
src/views/stuwork/onlinebooks/index.vue
Normal file
@@ -0,0 +1,347 @@
|
||||
<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="deptCode">
|
||||
<el-select
|
||||
v-model="searchForm.deptCode"
|
||||
placeholder="请选择学院"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 200px">
|
||||
<el-option
|
||||
v-for="item in deptList"
|
||||
:key="item.deptCode"
|
||||
:label="item.deptName"
|
||||
:value="item.deptCode">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="类别编码" prop="categoryCode">
|
||||
<el-select
|
||||
v-model="searchForm.categoryCode"
|
||||
placeholder="请选择类别"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 200px">
|
||||
<el-option
|
||||
v-for="item in categoryList"
|
||||
:key="item.categoryCode"
|
||||
:label="item.categoryName"
|
||||
:value="item.categoryCode">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="书名" prop="bookName">
|
||||
<el-input
|
||||
v-model="searchForm.bookName"
|
||||
placeholder="请输入书名"
|
||||
clearable
|
||||
style="width: 200px" />
|
||||
</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="FolderAdd"
|
||||
type="primary"
|
||||
@click="formDialogRef.openDialog()">
|
||||
新增
|
||||
</el-button>
|
||||
<right-toolbar
|
||||
v-model:showSearch="showSearch"
|
||||
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-table
|
||||
:data="state.dataList"
|
||||
v-loading="state.loading"
|
||||
stripe
|
||||
:cell-style="tableStyle.cellStyle"
|
||||
:header-cell-style="tableStyle.headerCellStyle"
|
||||
class="modern-table"
|
||||
@sort-change="sortChangeHandle">
|
||||
<el-table-column type="index" label="序号" width="70" align="center">
|
||||
<template #header>
|
||||
<el-icon><List /></el-icon>
|
||||
</template>
|
||||
<template #default="{ $index }">
|
||||
{{ $index + 1 + ((state.pagination?.current || 1) - 1) * (state.pagination?.size || 10) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<template v-for="col in visibleColumnsSorted" :key="col.prop || col.label">
|
||||
<el-table-column
|
||||
v-if="checkColumnVisible(col.prop || '') && col.prop !== '序号'"
|
||||
:prop="col.prop"
|
||||
:label="col.label"
|
||||
show-overflow-tooltip
|
||||
align="center">
|
||||
<template #header>
|
||||
<el-icon><component :is="columnConfigMap[col.prop]?.icon || Document" /></el-icon>
|
||||
<span style="margin-left: 4px">{{ col.label }}</span>
|
||||
</template>
|
||||
<!-- 封面图片 -->
|
||||
<template v-if="col.prop === 'bookImg'" #default="scope">
|
||||
<el-image
|
||||
v-if="scope.row.bookImg"
|
||||
:src="scope.row.bookImg"
|
||||
:preview-src-list="[scope.row.bookImg]"
|
||||
fit="cover"
|
||||
style="width: 60px; height: 80px; cursor: pointer; border-radius: 4px;"
|
||||
preview-teleported>
|
||||
</el-image>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
<!-- 链接 -->
|
||||
<template v-else-if="col.prop === 'webUrl'" #default="scope">
|
||||
<el-link
|
||||
v-if="scope.row.webUrl"
|
||||
:href="scope.row.webUrl"
|
||||
target="_blank"
|
||||
type="primary">
|
||||
{{ scope.row.webUrl }}
|
||||
</el-link>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
<el-table-column label="操作" width="150" align="center" fixed="right">
|
||||
<template #header>
|
||||
<el-icon><Setting /></el-icon>
|
||||
<span style="margin-left: 4px">操作</span>
|
||||
</template>
|
||||
<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>
|
||||
|
||||
<!-- 新增/编辑对话框 -->
|
||||
<FormDialog ref="formDialogRef" @refresh="getDataList()" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="OnlineBooks">
|
||||
import { reactive, ref, onMounted, computed, nextTick } from 'vue'
|
||||
import { BasicTableProps, useTable } from "/@/hooks/table";
|
||||
import { fetchList, delObj } from "/@/api/stuwork/onlinebooks";
|
||||
import { useMessage, useMessageBox } from "/@/hooks/message";
|
||||
import request from '/@/utils/request';
|
||||
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
|
||||
import FormDialog from './form.vue';
|
||||
import { List, Document, Picture, Link, User, Collection, Setting, Menu, Search } from '@element-plus/icons-vue'
|
||||
import { useTableColumnControl } from '/@/hooks/tableColumn'
|
||||
|
||||
// 定义变量
|
||||
const searchFormRef = ref()
|
||||
const columnControlRef = ref<any>()
|
||||
const formDialogRef = ref()
|
||||
const showSearch = ref(true)
|
||||
const deptList = ref<any[]>([])
|
||||
const categoryList = ref<any[]>([])
|
||||
|
||||
// 表格列配置
|
||||
const tableColumns = [
|
||||
{ prop: 'deptCode', label: '学院' },
|
||||
{ prop: 'categoryCode', label: '类别编码' },
|
||||
{ prop: 'bookImg', label: '封面' },
|
||||
{ prop: 'bookName', label: '书名' },
|
||||
{ prop: 'bookAuthor', label: '作者' },
|
||||
{ prop: 'webUrl', label: '链接' },
|
||||
{ prop: 'remarks', label: '备注' }
|
||||
]
|
||||
|
||||
// 列配置映射
|
||||
const columnConfigMap: Record<string, { icon: any }> = {
|
||||
deptCode: { icon: Collection },
|
||||
categoryCode: { icon: Collection },
|
||||
bookImg: { icon: Picture },
|
||||
bookName: { icon: Document },
|
||||
bookAuthor: { icon: User },
|
||||
webUrl: { icon: Link },
|
||||
remarks: { icon: Document }
|
||||
}
|
||||
|
||||
// 表格列控制hook
|
||||
const {
|
||||
visibleColumns,
|
||||
visibleColumnsSorted,
|
||||
checkColumnVisible,
|
||||
handleColumnChange,
|
||||
handleColumnOrderChange
|
||||
} = useTableColumnControl(tableColumns)
|
||||
|
||||
// 搜索表单
|
||||
const searchForm = reactive({
|
||||
deptCode: '',
|
||||
categoryCode: '',
|
||||
bookName: ''
|
||||
})
|
||||
|
||||
// 使用 useTable
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
queryForm: searchForm,
|
||||
pageList: fetchList,
|
||||
props: {
|
||||
item: 'records',
|
||||
totalCount: 'total'
|
||||
},
|
||||
createdIsNeed: true
|
||||
})
|
||||
|
||||
// table hook
|
||||
const {
|
||||
getDataList,
|
||||
currentChangeHandle,
|
||||
sizeChangeHandle,
|
||||
sortChangeHandle,
|
||||
tableStyle
|
||||
} = useTable(state)
|
||||
|
||||
// 查询
|
||||
const handleSearch = () => {
|
||||
getDataList()
|
||||
}
|
||||
|
||||
// 重置
|
||||
const handleReset = () => {
|
||||
searchFormRef.value?.formRef?.resetFields()
|
||||
searchForm.deptCode = ''
|
||||
searchForm.categoryCode = ''
|
||||
searchForm.bookName = ''
|
||||
getDataList()
|
||||
}
|
||||
|
||||
// 编辑
|
||||
const handleEdit = (row: any) => {
|
||||
formDialogRef.value.openDialog('edit', row)
|
||||
}
|
||||
|
||||
// 删除
|
||||
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 || '删除失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 获取学院列表
|
||||
const getDeptList = async () => {
|
||||
try {
|
||||
const res = await request({
|
||||
url: '/basic/basicdept/getDeptList',
|
||||
method: 'get',
|
||||
params: { secondFlag: 1 }
|
||||
})
|
||||
if (res.data) {
|
||||
deptList.value = Array.isArray(res.data) ? res.data : []
|
||||
}
|
||||
} catch (err) {
|
||||
deptList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
// 获取类别列表
|
||||
const getCategoryList = async () => {
|
||||
try {
|
||||
const res = await request({
|
||||
url: '/stuwork/onlinebookscategory/list',
|
||||
method: 'get'
|
||||
})
|
||||
if (res.data) {
|
||||
categoryList.value = Array.isArray(res.data) ? res.data : []
|
||||
}
|
||||
} catch (err) {
|
||||
categoryList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
getDeptList()
|
||||
getCategoryList()
|
||||
nextTick(() => {
|
||||
if (visibleColumns.value.length === 0) {
|
||||
}
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '/@/assets/styles/modern-page.scss';
|
||||
</style>
|
||||
|
||||
257
src/views/stuwork/onlinebooksbrowsinghistory/form.vue
Normal file
257
src/views/stuwork/onlinebooksbrowsinghistory/form.vue
Normal file
@@ -0,0 +1,257 @@
|
||||
<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="120px" v-loading="loading">
|
||||
<el-form-item label="学院" prop="deptCode">
|
||||
<el-select
|
||||
v-model="form.deptCode"
|
||||
placeholder="请选择学院"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in deptList"
|
||||
:key="item.deptCode"
|
||||
:label="item.deptName"
|
||||
:value="item.deptCode">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="用户类型" prop="userType">
|
||||
<el-select
|
||||
v-model="form.userType"
|
||||
placeholder="请选择用户类型"
|
||||
clearable
|
||||
style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in userTypeList"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="用户名" prop="userName">
|
||||
<el-input
|
||||
v-model="form.userName"
|
||||
placeholder="请输入用户名"
|
||||
clearable
|
||||
style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="姓名" prop="realName">
|
||||
<el-input
|
||||
v-model="form.realName"
|
||||
placeholder="请输入姓名"
|
||||
clearable
|
||||
style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="图书" prop="bookId">
|
||||
<el-select
|
||||
v-model="form.bookId"
|
||||
placeholder="请选择图书"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in bookList"
|
||||
:key="item.id"
|
||||
:label="item.bookName"
|
||||
:value="item.id">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="浏览时间" prop="readTime">
|
||||
<el-date-picker
|
||||
v-model="form.readTime"
|
||||
type="datetime"
|
||||
placeholder="请选择浏览时间"
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remarks">
|
||||
<el-input
|
||||
v-model="form.remarks"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="请输入备注"
|
||||
style="width: 100%" />
|
||||
</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="OnlineBooksBrowsingHistoryFormDialog">
|
||||
import { ref, reactive, nextTick, onMounted } from 'vue'
|
||||
import { useMessage } from '/@/hooks/message'
|
||||
import { addObj, editObj, getDetail } from '/@/api/stuwork/onlinebooksbrowsinghistory'
|
||||
import { getList as getBookList } from '/@/api/stuwork/onlinebooks'
|
||||
import request from '/@/utils/request'
|
||||
|
||||
const emit = defineEmits(['refresh'])
|
||||
|
||||
// 定义变量内容
|
||||
const dataFormRef = ref()
|
||||
const visible = ref(false)
|
||||
const loading = ref(false)
|
||||
const operType = ref('add') // add 或 edit
|
||||
const deptList = ref<any[]>([])
|
||||
const bookList = ref<any[]>([])
|
||||
|
||||
// 用户类型列表
|
||||
const userTypeList = [
|
||||
{ label: '学生', value: '0' },
|
||||
{ label: '老师', value: '1' }
|
||||
]
|
||||
|
||||
// 提交表单数据
|
||||
const form = reactive({
|
||||
id: '',
|
||||
deptCode: '',
|
||||
userType: '',
|
||||
userName: '',
|
||||
realName: '',
|
||||
bookId: '',
|
||||
readTime: '',
|
||||
remarks: ''
|
||||
})
|
||||
|
||||
// 定义校验规则
|
||||
const dataRules = {
|
||||
deptCode: [
|
||||
{ required: true, message: '请选择学院', trigger: 'change' }
|
||||
],
|
||||
userType: [
|
||||
{ required: true, message: '请选择用户类型', trigger: 'change' }
|
||||
],
|
||||
bookId: [
|
||||
{ required: true, message: '请选择图书', trigger: 'change' }
|
||||
]
|
||||
}
|
||||
|
||||
// 打开弹窗
|
||||
const openDialog = async (type: string = 'add', row?: any) => {
|
||||
visible.value = true
|
||||
operType.value = type
|
||||
|
||||
// 重置表单数据
|
||||
nextTick(() => {
|
||||
dataFormRef.value?.resetFields()
|
||||
form.id = ''
|
||||
form.deptCode = ''
|
||||
form.userType = ''
|
||||
form.userName = ''
|
||||
form.realName = ''
|
||||
form.bookId = ''
|
||||
form.readTime = ''
|
||||
form.remarks = ''
|
||||
|
||||
// 编辑时填充数据
|
||||
if (type === 'edit' && row) {
|
||||
loading.value = true
|
||||
getDetail(row.id)
|
||||
.then((res: any) => {
|
||||
if (res.data) {
|
||||
form.id = res.data.id || row.id
|
||||
form.deptCode = res.data.deptCode || ''
|
||||
form.userType = res.data.userType || ''
|
||||
form.userName = res.data.userName || ''
|
||||
form.realName = res.data.realName || ''
|
||||
form.bookId = res.data.bookId || ''
|
||||
form.readTime = res.data.readTime || ''
|
||||
form.remarks = res.data.remarks || ''
|
||||
}
|
||||
})
|
||||
.catch((err: any) => {
|
||||
useMessage().error(err.msg || '获取详情失败')
|
||||
visible.value = false
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
const onSubmit = async () => {
|
||||
if (!dataFormRef.value) return
|
||||
|
||||
await dataFormRef.value.validate(async (valid: boolean) => {
|
||||
if (!valid) return
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
const submitData: any = {
|
||||
deptCode: form.deptCode,
|
||||
userType: form.userType,
|
||||
userName: form.userName,
|
||||
realName: form.realName,
|
||||
bookId: form.bookId,
|
||||
readTime: form.readTime,
|
||||
remarks: form.remarks
|
||||
}
|
||||
|
||||
if (operType.value === 'add') {
|
||||
await addObj(submitData)
|
||||
useMessage().success('新增成功')
|
||||
} else {
|
||||
submitData.id = form.id
|
||||
await editObj(submitData)
|
||||
useMessage().success('编辑成功')
|
||||
}
|
||||
visible.value = false
|
||||
emit('refresh')
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg || (operType.value === 'add' ? '新增失败' : '编辑失败'))
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 获取学院列表
|
||||
const getDeptList = async () => {
|
||||
try {
|
||||
const res = await request({
|
||||
url: '/basic/basicdept/getDeptList',
|
||||
method: 'get',
|
||||
params: { secondFlag: 1 }
|
||||
})
|
||||
if (res.data) {
|
||||
deptList.value = Array.isArray(res.data) ? res.data : []
|
||||
}
|
||||
} catch (err) {
|
||||
deptList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
// 获取图书列表
|
||||
const getBookListData = async () => {
|
||||
try {
|
||||
const res = await getBookList()
|
||||
if (res.data) {
|
||||
bookList.value = Array.isArray(res.data) ? res.data : []
|
||||
}
|
||||
} catch (err) {
|
||||
bookList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
getDeptList()
|
||||
getBookListData()
|
||||
})
|
||||
|
||||
// 暴露方法给父组件
|
||||
defineExpose({
|
||||
openDialog
|
||||
})
|
||||
</script>
|
||||
|
||||
381
src/views/stuwork/onlinebooksbrowsinghistory/index.vue
Normal file
381
src/views/stuwork/onlinebooksbrowsinghistory/index.vue
Normal file
@@ -0,0 +1,381 @@
|
||||
<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="deptCode">
|
||||
<el-select
|
||||
v-model="searchForm.deptCode"
|
||||
placeholder="请选择学院"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 200px">
|
||||
<el-option
|
||||
v-for="item in deptList"
|
||||
:key="item.deptCode"
|
||||
:label="item.deptName"
|
||||
:value="item.deptCode">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="用户类型" prop="userType">
|
||||
<el-select
|
||||
v-model="searchForm.userType"
|
||||
placeholder="请选择用户类型"
|
||||
clearable
|
||||
style="width: 200px">
|
||||
<el-option
|
||||
v-for="item in userTypeList"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="用户名" prop="userName">
|
||||
<el-input
|
||||
v-model="searchForm.userName"
|
||||
placeholder="请输入用户名"
|
||||
clearable
|
||||
style="width: 200px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="姓名" prop="realName">
|
||||
<el-input
|
||||
v-model="searchForm.realName"
|
||||
placeholder="请输入姓名"
|
||||
clearable
|
||||
style="width: 200px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="图书" prop="bookId">
|
||||
<el-select
|
||||
v-model="searchForm.bookId"
|
||||
placeholder="请选择图书"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 200px">
|
||||
<el-option
|
||||
v-for="item in bookList"
|
||||
:key="item.id"
|
||||
:label="item.bookName"
|
||||
:value="item.id">
|
||||
</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="FolderAdd"
|
||||
type="primary"
|
||||
@click="formDialogRef.openDialog()">
|
||||
新增
|
||||
</el-button>
|
||||
<right-toolbar
|
||||
v-model:showSearch="showSearch"
|
||||
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-table
|
||||
:data="state.dataList"
|
||||
v-loading="state.loading"
|
||||
stripe
|
||||
:cell-style="tableStyle.cellStyle"
|
||||
:header-cell-style="tableStyle.headerCellStyle"
|
||||
class="modern-table"
|
||||
@sort-change="sortChangeHandle">
|
||||
<el-table-column type="index" label="序号" width="70" align="center">
|
||||
<template #header>
|
||||
<el-icon><List /></el-icon>
|
||||
</template>
|
||||
<template #default="{ $index }">
|
||||
{{ $index + 1 + ((state.pagination?.current || 1) - 1) * (state.pagination?.size || 10) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<template v-for="col in visibleColumnsSorted" :key="col.prop || col.label">
|
||||
<el-table-column
|
||||
v-if="checkColumnVisible(col.prop || '') && col.prop !== '序号'"
|
||||
:prop="col.prop"
|
||||
:label="col.label"
|
||||
show-overflow-tooltip
|
||||
align="center">
|
||||
<template #header>
|
||||
<el-icon><component :is="col.prop ? (columnConfigMap[col.prop]?.icon || Document) : Document" /></el-icon>
|
||||
<span style="margin-left: 4px">{{ col.label }}</span>
|
||||
</template>
|
||||
<!-- 用户类型格式化 -->
|
||||
<template v-if="col.prop === 'userType'" #default="scope">
|
||||
<el-tag v-if="String(scope.row.userType) === '0'" type="primary" size="small">学生</el-tag>
|
||||
<el-tag v-else-if="String(scope.row.userType) === '1'" type="success" size="small">老师</el-tag>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
<!-- 浏览时间格式化 -->
|
||||
<template v-else-if="col.prop === 'readTime'" #default="scope">
|
||||
<span v-if="scope.row.readTime">{{ formatDateTime(scope.row.readTime) }}</span>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
<el-table-column label="操作" width="150" align="center" fixed="right">
|
||||
<template #header>
|
||||
<el-icon><Setting /></el-icon>
|
||||
<span style="margin-left: 4px">操作</span>
|
||||
</template>
|
||||
<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>
|
||||
|
||||
<!-- 新增/编辑对话框 -->
|
||||
<FormDialog ref="formDialogRef" @refresh="getDataList()" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="OnlineBooksBrowsingHistory">
|
||||
import { reactive, ref, onMounted, computed, nextTick } from 'vue'
|
||||
import { BasicTableProps, useTable } from "/@/hooks/table";
|
||||
import { fetchList, delObj } from "/@/api/stuwork/onlinebooksbrowsinghistory";
|
||||
import { getList as getBookList } from "/@/api/stuwork/onlinebooks";
|
||||
import { useMessage, useMessageBox } from "/@/hooks/message";
|
||||
import request from '/@/utils/request';
|
||||
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
|
||||
import FormDialog from './form.vue';
|
||||
import { List, Document, User, Avatar, Collection, Clock, Setting, Menu, Search } from '@element-plus/icons-vue'
|
||||
import { useTableColumnControl } from '/@/hooks/tableColumn'
|
||||
|
||||
// 定义变量
|
||||
const searchFormRef = ref()
|
||||
const columnControlRef = ref<any>()
|
||||
const formDialogRef = ref()
|
||||
const showSearch = ref(true)
|
||||
const deptList = ref<any[]>([])
|
||||
const bookList = ref<any[]>([])
|
||||
|
||||
// 用户类型列表
|
||||
const userTypeList = [
|
||||
{ label: '学生', value: '0' },
|
||||
{ label: '老师', value: '1' }
|
||||
]
|
||||
|
||||
// 表格列配置
|
||||
const tableColumns = [
|
||||
{ prop: 'deptCode', label: '学院' },
|
||||
{ prop: 'userType', label: '用户类型' },
|
||||
{ prop: 'userName', label: '用户名' },
|
||||
{ prop: 'realName', label: '姓名' },
|
||||
{ prop: 'bookId', label: '图书' },
|
||||
{ prop: 'readTime', label: '浏览时间' },
|
||||
{ prop: 'remarks', label: '备注' }
|
||||
]
|
||||
|
||||
// 列配置映射
|
||||
const columnConfigMap: Record<string, { icon: any }> = {
|
||||
deptCode: { icon: Collection },
|
||||
userType: { icon: User },
|
||||
userName: { icon: User },
|
||||
realName: { icon: Avatar },
|
||||
bookId: { icon: Document },
|
||||
readTime: { icon: Clock },
|
||||
remarks: { icon: Document }
|
||||
}
|
||||
|
||||
// 表格列控制hook
|
||||
const {
|
||||
visibleColumns,
|
||||
visibleColumnsSorted,
|
||||
checkColumnVisible,
|
||||
handleColumnChange,
|
||||
handleColumnOrderChange
|
||||
} = useTableColumnControl(tableColumns)
|
||||
|
||||
// 搜索表单
|
||||
const searchForm = reactive({
|
||||
deptCode: '',
|
||||
userType: '',
|
||||
userName: '',
|
||||
realName: '',
|
||||
bookId: ''
|
||||
})
|
||||
|
||||
// 使用 useTable
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
queryForm: searchForm,
|
||||
pageList: fetchList,
|
||||
props: {
|
||||
item: 'records',
|
||||
totalCount: 'total'
|
||||
},
|
||||
createdIsNeed: true
|
||||
})
|
||||
|
||||
// table hook
|
||||
const {
|
||||
getDataList,
|
||||
currentChangeHandle,
|
||||
sizeChangeHandle,
|
||||
sortChangeHandle,
|
||||
tableStyle
|
||||
} = useTable(state)
|
||||
|
||||
// 查询
|
||||
const handleSearch = () => {
|
||||
getDataList()
|
||||
}
|
||||
|
||||
// 重置
|
||||
const handleReset = () => {
|
||||
searchFormRef.value?.formRef?.resetFields()
|
||||
searchForm.deptCode = ''
|
||||
searchForm.userType = ''
|
||||
searchForm.userName = ''
|
||||
searchForm.realName = ''
|
||||
searchForm.bookId = ''
|
||||
getDataList()
|
||||
}
|
||||
|
||||
// 编辑
|
||||
const handleEdit = (row: any) => {
|
||||
formDialogRef.value.openDialog('edit', row)
|
||||
}
|
||||
|
||||
// 删除
|
||||
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 || '删除失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 格式化日期时间
|
||||
const formatDateTime = (dateTime: string) => {
|
||||
if (!dateTime) return '-'
|
||||
try {
|
||||
const date = new Date(dateTime)
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
const hours = String(date.getHours()).padStart(2, '0')
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0')
|
||||
const seconds = String(date.getSeconds()).padStart(2, '0')
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
|
||||
} catch (e) {
|
||||
return dateTime
|
||||
}
|
||||
}
|
||||
|
||||
// 获取学院列表
|
||||
const getDeptList = async () => {
|
||||
try {
|
||||
const res = await request({
|
||||
url: '/basic/basicdept/getDeptList',
|
||||
method: 'get',
|
||||
params: { secondFlag: 1 }
|
||||
})
|
||||
if (res.data) {
|
||||
deptList.value = Array.isArray(res.data) ? res.data : []
|
||||
}
|
||||
} catch (err) {
|
||||
deptList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
// 获取图书列表
|
||||
const getBookListData = async () => {
|
||||
try {
|
||||
const res = await getBookList()
|
||||
if (res.data) {
|
||||
bookList.value = Array.isArray(res.data) ? res.data : []
|
||||
}
|
||||
} catch (err) {
|
||||
bookList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
getDeptList()
|
||||
getBookListData()
|
||||
nextTick(() => {
|
||||
if (visibleColumns.value.length === 0) {
|
||||
}
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '/@/assets/styles/modern-page.scss';
|
||||
</style>
|
||||
|
||||
153
src/views/stuwork/onlinebookscategory/form.vue
Normal file
153
src/views/stuwork/onlinebookscategory/form.vue
Normal file
@@ -0,0 +1,153 @@
|
||||
<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="120px" v-loading="loading">
|
||||
<el-form-item label="类别名称" prop="categoryName">
|
||||
<el-input
|
||||
v-model="form.categoryName"
|
||||
placeholder="请输入类别名称"
|
||||
clearable
|
||||
maxlength="30"
|
||||
show-word-limit
|
||||
style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="类别编码" prop="categoryCode">
|
||||
<el-input
|
||||
v-model="form.categoryCode"
|
||||
placeholder="请输入类别编码"
|
||||
clearable
|
||||
maxlength="30"
|
||||
show-word-limit
|
||||
:disabled="!!form.id"
|
||||
style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remarks">
|
||||
<el-input
|
||||
v-model="form.remarks"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="请输入备注"
|
||||
maxlength="250"
|
||||
show-word-limit
|
||||
style="width: 100%" />
|
||||
</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="OnlineBooksCategoryFormDialog">
|
||||
import { ref, reactive, nextTick } from 'vue'
|
||||
import { useMessage } from '/@/hooks/message'
|
||||
import { addObj, editObj, getDetail } from '/@/api/stuwork/onlinebookscategory'
|
||||
|
||||
const emit = defineEmits(['refresh'])
|
||||
|
||||
// 定义变量内容
|
||||
const dataFormRef = ref()
|
||||
const visible = ref(false)
|
||||
const loading = ref(false)
|
||||
const operType = ref('add') // add 或 edit
|
||||
|
||||
// 提交表单数据
|
||||
const form = reactive({
|
||||
id: '',
|
||||
categoryName: '',
|
||||
categoryCode: '',
|
||||
remarks: ''
|
||||
})
|
||||
|
||||
// 定义校验规则
|
||||
const dataRules = {
|
||||
categoryName: [
|
||||
{ required: true, message: '请输入类别名称', trigger: 'blur' },
|
||||
{ max: 30, message: '类别名称不能超过30个字符', trigger: 'blur' }
|
||||
],
|
||||
categoryCode: [
|
||||
{ required: true, message: '请输入类别编码', trigger: 'blur' },
|
||||
{ max: 30, message: '类别编码不能超过30个字符', trigger: 'blur' }
|
||||
],
|
||||
remarks: [
|
||||
{ max: 250, message: '备注不能超过250个字符', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
|
||||
// 打开弹窗
|
||||
const openDialog = async (type: string = 'add', row?: any) => {
|
||||
visible.value = true
|
||||
operType.value = type
|
||||
|
||||
// 重置表单数据
|
||||
nextTick(() => {
|
||||
dataFormRef.value?.resetFields()
|
||||
form.id = ''
|
||||
form.categoryName = ''
|
||||
form.categoryCode = ''
|
||||
form.remarks = ''
|
||||
|
||||
// 编辑时填充数据
|
||||
if (type === 'edit' && row) {
|
||||
loading.value = true
|
||||
getDetail(row.id)
|
||||
.then((res: any) => {
|
||||
if (res.data) {
|
||||
form.id = res.data.id || row.id
|
||||
form.categoryName = res.data.categoryName || ''
|
||||
form.categoryCode = res.data.categoryCode || ''
|
||||
form.remarks = res.data.remarks || ''
|
||||
}
|
||||
})
|
||||
.catch((err: any) => {
|
||||
useMessage().error(err.msg || '获取详情失败')
|
||||
visible.value = false
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
const onSubmit = async () => {
|
||||
if (!dataFormRef.value) return
|
||||
|
||||
await dataFormRef.value.validate(async (valid: boolean) => {
|
||||
if (!valid) return
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
const submitData: any = {
|
||||
categoryName: form.categoryName,
|
||||
categoryCode: form.categoryCode,
|
||||
remarks: form.remarks
|
||||
}
|
||||
|
||||
if (operType.value === 'add') {
|
||||
await addObj(submitData)
|
||||
useMessage().success('新增成功')
|
||||
} else {
|
||||
submitData.id = form.id
|
||||
await editObj(submitData)
|
||||
useMessage().success('编辑成功')
|
||||
}
|
||||
visible.value = false
|
||||
emit('refresh')
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg || (operType.value === 'add' ? '新增失败' : '编辑失败'))
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 暴露方法给父组件
|
||||
defineExpose({
|
||||
openDialog
|
||||
})
|
||||
</script>
|
||||
|
||||
269
src/views/stuwork/onlinebookscategory/index.vue
Normal file
269
src/views/stuwork/onlinebookscategory/index.vue
Normal file
@@ -0,0 +1,269 @@
|
||||
<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="categoryName">
|
||||
<el-input
|
||||
v-model="searchForm.categoryName"
|
||||
placeholder="请输入类别名称"
|
||||
clearable
|
||||
style="width: 200px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="类别编码" prop="categoryCode">
|
||||
<el-input
|
||||
v-model="searchForm.categoryCode"
|
||||
placeholder="请输入类别编码"
|
||||
clearable
|
||||
style="width: 200px" />
|
||||
</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="FolderAdd"
|
||||
type="primary"
|
||||
@click="formDialogRef.openDialog()">
|
||||
新增
|
||||
</el-button>
|
||||
<right-toolbar
|
||||
v-model:showSearch="showSearch"
|
||||
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-table
|
||||
:data="state.dataList"
|
||||
v-loading="state.loading"
|
||||
stripe
|
||||
:cell-style="tableStyle.cellStyle"
|
||||
:header-cell-style="tableStyle.headerCellStyle"
|
||||
class="modern-table"
|
||||
@sort-change="sortChangeHandle">
|
||||
<el-table-column type="index" label="序号" width="70" align="center">
|
||||
<template #header>
|
||||
<el-icon><List /></el-icon>
|
||||
</template>
|
||||
<template #default="{ $index }">
|
||||
{{ $index + 1 + ((state.pagination?.current || 1) - 1) * (state.pagination?.size || 10) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<template v-for="col in visibleColumnsSorted" :key="col.prop || col.label">
|
||||
<el-table-column
|
||||
v-if="checkColumnVisible(col.prop || '') && col.prop !== '序号'"
|
||||
:prop="col.prop"
|
||||
:label="col.label"
|
||||
show-overflow-tooltip
|
||||
align="center">
|
||||
<template #header>
|
||||
<el-icon><component :is="columnConfigMap[col.prop]?.icon || Document" /></el-icon>
|
||||
<span style="margin-left: 4px">{{ col.label }}</span>
|
||||
</template>
|
||||
<!-- 二维码图片 -->
|
||||
<template v-if="col.prop === 'qrCode'" #default="scope">
|
||||
<el-image
|
||||
v-if="scope.row.qrCode"
|
||||
:src="scope.row.qrCode"
|
||||
:preview-src-list="[scope.row.qrCode]"
|
||||
fit="cover"
|
||||
style="width: 80px; height: 80px; cursor: pointer; border-radius: 4px;"
|
||||
preview-teleported>
|
||||
</el-image>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
<el-table-column label="操作" width="150" align="center" fixed="right">
|
||||
<template #header>
|
||||
<el-icon><Setting /></el-icon>
|
||||
<span style="margin-left: 4px">操作</span>
|
||||
</template>
|
||||
<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>
|
||||
|
||||
<!-- 新增/编辑对话框 -->
|
||||
<FormDialog ref="formDialogRef" @refresh="getDataList()" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="OnlineBooksCategory">
|
||||
import { reactive, ref, onMounted, computed, nextTick } from 'vue'
|
||||
import { BasicTableProps, useTable } from "/@/hooks/table";
|
||||
import { fetchList, delObj } from "/@/api/stuwork/onlinebookscategory";
|
||||
import { useMessage, useMessageBox } from "/@/hooks/message";
|
||||
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
|
||||
import FormDialog from './form.vue';
|
||||
import { List, Document, Picture, Setting, Menu, Search } from '@element-plus/icons-vue'
|
||||
import { useTableColumnControl } from '/@/hooks/tableColumn'
|
||||
|
||||
// 定义变量
|
||||
const searchFormRef = ref()
|
||||
const columnControlRef = ref<any>()
|
||||
const formDialogRef = ref()
|
||||
const showSearch = ref(true)
|
||||
|
||||
// 表格列配置
|
||||
const tableColumns = [
|
||||
{ prop: 'categoryName', label: '类别名称' },
|
||||
{ prop: 'categoryCode', label: '类别编码' },
|
||||
{ prop: 'qrCode', label: '二维码' },
|
||||
{ prop: 'remarks', label: '备注' }
|
||||
]
|
||||
|
||||
// 列配置映射
|
||||
const columnConfigMap: Record<string, { icon: any }> = {
|
||||
categoryName: { icon: Document },
|
||||
categoryCode: { icon: Document },
|
||||
qrCode: { icon: Picture },
|
||||
remarks: { icon: Document }
|
||||
}
|
||||
|
||||
// 表格列控制hook
|
||||
const {
|
||||
visibleColumns,
|
||||
visibleColumnsSorted,
|
||||
checkColumnVisible,
|
||||
handleColumnChange,
|
||||
handleColumnOrderChange
|
||||
} = useTableColumnControl(tableColumns)
|
||||
|
||||
// 搜索表单
|
||||
const searchForm = reactive({
|
||||
categoryName: '',
|
||||
categoryCode: ''
|
||||
})
|
||||
|
||||
// 使用 useTable
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
queryForm: searchForm,
|
||||
pageList: fetchList,
|
||||
props: {
|
||||
item: 'records',
|
||||
totalCount: 'total'
|
||||
},
|
||||
createdIsNeed: true
|
||||
})
|
||||
|
||||
// table hook
|
||||
const {
|
||||
getDataList,
|
||||
currentChangeHandle,
|
||||
sizeChangeHandle,
|
||||
sortChangeHandle,
|
||||
tableStyle
|
||||
} = useTable(state)
|
||||
|
||||
// 查询
|
||||
const handleSearch = () => {
|
||||
getDataList()
|
||||
}
|
||||
|
||||
// 重置
|
||||
const handleReset = () => {
|
||||
searchFormRef.value?.formRef?.resetFields()
|
||||
searchForm.categoryName = ''
|
||||
searchForm.categoryCode = ''
|
||||
getDataList()
|
||||
}
|
||||
|
||||
// 编辑
|
||||
const handleEdit = (row: any) => {
|
||||
formDialogRef.value.openDialog('edit', row)
|
||||
}
|
||||
|
||||
// 删除
|
||||
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(() => {
|
||||
nextTick(() => {
|
||||
if (visibleColumns.value.length === 0) {
|
||||
}
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '/@/assets/styles/modern-page.scss';
|
||||
</style>
|
||||
|
||||
154
src/views/stuwork/psychologicalcounselingduty/form.vue
Normal file
154
src/views/stuwork/psychologicalcounselingduty/form.vue
Normal file
@@ -0,0 +1,154 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
title="新增排班"
|
||||
v-model="visible"
|
||||
:width="500"
|
||||
:close-on-click-modal="false"
|
||||
draggable>
|
||||
<el-form
|
||||
ref="dataFormRef"
|
||||
:model="form"
|
||||
:rules="dataRules"
|
||||
label-width="100px"
|
||||
v-loading="loading">
|
||||
<el-form-item label="值班日期" prop="date">
|
||||
<el-date-picker
|
||||
v-model="form.date"
|
||||
type="date"
|
||||
placeholder="选择值班日期"
|
||||
format="YYYY-MM-DD"
|
||||
value-format="YYYY-MM-DD"
|
||||
:disabled-date="disabledDate"
|
||||
style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="预约师" prop="teacherUserName">
|
||||
<el-select
|
||||
v-model="form.teacherUserName"
|
||||
placeholder="请选择预约师"
|
||||
filterable
|
||||
clearable
|
||||
style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in teacherList"
|
||||
:key="item.userName"
|
||||
:label="(item.realName || '') + (item.userName ? ` (${item.userName})` : '')"
|
||||
:value="item.userName" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="周类型" prop="weekType">
|
||||
<el-select
|
||||
v-model="form.weekType"
|
||||
placeholder="请选择"
|
||||
clearable
|
||||
style="width: 100%">
|
||||
<el-option label="单周" value="single" />
|
||||
<el-option label="双周" value="double" />
|
||||
</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="PsychologicalCounselingDutyFormDialog">
|
||||
import { ref, reactive, nextTick, watch } from 'vue'
|
||||
import { useMessage } from '/@/hooks/message'
|
||||
import { saveDuty } from '/@/api/stuwork/psychologicalcounselingduty'
|
||||
import { getList } from '/@/api/stuwork/psychologicalcounselingteacher'
|
||||
|
||||
const props = defineProps<{
|
||||
year?: number
|
||||
month?: number
|
||||
}>()
|
||||
|
||||
const emit = defineEmits(['refresh'])
|
||||
|
||||
const dataFormRef = ref()
|
||||
const visible = ref(false)
|
||||
const loading = ref(false)
|
||||
const teacherList = ref<any[]>([])
|
||||
|
||||
const form = reactive({
|
||||
date: '',
|
||||
teacherUserName: '',
|
||||
weekType: 'single'
|
||||
})
|
||||
|
||||
const dataRules = {
|
||||
date: [{ required: true, message: '请选择值班日期', trigger: 'change' }],
|
||||
teacherUserName: [{ required: true, message: '请选择预约师', trigger: 'change' }]
|
||||
}
|
||||
|
||||
// 限制只能选当前年月的日期
|
||||
const disabledDate = (time: Date) => {
|
||||
if (!props.year || !props.month) return false
|
||||
const y = time.getFullYear()
|
||||
const m = time.getMonth() + 1
|
||||
if (y !== props.year) return true
|
||||
if (m !== props.month) return true
|
||||
return false
|
||||
}
|
||||
|
||||
const loadTeacherList = async () => {
|
||||
try {
|
||||
const res = await getList()
|
||||
if (res.data && Array.isArray(res.data)) {
|
||||
teacherList.value = res.data
|
||||
} else {
|
||||
teacherList.value = []
|
||||
}
|
||||
} catch (err) {
|
||||
teacherList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
const openDialog = () => {
|
||||
visible.value = true
|
||||
nextTick(() => {
|
||||
dataFormRef.value?.resetFields()
|
||||
form.date = ''
|
||||
form.teacherUserName = ''
|
||||
form.weekType = 'single'
|
||||
})
|
||||
}
|
||||
|
||||
const onSubmit = async () => {
|
||||
if (!dataFormRef.value) return
|
||||
await dataFormRef.value.validate(async (valid: boolean) => {
|
||||
if (!valid) return
|
||||
loading.value = true
|
||||
try {
|
||||
await saveDuty([
|
||||
{
|
||||
date: form.date,
|
||||
teacherUserName: form.teacherUserName,
|
||||
weekType: form.weekType || 'single'
|
||||
}
|
||||
])
|
||||
useMessage().success('排班成功')
|
||||
visible.value = false
|
||||
emit('refresh')
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg || '排班失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
watch(visible, (v) => {
|
||||
if (v) loadTeacherList()
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
openDialog
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
</style>
|
||||
310
src/views/stuwork/psychologicalcounselingduty/index.vue
Normal file
310
src/views/stuwork/psychologicalcounselingduty/index.vue
Normal file
@@ -0,0 +1,310 @@
|
||||
<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="getDataList"
|
||||
class="search-form">
|
||||
<el-form-item label="年份" prop="year">
|
||||
<el-select
|
||||
v-model="searchForm.year"
|
||||
placeholder="请选择年份"
|
||||
clearable
|
||||
style="width: 120px">
|
||||
<el-option
|
||||
v-for="y in yearOptions"
|
||||
:key="y"
|
||||
:label="y + '年'"
|
||||
:value="y" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="月份" prop="month">
|
||||
<el-select
|
||||
v-model="searchForm.month"
|
||||
placeholder="请选择月份"
|
||||
clearable
|
||||
style="width: 120px">
|
||||
<el-option
|
||||
v-for="m in 12"
|
||||
:key="m"
|
||||
:label="m + '月'"
|
||||
:value="m" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="getDataList">查询</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"><Calendar /></el-icon>
|
||||
值班管理列表
|
||||
</span>
|
||||
<div class="header-actions">
|
||||
<el-button
|
||||
icon="FolderAdd"
|
||||
type="primary"
|
||||
@click="formDialogRef?.openDialog()">
|
||||
新增排班
|
||||
</el-button>
|
||||
<el-button
|
||||
icon="Delete"
|
||||
type="danger"
|
||||
plain
|
||||
:disabled="!dataList.length"
|
||||
@click="handleClearMonth">
|
||||
一键清空本月
|
||||
</el-button>
|
||||
<right-toolbar
|
||||
v-model:showSearch="showSearch"
|
||||
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-table
|
||||
:data="dataList"
|
||||
v-loading="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 #header>
|
||||
<el-icon><List /></el-icon>
|
||||
</template>
|
||||
<template #default="{ $index }">
|
||||
{{ $index + 1 }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<template v-for="col in visibleColumnsSorted" :key="col.prop || col.label">
|
||||
<el-table-column
|
||||
v-if="checkColumnVisible(col.prop || '') && col.prop !== '操作'"
|
||||
:prop="col.prop"
|
||||
:label="col.label"
|
||||
:min-width="col.minWidth"
|
||||
:width="col.width"
|
||||
show-overflow-tooltip
|
||||
align="center">
|
||||
<template #header>
|
||||
<el-icon v-if="col.icon"><component :is="col.icon" /></el-icon>
|
||||
<span :style="{ marginLeft: col.icon ? '4px' : '0' }">{{ col.label }}</span>
|
||||
</template>
|
||||
<template v-if="col.prop === 'reservation'" #default="scope">
|
||||
<el-tag :type="scope.row.reservation === '1' ? 'success' : 'info'" size="small">
|
||||
{{ scope.row.reservation === '1' ? '是' : '否' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
<el-table-column label="操作" width="100" align="center" fixed="right">
|
||||
<template #header>
|
||||
<el-icon><Setting /></el-icon>
|
||||
<span style="margin-left: 4px">操作</span>
|
||||
</template>
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
icon="Delete"
|
||||
link
|
||||
type="danger"
|
||||
@click="handleClearOne(scope.row)">
|
||||
清除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<template #empty>
|
||||
<el-empty description="暂无值班数据,请选择年月查询或新增排班" :image-size="120">
|
||||
<el-button type="primary" icon="FolderAdd" @click="formDialogRef?.openDialog()">新增排班</el-button>
|
||||
</el-empty>
|
||||
</template>
|
||||
</el-table>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<!-- 新增排班弹窗 -->
|
||||
<FormDialog
|
||||
ref="formDialogRef"
|
||||
:year="searchForm.year"
|
||||
:month="searchForm.month"
|
||||
@refresh="getDataList" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="PsychologicalCounselingDuty">
|
||||
import { reactive, ref, computed, onMounted, defineAsyncComponent } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { useMessage, useMessageBox } from '/@/hooks/message'
|
||||
import { getDutyByMonth, clearDuty, clearOneDuty } from '/@/api/stuwork/psychologicalcounselingduty'
|
||||
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
|
||||
import { List, Calendar, UserFilled, Phone, Document, Setting, Menu, Search } from '@element-plus/icons-vue'
|
||||
import { useTableColumnControl } from '/@/hooks/tableColumn'
|
||||
|
||||
const FormDialog = defineAsyncComponent(() => import('./form.vue'))
|
||||
|
||||
const route = useRoute()
|
||||
const formDialogRef = ref()
|
||||
const searchFormRef = ref()
|
||||
const columnControlRef = ref<any>()
|
||||
const showSearch = ref(true)
|
||||
const loading = ref(false)
|
||||
const dataList = ref<any[]>([])
|
||||
|
||||
// 年份选项:当前年前后各 5 年
|
||||
const yearOptions = computed(() => {
|
||||
const y = new Date().getFullYear()
|
||||
return Array.from({ length: 11 }, (_, i) => y - 5 + i)
|
||||
})
|
||||
|
||||
// 表格列配置(与其它页面一致,icon 写在列上)
|
||||
const tableColumns = [
|
||||
{ prop: 'date', label: '值班日期', icon: Calendar },
|
||||
{ prop: 'dutyTime', label: '值班时间', icon: Calendar },
|
||||
{ prop: 'realName', label: '教师姓名', icon: UserFilled },
|
||||
{ prop: 'userName', label: '用户名', icon: UserFilled },
|
||||
{ prop: 'phone', label: '联系方式', icon: Phone, minWidth: 120 },
|
||||
{ prop: 'reservation', label: '是否预约', icon: Document }
|
||||
]
|
||||
|
||||
const {
|
||||
visibleColumns,
|
||||
visibleColumnsSorted,
|
||||
checkColumnVisible,
|
||||
handleColumnChange,
|
||||
handleColumnOrderChange
|
||||
} = useTableColumnControl(tableColumns, route.path)
|
||||
|
||||
// 表格样式(与 useTable 一致)
|
||||
const tableStyle = {
|
||||
cellStyle: {},
|
||||
headerCellStyle: {}
|
||||
}
|
||||
|
||||
// 搜索表单
|
||||
const searchForm = reactive({
|
||||
year: new Date().getFullYear(),
|
||||
month: new Date().getMonth() + 1
|
||||
})
|
||||
|
||||
const getDataList = async () => {
|
||||
if (!searchForm.year || !searchForm.month) {
|
||||
useMessage().warning('请选择年份和月份')
|
||||
return
|
||||
}
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await getDutyByMonth({
|
||||
year: searchForm.year,
|
||||
month: searchForm.month
|
||||
})
|
||||
dataList.value = Array.isArray(res.data) ? res.data : []
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg || '获取值班表失败')
|
||||
dataList.value = []
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleReset = () => {
|
||||
const now = new Date()
|
||||
searchForm.year = now.getFullYear()
|
||||
searchForm.month = now.getMonth() + 1
|
||||
getDataList()
|
||||
}
|
||||
|
||||
const handleClearMonth = async () => {
|
||||
if (!searchForm.year || !searchForm.month) {
|
||||
useMessage().warning('请先选择年份和月份')
|
||||
return
|
||||
}
|
||||
try {
|
||||
await useMessageBox().confirm(
|
||||
`确定要清空 ${searchForm.year}年${searchForm.month}月 的全部值班吗?`,
|
||||
'提示',
|
||||
{ confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }
|
||||
)
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
try {
|
||||
await clearDuty({
|
||||
year: Number(searchForm.year),
|
||||
month: Number(searchForm.month)
|
||||
})
|
||||
useMessage().success('清空成功')
|
||||
getDataList()
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg || '清空失败')
|
||||
}
|
||||
}
|
||||
|
||||
const handleClearOne = async (row: any) => {
|
||||
const dateStr = row.date || row.dutyTime || ''
|
||||
if (!dateStr) {
|
||||
useMessage().warning('无法获取日期')
|
||||
return
|
||||
}
|
||||
const day = dateStr.split(' ')[0] || dateStr
|
||||
try {
|
||||
await useMessageBox().confirm(`确定要清除 ${day} 的值班吗?`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
try {
|
||||
await clearOneDuty({ days: day })
|
||||
useMessage().success('清除成功')
|
||||
getDataList()
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg || '清除失败')
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getDataList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '/@/assets/styles/modern-page.scss';
|
||||
</style>
|
||||
309
src/views/stuwork/psychologicalcounselingreservation/form.vue
Normal file
309
src/views/stuwork/psychologicalcounselingreservation/form.vue
Normal file
@@ -0,0 +1,309 @@
|
||||
<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="teacherNo">
|
||||
<el-select
|
||||
v-model="form.teacherNo"
|
||||
placeholder="请选择值班教师"
|
||||
filterable
|
||||
clearable
|
||||
style="width: 100%"
|
||||
:disabled="!!form.id"
|
||||
@change="handleTeacherChange">
|
||||
<el-option
|
||||
v-for="item in teacherList"
|
||||
:key="item.userName"
|
||||
:label="(item.realName || '') + (item.userName ? ` (${item.userName})` : '')"
|
||||
:value="item.userName" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="预约时间" prop="reservationTime">
|
||||
<el-date-picker
|
||||
v-model="form.reservationTime"
|
||||
type="datetime"
|
||||
placeholder="选择预约时间"
|
||||
format="YYYY-MM-DD HH:mm"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
style="width: 100%"
|
||||
:disabled="!!form.id" />
|
||||
</el-form-item>
|
||||
<el-form-item label="班号" prop="classCode">
|
||||
<el-select
|
||||
v-model="form.classCode"
|
||||
placeholder="请选择班号"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 100%"
|
||||
:disabled="!!form.id"
|
||||
@change="handleClassChange">
|
||||
<el-option
|
||||
v-for="item in classList"
|
||||
:key="item.classCode"
|
||||
:label="item.classNo"
|
||||
:value="item.classCode" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="学号" prop="stuNo">
|
||||
<el-select
|
||||
v-model="form.stuNo"
|
||||
:placeholder="form.classCode ? (studentLoading ? '加载中...' : '请选择学生') : '请先选择班号'"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 100%"
|
||||
:disabled="!form.classCode || !!form.id || studentLoading"
|
||||
:loading="studentLoading"
|
||||
@change="handleStudentChange">
|
||||
<el-option
|
||||
v-for="item in studentList"
|
||||
:key="item.stuNo"
|
||||
:label="`${item.realName || item.stuName || ''} (${item.stuNo})`"
|
||||
:value="item.stuNo" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="学生姓名" prop="stuName">
|
||||
<el-input v-model="form.stuName" placeholder="选学生后自动带出" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="联系方式" prop="phone">
|
||||
<el-input v-model="form.phone" placeholder="选学生后自动带出" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="学生留言" prop="remarks">
|
||||
<el-input
|
||||
v-model="form.remarks"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="请输入学生留言"
|
||||
maxlength="250"
|
||||
show-word-limit
|
||||
:disabled="!!form.id" />
|
||||
</el-form-item>
|
||||
<template v-if="form.id">
|
||||
<el-form-item label="是否处理" prop="isHandle">
|
||||
<el-select
|
||||
v-model="form.isHandle"
|
||||
placeholder="请选择"
|
||||
clearable
|
||||
style="width: 100%">
|
||||
<el-option label="是" value="1" />
|
||||
<el-option label="否" value="0" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="老师备注" prop="teaRemark">
|
||||
<el-input
|
||||
v-model="form.teaRemark"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="请输入老师备注"
|
||||
maxlength="250"
|
||||
show-word-limit />
|
||||
</el-form-item>
|
||||
</template>
|
||||
</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="PsychologicalCounselingReservationFormDialog">
|
||||
import { ref, reactive, nextTick, onMounted } from 'vue'
|
||||
import { useMessage } from '/@/hooks/message'
|
||||
import { addObj, editObj, getDetail } from '/@/api/stuwork/psychologicalcounselingreservation'
|
||||
import { getList } from '/@/api/stuwork/psychologicalcounselingteacher'
|
||||
import { getClassListByRole } from '/@/api/basic/basicclass'
|
||||
import { queryAllStudentByClassCode } from '/@/api/basic/basicstudent'
|
||||
|
||||
const emit = defineEmits(['refresh'])
|
||||
|
||||
const dataFormRef = ref()
|
||||
const visible = ref(false)
|
||||
const loading = ref(false)
|
||||
const studentLoading = ref(false)
|
||||
const operType = ref('add')
|
||||
const teacherList = ref<any[]>([])
|
||||
const classList = ref<any[]>([])
|
||||
const studentList = ref<any[]>([])
|
||||
|
||||
const form = reactive({
|
||||
id: '',
|
||||
teacherNo: '',
|
||||
realName: '',
|
||||
reservationTime: '',
|
||||
classCode: '',
|
||||
classNo: '',
|
||||
stuNo: '',
|
||||
stuName: '',
|
||||
phone: '',
|
||||
remarks: '',
|
||||
isHandle: '0',
|
||||
teaRemark: ''
|
||||
})
|
||||
|
||||
const dataRules = {
|
||||
teacherNo: [{ required: true, message: '请选择值班教师', trigger: 'change' }],
|
||||
reservationTime: [{ required: true, message: '请选择预约时间', trigger: 'change' }],
|
||||
classCode: [{ required: true, message: '请选择班号', trigger: 'change' }],
|
||||
stuNo: [{ required: true, message: '请选择学生', trigger: 'change' }]
|
||||
}
|
||||
|
||||
const handleTeacherChange = (val: string) => {
|
||||
const item = teacherList.value.find((t: any) => t.userName === val)
|
||||
form.realName = item ? (item.realName || '') : ''
|
||||
}
|
||||
|
||||
const handleClassChange = async () => {
|
||||
form.stuNo = ''
|
||||
form.stuName = ''
|
||||
form.phone = ''
|
||||
studentList.value = []
|
||||
if (!form.classCode) return
|
||||
studentLoading.value = true
|
||||
try {
|
||||
const res = await queryAllStudentByClassCode(form.classCode)
|
||||
studentList.value = Array.isArray(res.data) ? res.data : []
|
||||
} catch (err) {
|
||||
studentList.value = []
|
||||
} finally {
|
||||
studentLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleStudentChange = (val: string) => {
|
||||
const item = studentList.value.find((s: any) => s.stuNo === val)
|
||||
if (item) {
|
||||
form.stuName = item.realName || item.stuName || ''
|
||||
form.phone = item.phone || item.mobile || ''
|
||||
} else {
|
||||
form.stuName = ''
|
||||
form.phone = ''
|
||||
}
|
||||
}
|
||||
|
||||
const openDialog = (type: string = 'add', row?: any) => {
|
||||
operType.value = type
|
||||
visible.value = true
|
||||
|
||||
nextTick(() => {
|
||||
dataFormRef.value?.resetFields()
|
||||
form.id = ''
|
||||
form.teacherNo = ''
|
||||
form.realName = ''
|
||||
form.reservationTime = ''
|
||||
form.classCode = ''
|
||||
form.classNo = ''
|
||||
form.stuNo = ''
|
||||
form.stuName = ''
|
||||
form.phone = ''
|
||||
form.remarks = ''
|
||||
form.isHandle = '0'
|
||||
form.teaRemark = ''
|
||||
studentList.value = []
|
||||
|
||||
if (type === 'edit' && row) {
|
||||
form.id = row.id
|
||||
form.teacherNo = row.teacherNo || ''
|
||||
form.realName = row.realName || ''
|
||||
form.reservationTime = row.reservationTime || ''
|
||||
form.classNo = row.classNo || ''
|
||||
const cls = classList.value.find((c: any) => c.classNo === row.classNo)
|
||||
form.classCode = cls ? cls.classCode : (row.classCode || '')
|
||||
form.stuNo = row.stuNo || ''
|
||||
form.stuName = row.stuName || ''
|
||||
form.phone = row.phone || ''
|
||||
form.remarks = row.remarks || ''
|
||||
form.isHandle = row.isHandle !== undefined && row.isHandle !== null ? String(row.isHandle) : '0'
|
||||
form.teaRemark = row.teaRemark || ''
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const onSubmit = async () => {
|
||||
if (!dataFormRef.value) return
|
||||
await dataFormRef.value.validate(async (valid: boolean) => {
|
||||
if (!valid) return
|
||||
|
||||
const selectedClass = classList.value.find((c: any) => c.classCode === form.classCode)
|
||||
const classNo = selectedClass ? selectedClass.classNo : form.classNo || form.classCode
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
if (operType.value === 'add') {
|
||||
await addObj({
|
||||
teacherNo: form.teacherNo,
|
||||
realName: form.realName,
|
||||
reservationTime: form.reservationTime ? (form.reservationTime.length === 10 ? form.reservationTime + ' 00:00:00' : form.reservationTime) : '',
|
||||
classNo,
|
||||
stuNo: form.stuNo,
|
||||
stuName: form.stuName,
|
||||
phone: form.phone,
|
||||
remarks: form.remarks
|
||||
})
|
||||
useMessage().success('新增成功')
|
||||
} else {
|
||||
await editObj({
|
||||
id: form.id,
|
||||
teacherNo: form.teacherNo,
|
||||
realName: form.realName,
|
||||
reservationTime: form.reservationTime,
|
||||
classNo: form.classNo || classNo,
|
||||
stuNo: form.stuNo,
|
||||
stuName: form.stuName,
|
||||
phone: form.phone,
|
||||
remarks: form.remarks,
|
||||
isHandle: form.isHandle,
|
||||
teaRemark: form.teaRemark || ''
|
||||
})
|
||||
useMessage().success('编辑成功')
|
||||
}
|
||||
visible.value = false
|
||||
emit('refresh')
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg || (operType.value === 'add' ? '新增失败' : '编辑失败'))
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const loadTeacherList = async () => {
|
||||
try {
|
||||
const res = await getList()
|
||||
teacherList.value = Array.isArray(res.data) ? res.data : []
|
||||
} catch (err) {
|
||||
teacherList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
const loadClassList = async () => {
|
||||
try {
|
||||
const res = await getClassListByRole()
|
||||
classList.value = Array.isArray(res.data) ? res.data : []
|
||||
} catch (err) {
|
||||
classList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadTeacherList()
|
||||
loadClassList()
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
openDialog
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
</style>
|
||||
296
src/views/stuwork/psychologicalcounselingreservation/index.vue
Normal file
296
src/views/stuwork/psychologicalcounselingreservation/index.vue
Normal file
@@ -0,0 +1,296 @@
|
||||
<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="stuNo">
|
||||
<el-input
|
||||
v-model="searchForm.stuNo"
|
||||
placeholder="请输入学号"
|
||||
clearable
|
||||
style="width: 160px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="班号" prop="classNo">
|
||||
<el-input
|
||||
v-model="searchForm.classNo"
|
||||
placeholder="请输入班号"
|
||||
clearable
|
||||
style="width: 160px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="预约时间" prop="reservationTime">
|
||||
<el-date-picker
|
||||
v-model="searchForm.reservationTime"
|
||||
type="date"
|
||||
placeholder="选择预约时间"
|
||||
format="YYYY-MM-DD"
|
||||
value-format="YYYY-MM-DD"
|
||||
clearable
|
||||
style="width: 180px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="是否处理" prop="isHandle">
|
||||
<el-select
|
||||
v-model="searchForm.isHandle"
|
||||
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 type="primary" 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="FolderAdd"
|
||||
type="primary"
|
||||
@click="formDialogRef?.openDialog()">
|
||||
新增
|
||||
</el-button>
|
||||
<right-toolbar
|
||||
v-model:showSearch="showSearch"
|
||||
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-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 #header>
|
||||
<el-icon><List /></el-icon>
|
||||
</template>
|
||||
<template #default="{ $index }">
|
||||
{{ $index + 1 + ((state.pagination?.current || 1) - 1) * (state.pagination?.size || 10) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<template v-for="col in visibleColumnsSorted" :key="col.prop || col.label">
|
||||
<el-table-column
|
||||
v-if="checkColumnVisible(col.prop || '') && col.prop !== '操作'"
|
||||
:prop="col.prop"
|
||||
:label="col.label"
|
||||
:min-width="col.minWidth"
|
||||
:width="col.width"
|
||||
show-overflow-tooltip
|
||||
align="center">
|
||||
<template #header>
|
||||
<el-icon v-if="col.icon"><component :is="col.icon" /></el-icon>
|
||||
<span :style="{ marginLeft: col.icon ? '4px' : '0' }">{{ col.label }}</span>
|
||||
</template>
|
||||
<template v-if="col.prop === 'isHandle'" #default="scope">
|
||||
<el-tag :type="scope.row.isHandle === '1' ? 'success' : 'info'" size="small">
|
||||
{{ scope.row.isHandle === '1' ? '是' : '否' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
<el-table-column label="操作" width="150" align="center" fixed="right">
|
||||
<template #header>
|
||||
<el-icon><Setting /></el-icon>
|
||||
<span style="margin-left: 4px">操作</span>
|
||||
</template>
|
||||
<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">
|
||||
<el-button type="primary" icon="FolderAdd" @click="formDialogRef?.openDialog()">新增预约记录</el-button>
|
||||
</el-empty>
|
||||
</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" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="PsychologicalCounselingReservation">
|
||||
import { reactive, ref, onMounted, defineAsyncComponent } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { BasicTableProps, useTable } from '/@/hooks/table'
|
||||
import { fetchList, delObj } from '/@/api/stuwork/psychologicalcounselingreservation'
|
||||
import { useMessage, useMessageBox } from '/@/hooks/message'
|
||||
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
|
||||
import {
|
||||
List,
|
||||
Calendar,
|
||||
UserFilled,
|
||||
Phone,
|
||||
EditPen,
|
||||
Setting,
|
||||
Menu,
|
||||
Search,
|
||||
Document,
|
||||
FolderAdd
|
||||
} from '@element-plus/icons-vue'
|
||||
import { useTableColumnControl } from '/@/hooks/tableColumn'
|
||||
|
||||
const FormDialog = defineAsyncComponent(() => import('./form.vue'))
|
||||
|
||||
const route = useRoute()
|
||||
const formDialogRef = ref()
|
||||
const searchFormRef = ref()
|
||||
const columnControlRef = ref<any>()
|
||||
const showSearch = ref(true)
|
||||
|
||||
// 表格列配置(与其它页面一致,icon 写在列上)
|
||||
const tableColumns = [
|
||||
{ prop: 'reservationTime', label: '预约时间', icon: Calendar, minWidth: 160 },
|
||||
{ prop: 'realName', label: '值班老师', icon: UserFilled },
|
||||
{ prop: 'classNo', label: '班号', icon: Document },
|
||||
{ prop: 'stuNo', label: '学号', icon: UserFilled },
|
||||
{ prop: 'stuName', label: '学生姓名', icon: UserFilled },
|
||||
{ prop: 'phone', label: '联系方式', icon: Phone, minWidth: 120 },
|
||||
{ prop: 'isHandle', label: '是否处理', icon: Document },
|
||||
{ prop: 'teaRemark', label: '老师备注', icon: EditPen, minWidth: 120 },
|
||||
{ prop: 'remarks', label: '学生留言', icon: EditPen, minWidth: 120 }
|
||||
]
|
||||
|
||||
const {
|
||||
visibleColumns,
|
||||
visibleColumnsSorted,
|
||||
checkColumnVisible,
|
||||
handleColumnChange,
|
||||
handleColumnOrderChange
|
||||
} = useTableColumnControl(tableColumns, route.path)
|
||||
|
||||
// 搜索表单
|
||||
const searchForm = reactive({
|
||||
stuNo: '',
|
||||
classNo: '',
|
||||
reservationTime: '',
|
||||
isHandle: ''
|
||||
})
|
||||
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
queryForm: searchForm,
|
||||
pageList: fetchList,
|
||||
props: {
|
||||
item: 'records',
|
||||
totalCount: 'total'
|
||||
},
|
||||
createdIsNeed: true
|
||||
})
|
||||
|
||||
const {
|
||||
getDataList,
|
||||
currentChangeHandle,
|
||||
sizeChangeHandle,
|
||||
tableStyle
|
||||
} = useTable(state)
|
||||
|
||||
const handleSearch = () => {
|
||||
getDataList()
|
||||
}
|
||||
|
||||
const handleReset = () => {
|
||||
searchFormRef.value?.resetFields()
|
||||
searchForm.stuNo = ''
|
||||
searchForm.classNo = ''
|
||||
searchForm.reservationTime = ''
|
||||
searchForm.isHandle = ''
|
||||
getDataList()
|
||||
}
|
||||
|
||||
const handleEdit = (row: any) => {
|
||||
formDialogRef.value?.openDialog('edit', row)
|
||||
}
|
||||
|
||||
const handleDelete = async (row: any) => {
|
||||
try {
|
||||
await useMessageBox().confirm('确定要删除该预约记录吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
try {
|
||||
await delObj([row.id])
|
||||
useMessage().success('删除成功')
|
||||
getDataList()
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg || '删除失败')
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '/@/assets/styles/modern-page.scss';
|
||||
</style>
|
||||
186
src/views/stuwork/psychologicalcounselingteacher/form.vue
Normal file
186
src/views/stuwork/psychologicalcounselingteacher/form.vue
Normal file
@@ -0,0 +1,186 @@
|
||||
<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="userName">
|
||||
<el-select
|
||||
v-model="form.userName"
|
||||
placeholder="请选择教师"
|
||||
filterable
|
||||
clearable
|
||||
style="width: 100%"
|
||||
:disabled="!!form.id"
|
||||
@change="handleTeacherChange">
|
||||
<el-option
|
||||
v-for="item in teacherList"
|
||||
:key="item.teacherNo || item.userName"
|
||||
:label="(item.realName || item.name) + (item.teacherNo ? ` (${item.teacherNo})` : '')"
|
||||
:value="item.teacherNo || item.userName">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="真实姓名" prop="realName">
|
||||
<el-input
|
||||
v-model="form.realName"
|
||||
placeholder="选教师后自动带出,可修改"
|
||||
clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="联系方式" prop="phone">
|
||||
<el-input
|
||||
v-model="form.phone"
|
||||
placeholder="请输入联系方式"
|
||||
clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remarks">
|
||||
<el-input
|
||||
v-model="form.remarks"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
placeholder="请输入备注"
|
||||
maxlength="250"
|
||||
show-word-limit />
|
||||
</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="PsychologicalCounselingTeacherFormDialog">
|
||||
import { ref, reactive, nextTick, onMounted } from 'vue'
|
||||
import { useMessage } from '/@/hooks/message'
|
||||
import { addObj, editObj, getDetail } from '/@/api/stuwork/psychologicalcounselingteacher'
|
||||
import { getTeacherBaseList } from '/@/api/professional/professionaluser/teacherbase'
|
||||
|
||||
const emit = defineEmits(['refresh'])
|
||||
|
||||
const dataFormRef = ref()
|
||||
const visible = ref(false)
|
||||
const loading = ref(false)
|
||||
const operType = ref('add')
|
||||
const teacherList = ref<any[]>([])
|
||||
|
||||
const form = reactive({
|
||||
id: '',
|
||||
userName: '',
|
||||
realName: '',
|
||||
phone: '',
|
||||
remarks: ''
|
||||
})
|
||||
|
||||
const dataRules = {
|
||||
userName: [
|
||||
{ required: true, message: '请选择教师', trigger: 'change' }
|
||||
]
|
||||
}
|
||||
|
||||
const handleTeacherChange = (val: string) => {
|
||||
const item = teacherList.value.find(
|
||||
(t: any) => (t.teacherNo || t.userName) === val
|
||||
)
|
||||
if (item) {
|
||||
form.realName = item.realName || item.name || ''
|
||||
form.phone = item.phone || item.tel || ''
|
||||
} else {
|
||||
form.realName = ''
|
||||
form.phone = ''
|
||||
}
|
||||
}
|
||||
|
||||
const openDialog = (type: string = 'add', row?: any) => {
|
||||
operType.value = type
|
||||
visible.value = true
|
||||
|
||||
nextTick(() => {
|
||||
dataFormRef.value?.resetFields()
|
||||
form.id = ''
|
||||
form.userName = ''
|
||||
form.realName = ''
|
||||
form.phone = ''
|
||||
form.remarks = ''
|
||||
|
||||
if (type === 'edit' && row) {
|
||||
form.id = row.id
|
||||
form.userName = row.userName || ''
|
||||
form.realName = row.realName || ''
|
||||
form.phone = row.phone || ''
|
||||
form.remarks = row.remarks || ''
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
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({
|
||||
userName: form.userName,
|
||||
realName: form.realName,
|
||||
phone: form.phone,
|
||||
remarks: form.remarks
|
||||
})
|
||||
useMessage().success('新增成功')
|
||||
} else {
|
||||
await editObj({
|
||||
id: form.id,
|
||||
userName: form.userName,
|
||||
realName: form.realName,
|
||||
phone: form.phone,
|
||||
remarks: form.remarks
|
||||
})
|
||||
useMessage().success('编辑成功')
|
||||
}
|
||||
visible.value = false
|
||||
emit('refresh')
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg || (operType.value === 'add' ? '新增失败' : '编辑失败'))
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const loadTeacherList = async () => {
|
||||
try {
|
||||
const res = await getTeacherBaseList()
|
||||
if (res.data && Array.isArray(res.data)) {
|
||||
teacherList.value = res.data
|
||||
} else {
|
||||
teacherList.value = []
|
||||
}
|
||||
} catch (err) {
|
||||
teacherList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadTeacherList()
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
openDialog
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.mb20 {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
244
src/views/stuwork/psychologicalcounselingteacher/index.vue
Normal file
244
src/views/stuwork/psychologicalcounselingteacher/index.vue
Normal file
@@ -0,0 +1,244 @@
|
||||
<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="realName">
|
||||
<el-input
|
||||
v-model="searchForm.realName"
|
||||
placeholder="请输入教师姓名"
|
||||
clearable
|
||||
style="width: 200px" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<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-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
|
||||
v-model:showSearch="showSearch"
|
||||
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-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 #header>
|
||||
<el-icon><List /></el-icon>
|
||||
</template>
|
||||
<template #default="{ $index }">
|
||||
{{ $index + 1 + ((state.pagination?.current || 1) - 1) * (state.pagination?.size || 10) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<template v-for="col in visibleColumnsSorted" :key="col.prop || col.label">
|
||||
<el-table-column
|
||||
v-if="checkColumnVisible(col.prop || '') && col.prop !== '操作'"
|
||||
:prop="col.prop"
|
||||
:label="col.label"
|
||||
:min-width="col.minWidth"
|
||||
:width="col.width"
|
||||
show-overflow-tooltip
|
||||
align="center">
|
||||
<template #header>
|
||||
<el-icon v-if="col.icon"><component :is="col.icon" /></el-icon>
|
||||
<span :style="{ marginLeft: col.icon ? '4px' : '0' }">{{ col.label }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
<el-table-column label="操作" width="150" align="center" fixed="right">
|
||||
<template #header>
|
||||
<el-icon><Setting /></el-icon>
|
||||
<span style="margin-left: 4px">操作</span>
|
||||
</template>
|
||||
<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">
|
||||
<el-button type="primary" icon="FolderAdd" @click="formDialogRef?.openDialog()">新增预约师</el-button>
|
||||
</el-empty>
|
||||
</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" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="PsychologicalCounselingTeacher">
|
||||
import { reactive, ref, onMounted, defineAsyncComponent } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { BasicTableProps, useTable } from '/@/hooks/table'
|
||||
import { fetchList, delObj } from '/@/api/stuwork/psychologicalcounselingteacher'
|
||||
import { useMessage, useMessageBox } from '/@/hooks/message'
|
||||
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
|
||||
import { List, UserFilled, Phone, EditPen, Setting, Menu, Search, Document, FolderAdd } from '@element-plus/icons-vue'
|
||||
import { useTableColumnControl } from '/@/hooks/tableColumn'
|
||||
|
||||
const FormDialog = defineAsyncComponent(() => import('./form.vue'))
|
||||
|
||||
const route = useRoute()
|
||||
const formDialogRef = ref()
|
||||
const searchFormRef = ref()
|
||||
const columnControlRef = ref<any>()
|
||||
const showSearch = ref(true)
|
||||
|
||||
// 表格列配置(与其它页面一致,icon 写在列上)
|
||||
const tableColumns = [
|
||||
{ prop: 'userName', label: '用户名', icon: UserFilled },
|
||||
{ prop: 'realName', label: '教师姓名', icon: UserFilled },
|
||||
{ prop: 'phone', label: '联系方式', icon: Phone, minWidth: 120 },
|
||||
{ prop: 'remarks', label: '备注', icon: EditPen, minWidth: 150 }
|
||||
]
|
||||
|
||||
const {
|
||||
visibleColumns,
|
||||
visibleColumnsSorted,
|
||||
checkColumnVisible,
|
||||
handleColumnChange,
|
||||
handleColumnOrderChange
|
||||
} = useTableColumnControl(tableColumns, route.path)
|
||||
|
||||
// 搜索表单
|
||||
const searchForm = reactive({
|
||||
realName: ''
|
||||
})
|
||||
|
||||
// 配置 useTable
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
queryForm: searchForm,
|
||||
pageList: fetchList,
|
||||
props: {
|
||||
item: 'records',
|
||||
totalCount: 'total'
|
||||
},
|
||||
createdIsNeed: true
|
||||
})
|
||||
|
||||
const {
|
||||
getDataList,
|
||||
currentChangeHandle,
|
||||
sizeChangeHandle,
|
||||
tableStyle
|
||||
} = useTable(state)
|
||||
|
||||
const handleSearch = () => {
|
||||
getDataList()
|
||||
}
|
||||
|
||||
const handleReset = () => {
|
||||
searchFormRef.value?.resetFields()
|
||||
searchForm.realName = ''
|
||||
getDataList()
|
||||
}
|
||||
|
||||
const handleEdit = (row: any) => {
|
||||
formDialogRef.value?.openDialog('edit', row)
|
||||
}
|
||||
|
||||
const handleDelete = async (row: any) => {
|
||||
try {
|
||||
await useMessageBox().confirm('确定要删除该预约师吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
try {
|
||||
await delObj([row.id])
|
||||
useMessage().success('删除成功')
|
||||
getDataList()
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg || '删除失败')
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '/@/assets/styles/modern-page.scss';
|
||||
</style>
|
||||
@@ -31,12 +31,20 @@
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item label="奖项名称" prop="ruleName">
|
||||
<el-input
|
||||
v-model="form.ruleName"
|
||||
placeholder="请输入奖项名称"
|
||||
<el-form-item label="奖项名称" prop="ruleId">
|
||||
<el-select
|
||||
v-model="form.ruleId"
|
||||
placeholder="请选择奖项名称"
|
||||
clearable
|
||||
style="width: 100%" />
|
||||
filterable
|
||||
style="width: 100%"
|
||||
@change="handleRewardRuleChange">
|
||||
<el-option
|
||||
v-for="item in rewardRuleList"
|
||||
:key="item.id"
|
||||
:label="item.ruleName"
|
||||
:value="item.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" class="mb20">
|
||||
@@ -67,6 +75,7 @@ import { ref, reactive, nextTick, onMounted } from 'vue'
|
||||
import { useMessage } from '/@/hooks/message'
|
||||
import { addObj, editObj, getDetail } from '/@/api/stuwork/rewardclass'
|
||||
import { getClassListByRole } from '/@/api/basic/basicclass'
|
||||
import { getList as getRewardRuleList } from '/@/api/stuwork/rewardrule'
|
||||
|
||||
const emit = defineEmits(['refresh'])
|
||||
|
||||
@@ -76,6 +85,8 @@ const visible = ref(false)
|
||||
const loading = ref(false)
|
||||
const operType = ref('add')
|
||||
const classList = ref<any[]>([])
|
||||
/** 奖项列表(来自接口 /stuwork/rewardrule/list,用于班级的奖项类型可传 ruleType 筛选) */
|
||||
const rewardRuleList = ref<any[]>([])
|
||||
|
||||
// 提交表单数据
|
||||
const form = reactive({
|
||||
@@ -91,8 +102,8 @@ const dataRules = {
|
||||
classCode: [
|
||||
{ required: true, message: '请选择班号', trigger: 'change' }
|
||||
],
|
||||
ruleName: [
|
||||
{ required: true, message: '请输入奖项名称', trigger: 'blur' }
|
||||
ruleId: [
|
||||
{ required: true, message: '请选择奖项名称', trigger: 'change' }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -117,6 +128,8 @@ const openDialog = async (type: string = 'add', row?: any) => {
|
||||
form.ruleId = row.ruleId || ''
|
||||
form.ruleName = row.ruleName || ''
|
||||
form.remarks = row.remarks || ''
|
||||
// 选择奖项后同步 ruleName
|
||||
handleRewardRuleChange(form.ruleId)
|
||||
|
||||
// 如果需要获取详情
|
||||
if (row.id && (!row.ruleName || !row.remarks)) {
|
||||
@@ -138,8 +151,12 @@ const openDialog = async (type: string = 'add', row?: any) => {
|
||||
}
|
||||
|
||||
// 班级变化
|
||||
const handleClassChange = () => {
|
||||
// 可以根据班级获取相关信息,这里暂时不实现
|
||||
const handleClassChange = () => {}
|
||||
|
||||
// 选择奖项时同步 ruleName(提交时需要)
|
||||
const handleRewardRuleChange = (id: string) => {
|
||||
const item = rewardRuleList.value.find((r) => r.id === id)
|
||||
form.ruleName = item ? item.ruleName : ''
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
@@ -192,9 +209,21 @@ const getClassListData = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 获取奖项列表(接口:GET /stuwork/rewardrule/list)
|
||||
const getRewardRuleData = async () => {
|
||||
try {
|
||||
const res = await getRewardRuleList()
|
||||
const list = res.data && Array.isArray(res.data) ? res.data : []
|
||||
rewardRuleList.value = list
|
||||
} catch (err) {
|
||||
rewardRuleList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
getClassListData()
|
||||
getRewardRuleData()
|
||||
})
|
||||
|
||||
// 暴露方法
|
||||
|
||||
@@ -12,6 +12,39 @@
|
||||
label-width="100px"
|
||||
v-loading="loading">
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item label="学年" prop="schoolYear">
|
||||
<el-select
|
||||
v-model="form.schoolYear"
|
||||
placeholder="请选择学年"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in schoolYearList"
|
||||
:key="item.year"
|
||||
:label="item.year"
|
||||
:value="item.year">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item label="学期" prop="schoolTerm">
|
||||
<el-select
|
||||
v-model="form.schoolTerm"
|
||||
placeholder="请选择学期"
|
||||
clearable
|
||||
style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in schoolTermList"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item label="宿舍号" prop="roomNo">
|
||||
<el-select
|
||||
@@ -30,12 +63,20 @@
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item label="奖项名称" prop="ruleName">
|
||||
<el-input
|
||||
v-model="form.ruleName"
|
||||
placeholder="请输入奖项名称"
|
||||
<el-form-item label="奖项名称" prop="ruleId">
|
||||
<el-select
|
||||
v-model="form.ruleId"
|
||||
placeholder="请选择奖项名称"
|
||||
clearable
|
||||
style="width: 100%" />
|
||||
filterable
|
||||
style="width: 100%"
|
||||
@change="handleRewardRuleChange">
|
||||
<el-option
|
||||
v-for="item in rewardRuleList"
|
||||
:key="item.id"
|
||||
:label="item.ruleName"
|
||||
:value="item.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" class="mb20">
|
||||
@@ -66,6 +107,9 @@ import { ref, reactive, nextTick, onMounted } from 'vue'
|
||||
import { useMessage } from '/@/hooks/message'
|
||||
import { addObj, editObj, getDetail } from '/@/api/stuwork/rewarddorm'
|
||||
import { dormRoomList } from '/@/api/stuwork/dormroom'
|
||||
import { getList as getRewardRuleList } from '/@/api/stuwork/rewardrule'
|
||||
import { queryAllSchoolYear } from '/@/api/basic/basicyear'
|
||||
import { getDicts } from '/@/api/admin/dict'
|
||||
|
||||
const emit = defineEmits(['refresh'])
|
||||
|
||||
@@ -75,10 +119,16 @@ const visible = ref(false)
|
||||
const loading = ref(false)
|
||||
const operType = ref('add')
|
||||
const roomList = ref<any[]>([])
|
||||
const schoolYearList = ref<any[]>([])
|
||||
const schoolTermList = ref<any[]>([])
|
||||
/** 奖项列表(来自接口 /stuwork/rewardrule/list) */
|
||||
const rewardRuleList = ref<any[]>([])
|
||||
|
||||
// 提交表单数据
|
||||
// 提交表单数据(与接口文档一致:roomNo, ruleId, ruleName, schoolYear, schoolTerm, remarks)
|
||||
const form = reactive({
|
||||
id: '',
|
||||
schoolYear: '',
|
||||
schoolTerm: '',
|
||||
roomNo: '',
|
||||
ruleId: '',
|
||||
ruleName: '',
|
||||
@@ -87,11 +137,17 @@ const form = reactive({
|
||||
|
||||
// 定义校验规则
|
||||
const dataRules = {
|
||||
schoolYear: [
|
||||
{ required: true, message: '请选择学年', trigger: 'change' }
|
||||
],
|
||||
schoolTerm: [
|
||||
{ required: true, message: '请选择学期', trigger: 'change' }
|
||||
],
|
||||
roomNo: [
|
||||
{ required: true, message: '请选择宿舍号', trigger: 'change' }
|
||||
],
|
||||
ruleName: [
|
||||
{ required: true, message: '请输入奖项名称', trigger: 'blur' }
|
||||
ruleId: [
|
||||
{ required: true, message: '请选择奖项名称', trigger: 'change' }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -104,6 +160,8 @@ const openDialog = async (type: string = 'add', row?: any) => {
|
||||
nextTick(() => {
|
||||
dataFormRef.value?.resetFields()
|
||||
form.id = ''
|
||||
form.schoolYear = ''
|
||||
form.schoolTerm = ''
|
||||
form.roomNo = ''
|
||||
form.ruleId = ''
|
||||
form.ruleName = ''
|
||||
@@ -112,23 +170,27 @@ const openDialog = async (type: string = 'add', row?: any) => {
|
||||
// 编辑时填充数据
|
||||
if (type === 'edit' && row) {
|
||||
form.id = row.id
|
||||
form.schoolYear = row.schoolYear || ''
|
||||
form.schoolTerm = row.schoolTerm || ''
|
||||
form.roomNo = row.roomNo || ''
|
||||
form.ruleId = row.ruleId || ''
|
||||
form.ruleName = row.ruleName || ''
|
||||
form.remarks = row.remarks || ''
|
||||
handleRewardRuleChange(form.ruleId)
|
||||
|
||||
// 如果需要获取详情
|
||||
if (row.id && (!row.ruleName || !row.remarks)) {
|
||||
if (row.id && (!row.ruleName || !row.schoolYear)) {
|
||||
loading.value = true
|
||||
getDetail(row.id).then((res: any) => {
|
||||
if (res.data) {
|
||||
form.schoolYear = res.data.schoolYear || ''
|
||||
form.schoolTerm = res.data.schoolTerm || ''
|
||||
form.roomNo = res.data.roomNo || ''
|
||||
form.ruleId = res.data.ruleId || ''
|
||||
form.ruleName = res.data.ruleName || ''
|
||||
form.remarks = res.data.remarks || ''
|
||||
}
|
||||
}).catch((err) => {
|
||||
}).finally(() => {
|
||||
}).catch(() => {}).finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
@@ -136,6 +198,12 @@ const openDialog = async (type: string = 'add', row?: any) => {
|
||||
})
|
||||
}
|
||||
|
||||
// 选择奖项时同步 ruleName(提交时需要)
|
||||
const handleRewardRuleChange = (id: string) => {
|
||||
const item = rewardRuleList.value.find((r) => r.id === id)
|
||||
form.ruleName = item ? item.ruleName : ''
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
const onSubmit = async () => {
|
||||
if (!dataFormRef.value) return
|
||||
@@ -145,10 +213,13 @@ const onSubmit = async () => {
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
// 与接口文档一致:roomNo, ruleId, ruleName, schoolYear, schoolTerm, remarks
|
||||
const submitData = {
|
||||
roomNo: form.roomNo,
|
||||
ruleId: form.ruleId || '', // 如果没有ruleId,传空字符串
|
||||
ruleId: form.ruleId != null && form.ruleId !== '' ? String(form.ruleId) : '',
|
||||
ruleName: form.ruleName,
|
||||
schoolYear: form.schoolYear,
|
||||
schoolTerm: form.schoolTerm,
|
||||
remarks: form.remarks || ''
|
||||
}
|
||||
|
||||
@@ -164,8 +235,8 @@ const onSubmit = async () => {
|
||||
}
|
||||
visible.value = false
|
||||
emit('refresh')
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg || (operType.value === 'add' ? '新增失败' : '编辑失败'))
|
||||
} catch (_err) {
|
||||
// 不再在此处弹 error:request 拦截器已统一弹过,避免重复两个 error 弹窗
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
@@ -186,9 +257,50 @@ const getRoomListData = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 获取奖项列表(接口:GET /stuwork/rewardrule/list)
|
||||
const getRewardRuleData = async () => {
|
||||
try {
|
||||
const res = await getRewardRuleList()
|
||||
const list = res.data && Array.isArray(res.data) ? res.data : []
|
||||
rewardRuleList.value = list
|
||||
} catch (err) {
|
||||
rewardRuleList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
// 获取学年列表
|
||||
const getSchoolYearData = async () => {
|
||||
try {
|
||||
const res = await queryAllSchoolYear()
|
||||
schoolYearList.value = res.data && Array.isArray(res.data) ? res.data : []
|
||||
} catch (err) {
|
||||
schoolYearList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
// 获取学期字典
|
||||
const getSchoolTermData = async () => {
|
||||
try {
|
||||
const res = await getDicts('school_term')
|
||||
if (res.data && Array.isArray(res.data)) {
|
||||
schoolTermList.value = res.data.map((item: any) => ({
|
||||
label: item.label || item.dictLabel || item.name,
|
||||
value: item.value ?? item.dictValue ?? item.code
|
||||
}))
|
||||
} else {
|
||||
schoolTermList.value = []
|
||||
}
|
||||
} catch (err) {
|
||||
schoolTermList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
getRoomListData()
|
||||
getRewardRuleData()
|
||||
getSchoolYearData()
|
||||
getSchoolTermData()
|
||||
})
|
||||
|
||||
// 暴露方法
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
<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="state.queryForm" ref="searchFormRef" :inline="true" @keyup.enter="getDataList" class="search-form">
|
||||
<el-form-item label="??" prop="schoolYear">
|
||||
<el-form-item label="学年" prop="schoolYear">
|
||||
<el-select
|
||||
v-model="state.queryForm.schoolYear"
|
||||
placeholder="?????"
|
||||
placeholder="请选择学年"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 200px">
|
||||
@@ -27,10 +27,10 @@
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="??" prop="schoolTerm">
|
||||
<el-form-item label="学期" prop="schoolTerm">
|
||||
<el-select
|
||||
v-model="state.queryForm.schoolTerm"
|
||||
placeholder="?????"
|
||||
placeholder="请选择学期"
|
||||
clearable
|
||||
style="width: 200px">
|
||||
<el-option
|
||||
@@ -41,48 +41,48 @@
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="???" prop="roomNo">
|
||||
<el-form-item label="宿舍号" prop="roomNo">
|
||||
<el-input
|
||||
v-model="state.queryForm.roomNo"
|
||||
placeholder="??????"
|
||||
placeholder="请输入宿舍号"
|
||||
clearable
|
||||
style="width: 200px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="????" prop="ruleName">
|
||||
<el-form-item label="奖项名称" prop="ruleName">
|
||||
<el-input
|
||||
v-model="state.queryForm.ruleName"
|
||||
placeholder="???????"
|
||||
placeholder="请输入奖项名称"
|
||||
clearable
|
||||
style="width: 200px" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" plain icon="Search" @click="getDataList">??</el-button>
|
||||
<el-button icon="Refresh" @click="handleReset">??</el-button>
|
||||
<el-button type="primary" plain icon="Search" @click="getDataList">查询</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="formDialogRef.openDialog()">
|
||||
??
|
||||
新增
|
||||
</el-button>
|
||||
<el-button
|
||||
icon="Upload"
|
||||
type="success"
|
||||
class="ml10"
|
||||
@click="handleImport">
|
||||
??
|
||||
导入
|
||||
</el-button>
|
||||
<right-toolbar
|
||||
v-model:showSearch="showSearch"
|
||||
@@ -98,7 +98,7 @@
|
||||
@order-change="handleColumnOrderChange"
|
||||
>
|
||||
<template #trigger>
|
||||
<el-tooltip class="item" effect="dark" content="???" placement="top">
|
||||
<el-tooltip class="item" effect="dark" content="列设置" placement="top">
|
||||
<el-button circle style="margin-left: 0;">
|
||||
<el-icon><Menu /></el-icon>
|
||||
</el-button>
|
||||
@@ -110,7 +110,7 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- ?? -->
|
||||
<!-- 表格 -->
|
||||
<el-table
|
||||
:data="state.dataList"
|
||||
v-loading="state.loading"
|
||||
@@ -118,7 +118,7 @@
|
||||
:cell-style="tableStyle.cellStyle"
|
||||
:header-cell-style="tableStyle.headerCellStyle"
|
||||
class="modern-table">
|
||||
<el-table-column type="index" label="??" width="70" align="center">
|
||||
<el-table-column type="index" label="序号" width="70" align="center">
|
||||
<template #header>
|
||||
<el-icon><List /></el-icon>
|
||||
</template>
|
||||
@@ -128,7 +128,7 @@
|
||||
</el-table-column>
|
||||
<template v-for="col in visibleColumnsSorted" :key="col.prop || col.label">
|
||||
<el-table-column
|
||||
v-if="checkColumnVisible(col.prop || '') && col.prop !== '??'"
|
||||
v-if="checkColumnVisible(col.prop || '') && col.prop !== '序号'"
|
||||
:prop="col.prop"
|
||||
:label="col.label"
|
||||
:width="col.width"
|
||||
@@ -152,10 +152,10 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
<el-table-column label="??" width="150" align="center" fixed="right">
|
||||
<el-table-column label="操作" width="150" align="center" fixed="right">
|
||||
<template #header>
|
||||
<el-icon><Setting /></el-icon>
|
||||
<span style="margin-left: 4px">??</span>
|
||||
<span style="margin-left: 4px">操作</span>
|
||||
</template>
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
@@ -163,20 +163,20 @@
|
||||
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"
|
||||
@@ -186,12 +186,12 @@
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<!-- ??/?????? -->
|
||||
<!-- 新增/编辑对话框 -->
|
||||
<form-dialog ref="formDialogRef" @refresh="getDataList" />
|
||||
|
||||
<!-- ???? -->
|
||||
<!-- 导入对话框 -->
|
||||
<el-dialog
|
||||
title="??????"
|
||||
title="导入数据"
|
||||
v-model="importDialogVisible"
|
||||
:width="500"
|
||||
:close-on-click-modal="false"
|
||||
@@ -201,7 +201,7 @@
|
||||
icon="Download"
|
||||
type="success"
|
||||
@click="handleDownloadTemplate">
|
||||
????
|
||||
下载模板
|
||||
</el-button>
|
||||
</div>
|
||||
<el-upload
|
||||
@@ -213,18 +213,18 @@
|
||||
drag>
|
||||
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
|
||||
<div class="el-upload__text">
|
||||
????????<em>????</em>
|
||||
将文件拖到此处,或<em>点击上传</em>
|
||||
</div>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip">
|
||||
???? xlsx/xls ??
|
||||
只能上传 xlsx/xls 文件
|
||||
</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="importDialogVisible = false">??</el-button>
|
||||
<el-button type="primary" @click="handleImportSubmit" :disabled="!importFile || importLoading">??</el-button>
|
||||
<el-button @click="importDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="handleImportSubmit" :disabled="!importFile || importLoading">确认</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
@@ -245,7 +245,7 @@ import { useTableColumnControl } from '/@/hooks/tableColumn'
|
||||
import FormDialog from './form.vue'
|
||||
|
||||
|
||||
// ????
|
||||
// 定义变量
|
||||
const route = useRoute()
|
||||
const formDialogRef = ref()
|
||||
const uploadRef = ref()
|
||||
@@ -258,31 +258,31 @@ const importDialogVisible = ref(false)
|
||||
const importFile = ref<File | null>(null)
|
||||
const importLoading = ref(false)
|
||||
|
||||
// ?????
|
||||
// 表格列配置
|
||||
const tableColumns = [
|
||||
{ prop: 'schoolYear', label: '??', icon: Calendar },
|
||||
{ prop: 'schoolTerm', label: '??', icon: Clock },
|
||||
{ prop: 'roomNo', label: '???', icon: House },
|
||||
{ prop: 'ruleName', label: '????', icon: Trophy, minWidth: 150 },
|
||||
{ prop: 'remarks', label: '??', icon: EditPen, minWidth: 200 }
|
||||
{ prop: 'schoolYear', label: '学年', icon: Calendar },
|
||||
{ prop: 'schoolTerm', label: '学期', icon: Clock },
|
||||
{ prop: 'roomNo', label: '宿舍号', icon: House },
|
||||
{ prop: 'ruleName', label: '奖项名称', icon: Trophy, minWidth: 150 },
|
||||
{ prop: 'remarks', label: '备注', icon: EditPen, minWidth: 200 }
|
||||
]
|
||||
|
||||
// ??????? hook
|
||||
// 表格列控制 hook
|
||||
const {
|
||||
visibleColumns,
|
||||
visibleColumnsSorted,
|
||||
checkColumnVisible,
|
||||
handleColumnChange,
|
||||
handleColumnOrderChange
|
||||
} = useTableColumnControl(tableColumns, route.path)
|
||||
} = useTableColumnControl(tableColumns)
|
||||
|
||||
// ????
|
||||
// 表格样式
|
||||
const tableStyle = {
|
||||
cellStyle: { padding: '8px 0' },
|
||||
headerCellStyle: { background: '#f5f7fa', color: '#606266', fontWeight: 'bold' }
|
||||
}
|
||||
|
||||
// ?? useTable
|
||||
// 使用 useTable
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
queryForm: {
|
||||
schoolYear: '',
|
||||
@@ -295,7 +295,7 @@ const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
item: 'records',
|
||||
totalCount: 'total'
|
||||
},
|
||||
createdIsNeed: true // ?????????
|
||||
createdIsNeed: true // 创建时是否需要请求数据
|
||||
})
|
||||
|
||||
// table hook
|
||||
@@ -306,7 +306,7 @@ const {
|
||||
tableStyle: _tableStyle
|
||||
} = useTable(state)
|
||||
|
||||
// ?????
|
||||
// 格式化学期
|
||||
const formatSchoolTerm = (value: string | number) => {
|
||||
if (value === null || value === undefined || value === '') {
|
||||
return '-'
|
||||
@@ -315,7 +315,7 @@ const formatSchoolTerm = (value: string | number) => {
|
||||
return dictItem ? dictItem.label : value
|
||||
}
|
||||
|
||||
// ??
|
||||
// 重置
|
||||
const handleReset = () => {
|
||||
searchFormRef.value?.resetFields()
|
||||
state.queryForm.schoolYear = ''
|
||||
@@ -325,22 +325,22 @@ const handleReset = () => {
|
||||
getDataList()
|
||||
}
|
||||
|
||||
// ????
|
||||
// 导入
|
||||
const handleImport = () => {
|
||||
importDialogVisible.value = true
|
||||
importFile.value = null
|
||||
uploadRef.value?.clearFiles()
|
||||
}
|
||||
|
||||
// ????
|
||||
// 下载模板
|
||||
const handleDownloadTemplate = async () => {
|
||||
try {
|
||||
const fileName = '????????.xlsx'
|
||||
// ?????? views/stuwork/rewarddorm ? assets/file ?
|
||||
const fileName = '文明宿舍导入模板.xlsx'
|
||||
// 模板文件位于 views/stuwork/rewarddorm 下的 assets/file 目录
|
||||
const fileUrl = new URL(`../../../assets/file/${fileName}`, import.meta.url).href
|
||||
const response = await fetch(fileUrl)
|
||||
if (!response.ok) {
|
||||
throw new Error('??????')
|
||||
throw new Error('下载失败')
|
||||
}
|
||||
const blob = await response.blob()
|
||||
const url = window.URL.createObjectURL(blob)
|
||||
@@ -351,60 +351,60 @@ const handleDownloadTemplate = async () => {
|
||||
link.click()
|
||||
window.URL.revokeObjectURL(url)
|
||||
document.body.removeChild(link)
|
||||
useMessage().success('??????')
|
||||
useMessage().success('下载成功')
|
||||
} catch (error) {
|
||||
useMessage().error('??????')
|
||||
useMessage().error('下载失败')
|
||||
}
|
||||
}
|
||||
|
||||
// ????
|
||||
// 文件变化
|
||||
const handleFileChange = (file: any) => {
|
||||
importFile.value = file.raw
|
||||
}
|
||||
|
||||
// ????
|
||||
// 提交导入
|
||||
const handleImportSubmit = async () => {
|
||||
if (!importFile.value) {
|
||||
useMessage().warning('?????????')
|
||||
useMessage().warning('请先选择要上传的文件')
|
||||
return
|
||||
}
|
||||
|
||||
importLoading.value = true
|
||||
try {
|
||||
await importExcel(importFile.value)
|
||||
useMessage().success('????')
|
||||
useMessage().success('导入成功')
|
||||
importDialogVisible.value = false
|
||||
importFile.value = null
|
||||
uploadRef.value?.clearFiles()
|
||||
getDataList()
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg || '????')
|
||||
useMessage().error(err.msg || '导入失败')
|
||||
} finally {
|
||||
importLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// ??
|
||||
// 编辑
|
||||
const handleEdit = (row: any) => {
|
||||
formDialogRef.value?.openDialog('edit', row)
|
||||
}
|
||||
|
||||
// ??
|
||||
// 删除
|
||||
const handleDelete = async (row: any) => {
|
||||
const { confirm } = useMessageBox()
|
||||
try {
|
||||
await confirm('???????????')
|
||||
await confirm('确定要删除该记录吗?')
|
||||
await delObj([row.id])
|
||||
useMessage().success('????')
|
||||
useMessage().success('删除成功')
|
||||
getDataList()
|
||||
} catch (err: any) {
|
||||
if (err !== 'cancel') {
|
||||
useMessage().error(err.msg || '????')
|
||||
useMessage().error(err.msg || '删除失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ??????
|
||||
// 获取学年列表
|
||||
const getSchoolYearList = async () => {
|
||||
try {
|
||||
const res = await queryAllSchoolYear()
|
||||
@@ -418,7 +418,7 @@ const getSchoolYearList = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// ??????
|
||||
// 获取学期字典
|
||||
const getSchoolTermDict = async () => {
|
||||
try {
|
||||
const res = await getDicts('school_term')
|
||||
@@ -435,7 +435,7 @@ const getSchoolTermDict = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// ???
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
getSchoolYearList()
|
||||
getSchoolTermDict()
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
<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="state.queryForm" ref="searchFormRef" :inline="true" @keyup.enter="getDataList" class="search-form">
|
||||
<el-form-item label="????" prop="ruleType">
|
||||
<el-form-item label="奖项类型" prop="ruleType">
|
||||
<el-select
|
||||
v-model="state.queryForm.ruleType"
|
||||
placeholder="请输入奖项名称"
|
||||
placeholder="请选择奖项类型"
|
||||
clearable
|
||||
style="width: 200px">
|
||||
<el-option
|
||||
@@ -27,26 +27,26 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" plain icon="Search" @click="getDataList">??</el-button>
|
||||
<el-button type="primary" plain icon="Search" @click="getDataList">查询</el-button>
|
||||
<el-button icon="Refresh" @click="handleReset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-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="formDialogRef.openDialog()">
|
||||
??
|
||||
新增
|
||||
</el-button>
|
||||
<right-toolbar
|
||||
v-model:showSearch="showSearch"
|
||||
@@ -92,7 +92,7 @@
|
||||
</el-table-column>
|
||||
<template v-for="col in visibleColumnsSorted" :key="col.prop || col.label">
|
||||
<el-table-column
|
||||
v-if="checkColumnVisible(col.prop || '') && col.prop !== '操作'"
|
||||
v-if="checkColumnVisible(col.prop || '') && col.prop !== '序号'"
|
||||
:prop="col.prop"
|
||||
:label="col.label"
|
||||
:width="col.width"
|
||||
@@ -150,7 +150,7 @@
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<!-- ??/?????? -->
|
||||
<!-- 新增/编辑对话框 -->
|
||||
<form-dialog ref="formDialogRef" @refresh="getDataList" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -168,7 +168,7 @@ import { List, Trophy, Collection, EditPen, Setting, Menu, Search, Document } fr
|
||||
import { useTableColumnControl } from '/@/hooks/tableColumn'
|
||||
|
||||
|
||||
// ??
|
||||
// 定义变量
|
||||
const route = useRoute()
|
||||
const formDialogRef = ref()
|
||||
const searchFormRef = ref()
|
||||
@@ -176,29 +176,29 @@ const columnControlRef = ref()
|
||||
const showSearch = ref(true)
|
||||
const ruleTypeList = ref<any[]>([])
|
||||
|
||||
// ?????
|
||||
// 表格列配置
|
||||
const tableColumns = [
|
||||
{ prop: 'ruleName', label: '????', icon: Trophy, minWidth: 200 },
|
||||
{ prop: 'ruleType', label: '????', icon: Collection },
|
||||
{ prop: 'remarks', label: '??', icon: EditPen, minWidth: 200 }
|
||||
{ prop: 'ruleName', label: '奖项名称', icon: Trophy, minWidth: 200 },
|
||||
{ prop: 'ruleType', label: '奖项类型', icon: Collection },
|
||||
{ prop: 'remarks', label: '备注', icon: EditPen, minWidth: 200 }
|
||||
]
|
||||
|
||||
// ??????? hook
|
||||
// 表格列控制 hook
|
||||
const {
|
||||
visibleColumns,
|
||||
visibleColumnsSorted,
|
||||
checkColumnVisible,
|
||||
handleColumnChange,
|
||||
handleColumnOrderChange
|
||||
} = useTableColumnControl(tableColumns, route.path)
|
||||
} = useTableColumnControl(tableColumns)
|
||||
|
||||
// ????
|
||||
// 表格样式
|
||||
const tableStyle = {
|
||||
cellStyle: { padding: '8px 0' },
|
||||
headerCellStyle: { background: '#f5f7fa', color: '#606266', fontWeight: 'bold' }
|
||||
}
|
||||
|
||||
// ?? useTable
|
||||
// 使用 useTable
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
queryForm: {
|
||||
ruleType: ''
|
||||
@@ -208,7 +208,7 @@ const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
item: 'records',
|
||||
totalCount: 'total'
|
||||
},
|
||||
createdIsNeed: true // ???????????
|
||||
createdIsNeed: true // 创建时是否需要请求数据
|
||||
})
|
||||
|
||||
// table hook
|
||||
@@ -219,7 +219,7 @@ const {
|
||||
tableStyle: _tableStyle
|
||||
} = useTable(state)
|
||||
|
||||
// ???????
|
||||
// 格式化规则类型
|
||||
const formatRuleType = (value: string | number) => {
|
||||
if (value === null || value === undefined || value === '') {
|
||||
return '-'
|
||||
@@ -228,34 +228,34 @@ const formatRuleType = (value: string | number) => {
|
||||
return dictItem ? dictItem.label : value
|
||||
}
|
||||
|
||||
// ??
|
||||
// 重置
|
||||
const handleReset = () => {
|
||||
searchFormRef.value?.resetFields()
|
||||
state.queryForm.ruleType = ''
|
||||
getDataList()
|
||||
}
|
||||
|
||||
// ??
|
||||
// 编辑
|
||||
const handleEdit = (row: any) => {
|
||||
formDialogRef.value?.openDialog('edit', row)
|
||||
}
|
||||
|
||||
// ??
|
||||
// 删除
|
||||
const handleDelete = async (row: any) => {
|
||||
const { confirm } = useMessageBox()
|
||||
try {
|
||||
await confirm('??????????????')
|
||||
await confirm('确定要删除该记录吗?')
|
||||
await delObj([row.id])
|
||||
useMessage().success('????')
|
||||
useMessage().success('删除成功')
|
||||
getDataList()
|
||||
} catch (err: any) {
|
||||
if (err !== 'cancel') {
|
||||
useMessage().error(err.msg || '????')
|
||||
useMessage().error(err.msg || '删除失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ????????
|
||||
// 获取规则类型字典
|
||||
const getRuleTypeDict = async () => {
|
||||
try {
|
||||
const res = await getDicts('rule_type')
|
||||
@@ -272,7 +272,7 @@ const getRuleTypeDict = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// ???
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
getRuleTypeDict()
|
||||
})
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
<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="queryForm" ref="searchFormRef" :inline="true" @keyup.enter="getDataList" class="search-form">
|
||||
<el-form-item label="??" prop="deptCode">
|
||||
<el-form-item label="学院" prop="deptCode">
|
||||
<el-select
|
||||
v-model="queryForm.deptCode"
|
||||
placeholder="?????"
|
||||
placeholder="请选择学院"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 200px">
|
||||
@@ -27,10 +27,10 @@
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="??" prop="classCode">
|
||||
<el-form-item label="班级" prop="classCode">
|
||||
<el-select
|
||||
v-model="queryForm.classCode"
|
||||
placeholder="?????"
|
||||
placeholder="请选择班级"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 200px">
|
||||
@@ -42,10 +42,10 @@
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="??" prop="schoolYear">
|
||||
<el-form-item label="学年" prop="schoolYear">
|
||||
<el-select
|
||||
v-model="queryForm.schoolYear"
|
||||
placeholder="?????"
|
||||
placeholder="请选择学年"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 200px">
|
||||
@@ -57,10 +57,10 @@
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="??" prop="schoolTerm">
|
||||
<el-form-item label="学期" prop="schoolTerm">
|
||||
<el-select
|
||||
v-model="queryForm.schoolTerm"
|
||||
placeholder="?????"
|
||||
placeholder="请选择学期"
|
||||
clearable
|
||||
style="width: 200px">
|
||||
<el-option
|
||||
@@ -72,19 +72,19 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" plain icon="Search" @click="getDataList">??</el-button>
|
||||
<el-button icon="Refresh" @click="handleReset">??</el-button>
|
||||
<el-button type="primary" plain icon="Search" @click="getDataList">查询</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
|
||||
@@ -92,7 +92,7 @@
|
||||
type="success"
|
||||
class="ml10"
|
||||
@click="handleExport">
|
||||
??
|
||||
导出
|
||||
</el-button>
|
||||
<right-toolbar
|
||||
v-model:showSearch="showSearch"
|
||||
@@ -108,7 +108,7 @@
|
||||
@order-change="handleColumnOrderChange"
|
||||
>
|
||||
<template #trigger>
|
||||
<el-tooltip class="item" effect="dark" content="???" placement="top">
|
||||
<el-tooltip class="item" effect="dark" content="列设置" placement="top">
|
||||
<el-button circle style="margin-left: 0;">
|
||||
<el-icon><Menu /></el-icon>
|
||||
</el-button>
|
||||
@@ -120,7 +120,7 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- ?? -->
|
||||
<!-- 表格 -->
|
||||
<el-table
|
||||
:data="dataList"
|
||||
v-loading="loading"
|
||||
@@ -128,7 +128,7 @@
|
||||
:cell-style="tableStyle.cellStyle"
|
||||
:header-cell-style="tableStyle.headerCellStyle"
|
||||
class="modern-table">
|
||||
<el-table-column type="index" label="??" width="70" align="center">
|
||||
<el-table-column type="index" label="序号" width="70" align="center">
|
||||
<template #header>
|
||||
<el-icon><List /></el-icon>
|
||||
</template>
|
||||
@@ -138,7 +138,7 @@
|
||||
</el-table-column>
|
||||
<template v-for="col in visibleColumnsSorted" :key="col.prop || col.label">
|
||||
<el-table-column
|
||||
v-if="checkColumnVisible(col.prop || '') && col.prop !== '??'"
|
||||
v-if="checkColumnVisible(col.prop || '') && col.prop !== '序号'"
|
||||
:prop="col.prop"
|
||||
:label="col.label"
|
||||
show-overflow-tooltip
|
||||
@@ -149,28 +149,28 @@
|
||||
<el-icon><component :is="columnConfigMap[col.prop]?.icon || OfficeBuilding" /></el-icon>
|
||||
<span style="margin-left: 4px">{{ col.label }}</span>
|
||||
</template>
|
||||
<!-- ?????????? -->
|
||||
<!-- 平均操行分格式化 -->
|
||||
<template v-if="col.prop === 'averageConduct'" #default="scope">
|
||||
<el-tag v-if="scope.row.averageConduct !== null && scope.row.averageConduct !== undefined" size="small" type="success" effect="plain">
|
||||
{{ scope.row.averageConduct.toFixed(2) }}
|
||||
</el-tag>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
<!-- ???????????? -->
|
||||
<!-- 平均学习成绩格式化 -->
|
||||
<template v-else-if="col.prop === 'averageScore'" #default="scope">
|
||||
<el-tag v-if="scope.row.averageScore !== null && scope.row.averageScore !== undefined" size="small" type="primary" effect="plain">
|
||||
{{ scope.row.averageScore.toFixed(2) }}
|
||||
</el-tag>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
<!-- ????????? -->
|
||||
<!-- 规则名称格式化 -->
|
||||
<template v-else-if="col.prop === 'ruleName'" #default="scope">
|
||||
<el-tag v-if="scope.row.ruleName && Array.isArray(scope.row.ruleName) && scope.row.ruleName.length > 0" size="small" type="warning" effect="light">
|
||||
{{ scope.row.ruleName.join('?') }}
|
||||
{{ scope.row.ruleName.join('、') }}
|
||||
</el-tag>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
<!-- ????????? -->
|
||||
<!-- 更新时间格式化 -->
|
||||
<template v-else-if="col.prop === 'upDateTime'" #default="scope">
|
||||
<span>{{ parseTime(scope.row.upDateTime, dateTimeFormat) }}</span>
|
||||
</template>
|
||||
@@ -198,7 +198,7 @@ import { useTableColumnControl } from '/@/hooks/tableColumn'
|
||||
|
||||
const dateTimeFormat = '{y}-{m}-{d} {h}:{i}:{s}'
|
||||
|
||||
// ????
|
||||
// 定义变量
|
||||
const route = useRoute()
|
||||
const searchFormRef = ref()
|
||||
const columnControlRef = ref<any>()
|
||||
@@ -210,20 +210,20 @@ const schoolTermList = ref<any[]>([])
|
||||
const deptList = ref<any[]>([])
|
||||
const classList = ref<any[]>([])
|
||||
|
||||
// ?????
|
||||
// 表格列配置
|
||||
const tableColumns = [
|
||||
{ prop: 'departName', label: '????' },
|
||||
{ prop: 'classNo', label: '??' },
|
||||
{ prop: 'stuNo', label: '??' },
|
||||
{ prop: 'realName', label: '??' },
|
||||
{ prop: 'averageConduct', label: '?????' },
|
||||
{ prop: 'averageScore', label: '???????' },
|
||||
{ prop: 'violation', label: '????', minWidth: 150 },
|
||||
{ prop: 'ruleName', label: '????', minWidth: 200 },
|
||||
{ prop: 'upDateTime', label: '????', width: 180 }
|
||||
{ prop: 'departName', label: '学院名称' },
|
||||
{ prop: 'classNo', label: '班级' },
|
||||
{ prop: 'stuNo', label: '学号' },
|
||||
{ prop: 'realName', label: '姓名' },
|
||||
{ prop: 'averageConduct', label: '平均操行分' },
|
||||
{ prop: 'averageScore', label: '平均学习成绩' },
|
||||
{ prop: 'violation', label: '违规情况', minWidth: 150 },
|
||||
{ prop: 'ruleName', label: '奖励规则', minWidth: 200 },
|
||||
{ prop: 'upDateTime', label: '更新时间', width: 180 }
|
||||
]
|
||||
|
||||
// ?????????????
|
||||
// 列配置映射
|
||||
const columnConfigMap: Record<string, { icon: any }> = {
|
||||
departName: { icon: OfficeBuilding },
|
||||
classNo: { icon: Grid },
|
||||
@@ -236,7 +236,7 @@ const columnConfigMap: Record<string, { icon: any }> = {
|
||||
upDateTime: { icon: Clock }
|
||||
}
|
||||
|
||||
// ???????hook
|
||||
// 表格列控制hook
|
||||
const {
|
||||
visibleColumns,
|
||||
visibleColumnsSorted,
|
||||
@@ -245,7 +245,7 @@ const {
|
||||
handleColumnOrderChange
|
||||
} = useTableColumnControl(tableColumns, route.path)
|
||||
|
||||
// ????
|
||||
// 查询表单
|
||||
const queryForm = reactive({
|
||||
deptCode: '',
|
||||
classCode: '',
|
||||
@@ -253,13 +253,13 @@ const queryForm = reactive({
|
||||
schoolTerm: ''
|
||||
})
|
||||
|
||||
// ????
|
||||
// 表格样式
|
||||
const tableStyle = {
|
||||
cellStyle: { padding: '8px 0' },
|
||||
headerCellStyle: { background: '#f5f7fa', color: '#606266', fontWeight: 'bold' }
|
||||
}
|
||||
|
||||
// ?????
|
||||
// 格式化学期
|
||||
const formatSchoolTerm = (value: string | number) => {
|
||||
if (value === null || value === undefined || value === '') {
|
||||
return '-'
|
||||
@@ -268,7 +268,7 @@ const formatSchoolTerm = (value: string | number) => {
|
||||
return dictItem ? dictItem.label : value
|
||||
}
|
||||
|
||||
// ????
|
||||
// 获取数据列表
|
||||
const getDataList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
@@ -279,14 +279,14 @@ const getDataList = async () => {
|
||||
dataList.value = []
|
||||
}
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg || '??????')
|
||||
useMessage().error(err.msg || '查询失败')
|
||||
dataList.value = []
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// ??
|
||||
// 重置
|
||||
const handleReset = () => {
|
||||
searchFormRef.value?.resetFields()
|
||||
queryForm.deptCode = ''
|
||||
@@ -296,43 +296,43 @@ const handleReset = () => {
|
||||
getDataList()
|
||||
}
|
||||
|
||||
// ??
|
||||
// 导出
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
const res = await exportExcel(queryForm)
|
||||
|
||||
// ?? blob
|
||||
// 创建 blob
|
||||
const blob = new Blob([res], {
|
||||
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||||
})
|
||||
|
||||
// ??????
|
||||
// 创建下载链接
|
||||
const url = window.URL.createObjectURL(blob)
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
|
||||
// ???
|
||||
const fileName = `??????_${new Date().getTime()}.xlsx`
|
||||
// 设置文件名
|
||||
const fileName = `奖励学生_${new Date().getTime()}.xlsx`
|
||||
link.setAttribute('download', fileName)
|
||||
|
||||
// ????
|
||||
// 触发下载
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
|
||||
// ??
|
||||
// 清理
|
||||
document.body.removeChild(link)
|
||||
window.URL.revokeObjectURL(url)
|
||||
|
||||
useMessage().success('????')
|
||||
useMessage().success('导出成功')
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg || '????')
|
||||
useMessage().error(err.msg || '导出失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// ??????
|
||||
// 获取学年列表
|
||||
const getSchoolYearList = async () => {
|
||||
try {
|
||||
const res = await queryAllSchoolYear()
|
||||
@@ -346,7 +346,7 @@ const getSchoolYearList = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// ??????
|
||||
// 获取学期字典
|
||||
const getSchoolTermDict = async () => {
|
||||
try {
|
||||
const res = await getDicts('school_term')
|
||||
@@ -363,7 +363,7 @@ const getSchoolTermDict = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// ??????
|
||||
// 获取学院列表
|
||||
const getDeptListData = async () => {
|
||||
try {
|
||||
const res = await getDeptList()
|
||||
@@ -377,7 +377,7 @@ const getDeptListData = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// ??????
|
||||
// 获取班级列表
|
||||
const getClassListData = async () => {
|
||||
try {
|
||||
const res = await getClassListByRole()
|
||||
@@ -391,7 +391,7 @@ const getClassListData = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// ????
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
getSchoolYearList()
|
||||
getSchoolTermDict()
|
||||
@@ -400,7 +400,6 @@ onMounted(() => {
|
||||
getDataList()
|
||||
nextTick(() => {
|
||||
if (visibleColumns.value.length === 0) {
|
||||
loadSavedConfig()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
<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="state.queryForm" ref="searchFormRef" :inline="true" @keyup.enter="getDataList" class="search-form">
|
||||
<el-form-item label="??" prop="schoolYear">
|
||||
<el-form-item label="学年" prop="schoolYear">
|
||||
<el-select
|
||||
v-model="state.queryForm.schoolYear"
|
||||
placeholder="?????"
|
||||
placeholder="请选择学年"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 200px">
|
||||
@@ -27,10 +27,10 @@
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="??" prop="schoolTerm">
|
||||
<el-form-item label="学期" prop="schoolTerm">
|
||||
<el-select
|
||||
v-model="state.queryForm.schoolTerm"
|
||||
placeholder="?????"
|
||||
placeholder="请选择学期"
|
||||
clearable
|
||||
style="width: 200px">
|
||||
<el-option
|
||||
@@ -41,10 +41,10 @@
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="??" prop="deptCode">
|
||||
<el-form-item label="学院" prop="deptCode">
|
||||
<el-select
|
||||
v-model="state.queryForm.deptCode"
|
||||
placeholder="?????"
|
||||
placeholder="请选择学院"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 200px">
|
||||
@@ -56,10 +56,10 @@
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="??" prop="classCode">
|
||||
<el-form-item label="班级" prop="classCode">
|
||||
<el-select
|
||||
v-model="state.queryForm.classCode"
|
||||
placeholder="?????"
|
||||
placeholder="请选择班级"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 200px">
|
||||
@@ -71,41 +71,41 @@
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="??" prop="realName">
|
||||
<el-form-item label="姓名" prop="realName">
|
||||
<el-input
|
||||
v-model="state.queryForm.realName"
|
||||
placeholder="?????"
|
||||
placeholder="请输入姓名"
|
||||
clearable
|
||||
style="width: 200px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="??" prop="stuNo">
|
||||
<el-form-item label="学号" prop="stuNo">
|
||||
<el-input
|
||||
v-model="state.queryForm.stuNo"
|
||||
placeholder="?????"
|
||||
placeholder="请输入学号"
|
||||
clearable
|
||||
style="width: 200px" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" plain icon="Search" @click="getDataList">??</el-button>
|
||||
<el-button icon="Refresh" @click="handleReset">??</el-button>
|
||||
<el-button type="primary" plain icon="Search" @click="getDataList">查询</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="formDialogRef.openDialog()">
|
||||
??
|
||||
新增
|
||||
</el-button>
|
||||
<right-toolbar
|
||||
v-model:showSearch="showSearch"
|
||||
@@ -121,7 +121,7 @@
|
||||
@order-change="handleColumnOrderChange"
|
||||
>
|
||||
<template #trigger>
|
||||
<el-tooltip class="item" effect="dark" content="???" placement="top">
|
||||
<el-tooltip class="item" effect="dark" content="列设置" placement="top">
|
||||
<el-button circle style="margin-left: 0;">
|
||||
<el-icon><Menu /></el-icon>
|
||||
</el-button>
|
||||
@@ -133,7 +133,7 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- ?? -->
|
||||
<!-- 表格 -->
|
||||
<el-table
|
||||
:data="state.dataList"
|
||||
v-loading="state.loading"
|
||||
@@ -141,7 +141,7 @@
|
||||
:cell-style="tableStyle.cellStyle"
|
||||
:header-cell-style="tableStyle.headerCellStyle"
|
||||
class="modern-table">
|
||||
<el-table-column type="index" label="??" width="70" align="center">
|
||||
<el-table-column type="index" label="序号" width="70" align="center">
|
||||
<template #header>
|
||||
<el-icon><List /></el-icon>
|
||||
</template>
|
||||
@@ -151,7 +151,7 @@
|
||||
</el-table-column>
|
||||
<template v-for="col in visibleColumnsSorted" :key="col.prop || col.label">
|
||||
<el-table-column
|
||||
v-if="checkColumnVisible(col.prop || '') && col.prop !== '??'"
|
||||
v-if="checkColumnVisible(col.prop || '') && col.prop !== '序号'"
|
||||
:prop="col.prop"
|
||||
:label="col.label"
|
||||
show-overflow-tooltip
|
||||
@@ -162,32 +162,32 @@
|
||||
<el-icon><component :is="columnConfigMap[col.prop]?.icon || Calendar" /></el-icon>
|
||||
<span style="margin-left: 4px">{{ col.label }}</span>
|
||||
</template>
|
||||
<!-- ??????? -->
|
||||
<!-- 学期格式化 -->
|
||||
<template v-if="col.prop === 'schoolTerm'" #default="scope">
|
||||
<el-tag size="small" type="primary" effect="plain">
|
||||
{{ formatSchoolTerm(scope.row.schoolTerm) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
<!-- ?????????? -->
|
||||
<!-- 关怀类型格式化 -->
|
||||
<template v-else-if="col.prop === 'careType'" #default="scope">
|
||||
<el-tag size="small" type="danger" effect="plain">
|
||||
{{ formatCareType(scope.row.careType) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
<!-- ????????? -->
|
||||
<!-- 记录日期格式化 -->
|
||||
<template v-else-if="col.prop === 'recordDate'" #default="scope">
|
||||
<span>{{ scope.row.recordDate || '-' }}</span>
|
||||
</template>
|
||||
<!-- ????????? -->
|
||||
<!-- 处理结果格式化 -->
|
||||
<template v-else-if="col.prop === 'result'" #default="scope">
|
||||
<span>{{ scope.row.result || '-' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
<el-table-column label="??" width="250" align="center" fixed="right">
|
||||
<el-table-column label="操作" width="250" align="center" fixed="right">
|
||||
<template #header>
|
||||
<el-icon><Setting /></el-icon>
|
||||
<span style="margin-left: 4px">??</span>
|
||||
<span style="margin-left: 4px">操作</span>
|
||||
</template>
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
@@ -195,27 +195,27 @@
|
||||
link
|
||||
type="primary"
|
||||
@click="handleEdit(scope.row)">
|
||||
??
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
icon="Document"
|
||||
link
|
||||
type="success"
|
||||
@click="handleResult(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"
|
||||
@@ -225,10 +225,10 @@
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<!-- ??/?????? -->
|
||||
<!-- 新增/编辑对话框 -->
|
||||
<form-dialog ref="formDialogRef" @refresh="getDataList" />
|
||||
|
||||
<!-- ?????? -->
|
||||
<!-- 处理结果对话框 -->
|
||||
<result-dialog ref="resultDialogRef" @refresh="getDataList" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -250,7 +250,7 @@ import { List, Calendar, Clock, OfficeBuilding, Grid, Avatar, UserFilled, Phone,
|
||||
import { useTableColumnControl } from '/@/hooks/tableColumn'
|
||||
|
||||
|
||||
// ??????
|
||||
// 定义变量
|
||||
const route = useRoute()
|
||||
const formDialogRef = ref()
|
||||
const resultDialogRef = ref()
|
||||
@@ -263,22 +263,22 @@ const deptList = ref<any[]>([])
|
||||
const classList = ref<any[]>([])
|
||||
const careTypeList = ref<any[]>([])
|
||||
|
||||
// ?????
|
||||
// 表格列配置
|
||||
const tableColumns = [
|
||||
{ prop: 'schoolYear', label: '??' },
|
||||
{ prop: 'schoolTerm', label: '??' },
|
||||
{ prop: 'deptName', label: '??' },
|
||||
{ prop: 'classNo', label: '??' },
|
||||
{ prop: 'realName', label: '??' },
|
||||
{ prop: 'teacherRealName', label: '?????' },
|
||||
{ prop: 'teacherTel', label: '?????' },
|
||||
{ prop: 'careType', label: '?????' },
|
||||
{ prop: 'present', label: '????', minWidth: 150 },
|
||||
{ prop: 'recordDate', label: '????', width: 120 },
|
||||
{ prop: 'result', label: '????', minWidth: 150 }
|
||||
{ prop: 'schoolYear', label: '学年' },
|
||||
{ prop: 'schoolTerm', label: '学期' },
|
||||
{ prop: 'deptName', label: '学院' },
|
||||
{ prop: 'classNo', label: '班级' },
|
||||
{ prop: 'realName', label: '姓名' },
|
||||
{ prop: 'teacherRealName', label: '班主任姓名' },
|
||||
{ prop: 'teacherTel', label: '班主任电话' },
|
||||
{ prop: 'careType', label: '关怀类型' },
|
||||
{ prop: 'present', label: '现状描述', minWidth: 150 },
|
||||
{ prop: 'recordDate', label: '记录日期', width: 120 },
|
||||
{ prop: 'result', label: '处理结果', minWidth: 150 }
|
||||
]
|
||||
|
||||
// ?????????????
|
||||
// 列配置映射
|
||||
const columnConfigMap: Record<string, { icon: any }> = {
|
||||
schoolYear: { icon: Calendar },
|
||||
schoolTerm: { icon: Clock },
|
||||
@@ -293,7 +293,7 @@ const columnConfigMap: Record<string, { icon: any }> = {
|
||||
result: { icon: CircleCheck }
|
||||
}
|
||||
|
||||
// ???????hook
|
||||
// 表格列控制hook
|
||||
const {
|
||||
visibleColumns,
|
||||
visibleColumnsSorted,
|
||||
@@ -302,7 +302,7 @@ const {
|
||||
handleColumnOrderChange
|
||||
} = useTableColumnControl(tableColumns, route.path)
|
||||
|
||||
// ?? useTable
|
||||
// 使用 useTable
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
queryForm: {
|
||||
schoolYear: '',
|
||||
@@ -328,7 +328,7 @@ const {
|
||||
tableStyle
|
||||
} = useTable(state)
|
||||
|
||||
// ?????
|
||||
// 格式化学期
|
||||
const formatSchoolTerm = (value: string | number) => {
|
||||
if (value === null || value === undefined || value === '') {
|
||||
return '-'
|
||||
@@ -337,14 +337,14 @@ const formatSchoolTerm = (value: string | number) => {
|
||||
return dictItem ? dictItem.label : value
|
||||
}
|
||||
|
||||
// ????????
|
||||
// 格式化关怀类型
|
||||
const formatCareType = (value: string) => {
|
||||
if (!value) return '-'
|
||||
const item = careTypeList.value.find((item: any) => item.value === value)
|
||||
return item ? item.label : value
|
||||
}
|
||||
|
||||
// ??
|
||||
// 重置
|
||||
const handleReset = () => {
|
||||
searchFormRef.value?.resetFields()
|
||||
state.queryForm.schoolYear = ''
|
||||
@@ -356,32 +356,32 @@ const handleReset = () => {
|
||||
getDataList()
|
||||
}
|
||||
|
||||
// ??
|
||||
// 编辑
|
||||
const handleEdit = (row: any) => {
|
||||
formDialogRef.value?.openDialog('edit', row)
|
||||
}
|
||||
|
||||
// ????
|
||||
// 处理结果
|
||||
const handleResult = (row: any) => {
|
||||
resultDialogRef.value?.openDialog(row)
|
||||
}
|
||||
|
||||
// ??
|
||||
// 删除
|
||||
const handleDelete = async (row: any) => {
|
||||
const { confirm } = useMessageBox()
|
||||
try {
|
||||
await confirm('??????????????')
|
||||
await confirm('确定要删除该记录吗?')
|
||||
await delObj([row.id])
|
||||
useMessage().success('????')
|
||||
useMessage().success('删除成功')
|
||||
getDataList()
|
||||
} catch (err: any) {
|
||||
if (err !== 'cancel') {
|
||||
useMessage().error(err.msg || '????')
|
||||
useMessage().error(err.msg || '删除失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ??????
|
||||
// 获取学年列表
|
||||
const getSchoolYearList = async () => {
|
||||
try {
|
||||
const res = await queryAllSchoolYear()
|
||||
@@ -395,7 +395,7 @@ const getSchoolYearList = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// ??????
|
||||
// 获取学期字典
|
||||
const getSchoolTermDict = async () => {
|
||||
try {
|
||||
const res = await getDicts('school_term')
|
||||
@@ -412,7 +412,7 @@ const getSchoolTermDict = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// ??????
|
||||
// 获取学院列表
|
||||
const getDeptListData = async () => {
|
||||
try {
|
||||
const res = await getDeptList()
|
||||
@@ -426,7 +426,7 @@ const getDeptListData = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// ??????
|
||||
// 获取班级列表
|
||||
const getClassListData = async () => {
|
||||
try {
|
||||
const res = await getClassListByRole()
|
||||
@@ -440,7 +440,7 @@ const getClassListData = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// ?????????
|
||||
// 获取关怀类型字典
|
||||
const getCareTypeDict = async () => {
|
||||
try {
|
||||
const res = await getDicts('stu_care_type')
|
||||
@@ -457,7 +457,7 @@ const getCareTypeDict = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// ???
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
getSchoolYearList()
|
||||
getSchoolTermDict()
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
<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="state.queryForm" ref="searchFormRef" :inline="true" @keyup.enter="getDataList" class="search-form">
|
||||
<el-form-item label="??" prop="schoolYear">
|
||||
<el-form-item label="学年" prop="schoolYear">
|
||||
<el-select
|
||||
v-model="state.queryForm.schoolYear"
|
||||
placeholder="?????"
|
||||
placeholder="请选择学年"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 200px">
|
||||
@@ -27,10 +27,10 @@
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="??" prop="schoolTerm">
|
||||
<el-form-item label="学期" prop="schoolTerm">
|
||||
<el-select
|
||||
v-model="state.queryForm.schoolTerm"
|
||||
placeholder="?????"
|
||||
placeholder="请选择学期"
|
||||
clearable
|
||||
style="width: 200px">
|
||||
<el-option
|
||||
@@ -41,10 +41,10 @@
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="??" prop="deptCode">
|
||||
<el-form-item label="学院" prop="deptCode">
|
||||
<el-select
|
||||
v-model="state.queryForm.deptCode"
|
||||
placeholder="?????"
|
||||
placeholder="请选择学院"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 200px"
|
||||
@@ -57,10 +57,10 @@
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="??" prop="classCode">
|
||||
<el-form-item label="班级" prop="classCode">
|
||||
<el-select
|
||||
v-model="state.queryForm.classCode"
|
||||
placeholder="?????"
|
||||
placeholder="请选择班级"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 200px">
|
||||
@@ -72,17 +72,17 @@
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="??" prop="realName">
|
||||
<el-form-item label="姓名" prop="realName">
|
||||
<el-input
|
||||
v-model="state.queryForm.realName"
|
||||
placeholder="?????"
|
||||
placeholder="请输入姓名"
|
||||
clearable
|
||||
style="width: 200px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="??" prop="conductType">
|
||||
<el-form-item label="类型" prop="conductType">
|
||||
<el-select
|
||||
v-model="state.queryForm.conductType"
|
||||
placeholder="?????"
|
||||
placeholder="请选择类型"
|
||||
clearable
|
||||
style="width: 200px">
|
||||
<el-option
|
||||
@@ -94,33 +94,33 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" plain icon="Search" @click="getDataList">??</el-button>
|
||||
<el-button icon="Refresh" @click="handleReset">??</el-button>
|
||||
<el-button type="primary" plain icon="Search" @click="getDataList">查询</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="formDialogRef.openDialog()">
|
||||
??
|
||||
新增
|
||||
</el-button>
|
||||
<el-button
|
||||
icon="Upload"
|
||||
type="success"
|
||||
class="ml10"
|
||||
@click="handleImport">
|
||||
??
|
||||
导入
|
||||
</el-button>
|
||||
<right-toolbar
|
||||
v-model:showSearch="showSearch"
|
||||
@@ -136,7 +136,7 @@
|
||||
@order-change="handleColumnOrderChange"
|
||||
>
|
||||
<template #trigger>
|
||||
<el-tooltip class="item" effect="dark" content="???" placement="top">
|
||||
<el-tooltip class="item" effect="dark" content="列设置" placement="top">
|
||||
<el-button circle style="margin-left: 0;">
|
||||
<el-icon><Menu /></el-icon>
|
||||
</el-button>
|
||||
@@ -148,7 +148,7 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- ?? -->
|
||||
<!-- 表格 -->
|
||||
<el-table
|
||||
:data="state.dataList"
|
||||
v-loading="state.loading"
|
||||
@@ -156,7 +156,7 @@
|
||||
:cell-style="tableStyle.cellStyle"
|
||||
:header-cell-style="tableStyle.headerCellStyle"
|
||||
class="modern-table">
|
||||
<el-table-column type="index" label="??" width="70" align="center">
|
||||
<el-table-column type="index" label="序号" width="70" align="center">
|
||||
<template #header>
|
||||
<el-icon><List /></el-icon>
|
||||
</template>
|
||||
@@ -166,7 +166,7 @@
|
||||
</el-table-column>
|
||||
<template v-for="col in visibleColumnsSorted" :key="col.prop || col.label">
|
||||
<el-table-column
|
||||
v-if="checkColumnVisible(col.prop || '') && col.prop !== '??'"
|
||||
v-if="checkColumnVisible(col.prop || '') && col.prop !== '序号'"
|
||||
:prop="col.prop"
|
||||
:label="col.label"
|
||||
show-overflow-tooltip
|
||||
@@ -177,23 +177,23 @@
|
||||
<el-icon><component :is="columnConfigMap[col.prop]?.icon || Calendar" /></el-icon>
|
||||
<span style="margin-left: 4px">{{ col.label }}</span>
|
||||
</template>
|
||||
<!-- ??????? -->
|
||||
<!-- 学期格式化 -->
|
||||
<template v-if="col.prop === 'schoolTerm'" #default="scope">
|
||||
<el-tag size="small" type="primary" effect="plain">
|
||||
{{ formatSchoolTerm(scope.row.schoolTerm) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
<!-- ??????? -->
|
||||
<!-- 类型格式化 -->
|
||||
<template v-else-if="col.prop === 'conductType'" #default="scope">
|
||||
<el-tag size="small" type="warning" effect="plain">
|
||||
{{ formatType(scope.row.conductType) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
<!-- ????????? -->
|
||||
<!-- 记录日期格式化 -->
|
||||
<template v-else-if="col.prop === 'recordDate'" #default="scope">
|
||||
<span>{{ scope.row.recordDate || '-' }}</span>
|
||||
</template>
|
||||
<!-- ??????? -->
|
||||
<!-- 附件格式化 -->
|
||||
<template v-else-if="col.prop === 'attachment'" #default="scope">
|
||||
<el-button
|
||||
v-if="scope.row.attachment"
|
||||
@@ -202,16 +202,16 @@
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="handleViewAttachment(scope.row)">
|
||||
??
|
||||
查看
|
||||
</el-button>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
<el-table-column label="??" width="150" align="center" fixed="right">
|
||||
<el-table-column label="操作" width="150" align="center" fixed="right">
|
||||
<template #header>
|
||||
<el-icon><Setting /></el-icon>
|
||||
<span style="margin-left: 4px">??</span>
|
||||
<span style="margin-left: 4px">操作</span>
|
||||
</template>
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
@@ -219,20 +219,20 @@
|
||||
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"
|
||||
@@ -242,12 +242,12 @@
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<!-- ??/?????? -->
|
||||
<!-- 新增/编辑对话框 -->
|
||||
<form-dialog ref="formDialogRef" @refresh="getDataList" />
|
||||
|
||||
<!-- ???? -->
|
||||
<!-- 导入对话框 -->
|
||||
<el-dialog
|
||||
title="??????"
|
||||
title="导入数据"
|
||||
v-model="importDialogVisible"
|
||||
:width="500"
|
||||
:close-on-click-modal="false"
|
||||
@@ -261,18 +261,18 @@
|
||||
drag>
|
||||
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
|
||||
<div class="el-upload__text">
|
||||
????????<em>????</em>
|
||||
将文件拖到此处,或<em>点击上传</em>
|
||||
</div>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip">
|
||||
???? xlsx/xls ??
|
||||
只能上传 xlsx/xls 文件
|
||||
</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="importDialogVisible = false">??</el-button>
|
||||
<el-button type="primary" @click="handleImportSubmit" :disabled="!importFile || importLoading">??</el-button>
|
||||
<el-button @click="importDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="handleImportSubmit" :disabled="!importFile || importLoading">确认</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
@@ -295,7 +295,7 @@ import { useTableColumnControl } from '/@/hooks/tableColumn'
|
||||
|
||||
import FormDialog from './form.vue'
|
||||
|
||||
// ??????
|
||||
// 定义变量
|
||||
const route = useRoute()
|
||||
const formDialogRef = ref()
|
||||
const columnControlRef = ref<any>()
|
||||
@@ -311,21 +311,21 @@ const importDialogVisible = ref(false)
|
||||
const importFile = ref<File | null>(null)
|
||||
const importLoading = ref(false)
|
||||
|
||||
// ?????
|
||||
// 表格列配置
|
||||
const tableColumns = [
|
||||
{ prop: 'stuNo', label: '??' },
|
||||
{ prop: 'schoolYear', label: '??' },
|
||||
{ prop: 'schoolTerm', label: '??' },
|
||||
{ prop: 'deptName', label: '??' },
|
||||
{ prop: 'classNo', label: '??' },
|
||||
{ prop: 'realName', label: '??' },
|
||||
{ prop: 'conductType', label: '??' },
|
||||
{ prop: 'recordDate', label: '????', width: 120 },
|
||||
{ prop: 'description', label: '????', minWidth: 150 },
|
||||
{ prop: 'attachment', label: '??', width: 100 }
|
||||
{ prop: 'stuNo', label: '学号' },
|
||||
{ prop: 'schoolYear', label: '学年' },
|
||||
{ prop: 'schoolTerm', label: '学期' },
|
||||
{ prop: 'deptName', label: '学院' },
|
||||
{ prop: 'classNo', label: '班级' },
|
||||
{ prop: 'realName', label: '姓名' },
|
||||
{ prop: 'conductType', label: '类型' },
|
||||
{ prop: 'recordDate', label: '记录日期', width: 120 },
|
||||
{ prop: 'description', label: '描述说明', minWidth: 150 },
|
||||
{ prop: 'attachment', label: '附件', width: 100 }
|
||||
]
|
||||
|
||||
// ????????????
|
||||
// 列配置映射
|
||||
const columnConfigMap: Record<string, { icon: any }> = {
|
||||
stuNo: { icon: CreditCard },
|
||||
schoolYear: { icon: Calendar },
|
||||
@@ -339,7 +339,7 @@ const columnConfigMap: Record<string, { icon: any }> = {
|
||||
attachment: { icon: Document }
|
||||
}
|
||||
|
||||
// ???????hook
|
||||
// 表格列控制hook
|
||||
const {
|
||||
visibleColumns,
|
||||
visibleColumnsSorted,
|
||||
@@ -348,7 +348,7 @@ const {
|
||||
handleColumnOrderChange
|
||||
} = useTableColumnControl(tableColumns, route.path)
|
||||
|
||||
// ?? useTable
|
||||
// 使用 useTable
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
queryForm: {
|
||||
schoolYear: '',
|
||||
@@ -374,7 +374,7 @@ const {
|
||||
tableStyle
|
||||
} = useTable(state)
|
||||
|
||||
// ?????
|
||||
// 格式化学期
|
||||
const formatSchoolTerm = (value: string | number) => {
|
||||
if (value === null || value === undefined || value === '') {
|
||||
return '-'
|
||||
@@ -383,19 +383,19 @@ const formatSchoolTerm = (value: string | number) => {
|
||||
return dictItem ? dictItem.label : value
|
||||
}
|
||||
|
||||
// ?????
|
||||
// 格式化类型
|
||||
const formatType = (value: string) => {
|
||||
if (!value) return '-'
|
||||
const item = typeList.value.find((item: any) => item.value === value)
|
||||
return item ? item.label : value
|
||||
}
|
||||
|
||||
// ????
|
||||
// 学院变化
|
||||
const handleDeptChange = () => {
|
||||
// ??????????????????
|
||||
// 学院变化时清空班级选择
|
||||
}
|
||||
|
||||
// ??
|
||||
// 重置
|
||||
const handleReset = () => {
|
||||
searchFormRef.value?.resetFields()
|
||||
state.queryForm.schoolYear = ''
|
||||
@@ -407,7 +407,7 @@ const handleReset = () => {
|
||||
getDataList()
|
||||
}
|
||||
|
||||
// ????
|
||||
// 查看附件
|
||||
const handleViewAttachment = (row: any) => {
|
||||
if (row.attachment) {
|
||||
const urls = typeof row.attachment === 'string' ? row.attachment.split(',') : [row.attachment]
|
||||
@@ -419,61 +419,61 @@ const handleViewAttachment = (row: any) => {
|
||||
}
|
||||
}
|
||||
|
||||
// ??
|
||||
// 导入
|
||||
const handleImport = () => {
|
||||
importDialogVisible.value = true
|
||||
importFile.value = null
|
||||
uploadRef.value?.clearFiles()
|
||||
}
|
||||
|
||||
// ????
|
||||
// 文件变化
|
||||
const handleFileChange = (file: any) => {
|
||||
importFile.value = file.raw
|
||||
}
|
||||
|
||||
// ????
|
||||
// 提交导入
|
||||
const handleImportSubmit = async () => {
|
||||
if (!importFile.value) {
|
||||
useMessage().warning('?????????')
|
||||
useMessage().warning('请先选择要上传的文件')
|
||||
return
|
||||
}
|
||||
|
||||
importLoading.value = true
|
||||
try {
|
||||
await importExcel(importFile.value)
|
||||
useMessage().success('????')
|
||||
useMessage().success('导入成功')
|
||||
importDialogVisible.value = false
|
||||
importFile.value = null
|
||||
uploadRef.value?.clearFiles()
|
||||
getDataList()
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg || '????')
|
||||
useMessage().error(err.msg || '导入失败')
|
||||
} finally {
|
||||
importLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// ??
|
||||
// 编辑
|
||||
const handleEdit = (row: any) => {
|
||||
formDialogRef.value?.openDialog('edit', row)
|
||||
}
|
||||
|
||||
// ??
|
||||
// 删除
|
||||
const handleDelete = async (row: any) => {
|
||||
const { confirm } = useMessageBox()
|
||||
try {
|
||||
await confirm('??????????????')
|
||||
await confirm('确定要删除该记录吗?')
|
||||
await delObj([row.id])
|
||||
useMessage().success('????')
|
||||
useMessage().success('删除成功')
|
||||
getDataList()
|
||||
} catch (err: any) {
|
||||
if (err !== 'cancel') {
|
||||
useMessage().error(err.msg || '????')
|
||||
useMessage().error(err.msg || '删除失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ??????
|
||||
// 获取学年列表
|
||||
const getSchoolYearList = async () => {
|
||||
try {
|
||||
const res = await queryAllSchoolYear()
|
||||
@@ -487,7 +487,7 @@ const getSchoolYearList = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// ??????
|
||||
// 获取学期字典
|
||||
const getSchoolTermDict = async () => {
|
||||
try {
|
||||
const res = await getDicts('school_term')
|
||||
@@ -504,7 +504,7 @@ const getSchoolTermDict = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// ??????
|
||||
// 获取学院列表
|
||||
const getDeptListData = async () => {
|
||||
try {
|
||||
const res = await getDeptList()
|
||||
@@ -518,7 +518,7 @@ const getDeptListData = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// ??????
|
||||
// 获取班级列表
|
||||
const getClassListData = async () => {
|
||||
try {
|
||||
const res = await getClassListByRole()
|
||||
@@ -532,7 +532,7 @@ const getClassListData = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// ??????
|
||||
// 获取类型字典
|
||||
const getTypeDict = async () => {
|
||||
try {
|
||||
const res = await getDicts('conduct_type')
|
||||
@@ -549,7 +549,7 @@ const getTypeDict = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// ???
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
getSchoolYearList()
|
||||
getSchoolTermDict()
|
||||
|
||||
@@ -104,12 +104,62 @@
|
||||
</el-table>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
<!-- 查看详情弹窗(接口:queryDataByStuNo 通过学年学号查看详情,按当前学期筛选) -->
|
||||
<el-dialog
|
||||
v-model="viewDialogVisible"
|
||||
title="学期操行考核详情"
|
||||
width="800px"
|
||||
destroy-on-close
|
||||
@close="viewDetailList = []">
|
||||
<div v-if="viewRow" class="view-summary">
|
||||
<el-descriptions :column="2" border size="small">
|
||||
<el-descriptions-item label="学号">{{ viewRow.stuNo }}</el-descriptions-item>
|
||||
<el-descriptions-item label="姓名">{{ viewRow.realName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="学年">{{ queryForm.schoolYear }}</el-descriptions-item>
|
||||
<el-descriptions-item label="学期">{{ formatSchoolTerm(queryForm.schoolTerm) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="学期总评" :span="2">
|
||||
{{ viewRow.score != null && viewRow.score !== undefined ? Number(viewRow.score).toFixed(2) : '-' }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
<div class="view-detail-title">考核记录</div>
|
||||
<el-table
|
||||
:data="viewDetailList"
|
||||
v-loading="viewLoading"
|
||||
border
|
||||
size="small"
|
||||
max-height="400"
|
||||
:cell-style="tableStyle.cellStyle"
|
||||
:header-cell-style="tableStyle.headerCellStyle">
|
||||
<el-table-column type="index" label="序号" width="60" align="center" />
|
||||
<el-table-column prop="schoolTerm" label="学期" width="80" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="recordDate" label="考核日期" width="110" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="conductType" label="类型" width="80" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag :type="scope.row.conductType === '1' ? 'success' : 'danger'" size="small">
|
||||
{{ scope.row.conductType === '1' ? '加分' : scope.row.conductType === '0' ? '扣分' : scope.row.conductType || '-' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="score" label="分数" width="80" align="center">
|
||||
<template #default="scope">
|
||||
{{ scope.row.score != null && scope.row.score !== undefined ? Number(scope.row.score) : '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="description" label="情况记录" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column prop="remarks" label="备注" min-width="100" show-overflow-tooltip />
|
||||
</el-table>
|
||||
<template v-if="viewDetailList.length === 0 && !viewLoading">
|
||||
<el-empty description="暂无考核记录" :image-size="80" />
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="StuConductTerm">
|
||||
import { reactive, ref, onMounted, computed } from 'vue'
|
||||
import { getStuConductTerm } from "/@/api/stuwork/stuconduct";
|
||||
import { getStuConductTerm, queryDataByStuNo } from "/@/api/stuwork/stuconduct";
|
||||
import { getClassListByRole } from "/@/api/basic/basicclass";
|
||||
import { queryAllSchoolYear } from "/@/api/basic/basicyear";
|
||||
import { getDicts } from "/@/api/admin/dict";
|
||||
@@ -129,6 +179,10 @@ const schoolYearList = ref<any[]>([])
|
||||
const schoolTermList = ref<any[]>([])
|
||||
const classList = ref<any[]>([])
|
||||
const studentList = ref<any[]>([])
|
||||
const viewDialogVisible = ref(false)
|
||||
const viewLoading = ref(false)
|
||||
const viewRow = ref<any>(null)
|
||||
const viewDetailList = ref<any[]>([])
|
||||
|
||||
// 查询表单
|
||||
const queryForm = reactive({
|
||||
@@ -263,14 +317,20 @@ const getDataList = async () => {
|
||||
} else {
|
||||
studentList.value = []
|
||||
}
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg || '获取数据列表失败')
|
||||
} catch (_err) {
|
||||
studentList.value = []
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 格式化学期显示
|
||||
const formatSchoolTerm = (value: string | number) => {
|
||||
if (value === null || value === undefined || value === '') return '-'
|
||||
const dictItem = schoolTermList.value.find((item: any) => item.value == value)
|
||||
return dictItem ? dictItem.label : value
|
||||
}
|
||||
|
||||
// 重置
|
||||
const handleReset = () => {
|
||||
searchFormRef.value?.resetFields()
|
||||
@@ -280,10 +340,29 @@ const handleReset = () => {
|
||||
studentList.value = []
|
||||
}
|
||||
|
||||
// 查看详情
|
||||
const handleView = (row: any) => {
|
||||
// 可以跳转到详情页面或打开详情弹窗
|
||||
useMessage().info('查看详情功能待实现')
|
||||
// 查看详情(接口:GET /stuwork/stuconduct/queryDataByStuNo,按当前学年+学号拉取后筛本学期记录)
|
||||
const handleView = async (row: any) => {
|
||||
if (!queryForm.schoolYear || !row.stuNo) {
|
||||
useMessage().warning('缺少学年或学号')
|
||||
return
|
||||
}
|
||||
viewRow.value = row
|
||||
viewDialogVisible.value = true
|
||||
viewDetailList.value = []
|
||||
viewLoading.value = true
|
||||
try {
|
||||
const res = await queryDataByStuNo({
|
||||
schoolYear: queryForm.schoolYear,
|
||||
stuNo: row.stuNo
|
||||
})
|
||||
const list = Array.isArray(res.data) ? res.data : []
|
||||
const term = queryForm.schoolTerm
|
||||
viewDetailList.value = term ? list.filter((r: any) => String(r.schoolTerm) === String(term)) : list
|
||||
} catch (_err) {
|
||||
viewDetailList.value = []
|
||||
} finally {
|
||||
viewLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 获取学年列表
|
||||
@@ -350,6 +429,15 @@ onMounted(() => {
|
||||
}
|
||||
}
|
||||
|
||||
.view-summary {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.view-detail-title {
|
||||
margin-bottom: 8px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
// 确保页面可以滚动
|
||||
.layout-padding {
|
||||
height: 100%;
|
||||
|
||||
@@ -90,12 +90,61 @@
|
||||
</el-table>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
<!-- 查看详情弹窗(接口:queryDataByStuNo 通过学年学号查看详情) -->
|
||||
<el-dialog
|
||||
v-model="viewDialogVisible"
|
||||
title="学年操行考核详情"
|
||||
width="800px"
|
||||
destroy-on-close
|
||||
@close="viewDetailList = []">
|
||||
<div v-if="viewRow" class="view-summary">
|
||||
<el-descriptions :column="2" border size="small">
|
||||
<el-descriptions-item label="学号">{{ viewRow.stuNo }}</el-descriptions-item>
|
||||
<el-descriptions-item label="姓名">{{ viewRow.realName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="学年">{{ queryForm.schoolYear }}</el-descriptions-item>
|
||||
<el-descriptions-item label="学年总评">
|
||||
{{ viewRow.score != null && viewRow.score !== undefined ? Number(viewRow.score).toFixed(2) : '-' }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
<div class="view-detail-title">考核记录</div>
|
||||
<el-table
|
||||
:data="viewDetailList"
|
||||
v-loading="viewLoading"
|
||||
border
|
||||
size="small"
|
||||
max-height="400"
|
||||
:cell-style="tableStyle.cellStyle"
|
||||
:header-cell-style="tableStyle.headerCellStyle">
|
||||
<el-table-column type="index" label="序号" width="60" align="center" />
|
||||
<el-table-column prop="schoolTerm" label="学期" width="80" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="recordDate" label="考核日期" width="110" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="conductType" label="类型" width="80" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag :type="scope.row.conductType === '1' ? 'success' : 'danger'" size="small">
|
||||
{{ scope.row.conductType === '1' ? '加分' : scope.row.conductType === '0' ? '扣分' : scope.row.conductType || '-' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="score" label="分数" width="80" align="center">
|
||||
<template #default="scope">
|
||||
{{ scope.row.score != null && scope.row.score !== undefined ? Number(scope.row.score) : '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="description" label="情况记录" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column prop="remarks" label="备注" min-width="100" show-overflow-tooltip />
|
||||
</el-table>
|
||||
<template v-if="viewDetailList.length === 0 && !viewLoading">
|
||||
<el-empty description="暂无考核记录" :image-size="80" />
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="StuConductYear">
|
||||
import { reactive, ref, onMounted, computed } from 'vue'
|
||||
import { getStuConductYear } from "/@/api/stuwork/stuconduct";
|
||||
import { getStuConductYear, queryDataByStuNo } from "/@/api/stuwork/stuconduct";
|
||||
import { getClassListByRole } from "/@/api/basic/basicclass";
|
||||
import { queryAllSchoolYear } from "/@/api/basic/basicyear";
|
||||
import { useMessage } from "/@/hooks/message";
|
||||
@@ -113,6 +162,10 @@ const loading = ref(false)
|
||||
const schoolYearList = ref<any[]>([])
|
||||
const classList = ref<any[]>([])
|
||||
const studentList = ref<any[]>([])
|
||||
const viewDialogVisible = ref(false)
|
||||
const viewLoading = ref(false)
|
||||
const viewRow = ref<any>(null)
|
||||
const viewDetailList = ref<any[]>([])
|
||||
|
||||
// 查询表单
|
||||
const queryForm = reactive({
|
||||
@@ -245,8 +298,7 @@ const getDataList = async () => {
|
||||
} else {
|
||||
studentList.value = []
|
||||
}
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg || '获取数据列表失败')
|
||||
} catch (_err) {
|
||||
studentList.value = []
|
||||
} finally {
|
||||
loading.value = false
|
||||
@@ -261,10 +313,27 @@ const handleReset = () => {
|
||||
studentList.value = []
|
||||
}
|
||||
|
||||
// 查看详情
|
||||
const handleView = (row: any) => {
|
||||
// 可以跳转到详情页面或打开详情弹窗
|
||||
useMessage().info('查看详情功能待实现')
|
||||
// 查看详情(接口:GET /stuwork/stuconduct/queryDataByStuNo,通过学年学号查看详情)
|
||||
const handleView = async (row: any) => {
|
||||
if (!queryForm.schoolYear || !row.stuNo) {
|
||||
useMessage().warning('缺少学年或学号')
|
||||
return
|
||||
}
|
||||
viewRow.value = row
|
||||
viewDialogVisible.value = true
|
||||
viewDetailList.value = []
|
||||
viewLoading.value = true
|
||||
try {
|
||||
const res = await queryDataByStuNo({
|
||||
schoolYear: queryForm.schoolYear,
|
||||
stuNo: row.stuNo
|
||||
})
|
||||
viewDetailList.value = Array.isArray(res.data) ? res.data : []
|
||||
} catch (_err) {
|
||||
viewDetailList.value = []
|
||||
} finally {
|
||||
viewLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 获取学年列表
|
||||
@@ -313,6 +382,15 @@ onMounted(() => {
|
||||
}
|
||||
}
|
||||
|
||||
.view-summary {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.view-detail-title {
|
||||
margin-bottom: 8px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
// 确保页面可以滚动
|
||||
.layout-padding {
|
||||
height: 100%;
|
||||
|
||||
309
src/views/stuwork/stugraducheck/index.vue
Normal file
309
src/views/stuwork/stugraducheck/index.vue
Normal file
@@ -0,0 +1,309 @@
|
||||
<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="graduYear">
|
||||
<el-select
|
||||
v-model="searchForm.graduYear"
|
||||
placeholder="请选择毕业年份"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 140px">
|
||||
<el-option
|
||||
v-for="y in graduYearOptions"
|
||||
:key="y"
|
||||
:label="y + '年'"
|
||||
:value="y" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="毕业状态" prop="status">
|
||||
<el-select
|
||||
v-model="searchForm.status"
|
||||
placeholder="请选择"
|
||||
clearable
|
||||
style="width: 140px">
|
||||
<el-option label="待确认" value="0" />
|
||||
<el-option label="确认毕业" value="1" />
|
||||
<el-option label="不可毕业" value="-1" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="毕业类型" prop="type">
|
||||
<el-select
|
||||
v-model="searchForm.type"
|
||||
placeholder="请选择"
|
||||
clearable
|
||||
style="width: 120px">
|
||||
<el-option label="段段清" value="1" />
|
||||
<el-option label="正常毕业" value="2" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="学号" prop="stuNo">
|
||||
<el-input
|
||||
v-model="searchForm.stuNo"
|
||||
placeholder="请输入学号"
|
||||
clearable
|
||||
style="width: 140px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="姓名" prop="realName">
|
||||
<el-input
|
||||
v-model="searchForm.realName"
|
||||
placeholder="请输入姓名"
|
||||
clearable
|
||||
style="width: 120px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="学院" prop="deptCode">
|
||||
<el-select
|
||||
v-model="searchForm.deptCode"
|
||||
placeholder="请选择学院"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 160px">
|
||||
<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="班号" prop="classNo">
|
||||
<el-input
|
||||
v-model="searchForm.classNo"
|
||||
placeholder="请输入班号"
|
||||
clearable
|
||||
style="width: 120px" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<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-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
|
||||
v-model:showSearch="showSearch"
|
||||
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-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 #header>
|
||||
<el-icon><List /></el-icon>
|
||||
</template>
|
||||
<template #default="{ $index }">
|
||||
{{ $index + 1 + ((state.pagination?.current || 1) - 1) * (state.pagination?.size || 10) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<template v-for="col in visibleColumnsSorted" :key="col.prop || col.label">
|
||||
<el-table-column
|
||||
v-if="checkColumnVisible(col.prop || '') && col.prop !== '操作'"
|
||||
:prop="col.prop"
|
||||
:label="col.label"
|
||||
:min-width="col.minWidth"
|
||||
:width="col.width"
|
||||
show-overflow-tooltip
|
||||
align="center">
|
||||
<template #header>
|
||||
<el-icon v-if="col.icon"><component :is="col.icon" /></el-icon>
|
||||
<span :style="{ marginLeft: col.icon ? '4px' : '0' }">{{ col.label }}</span>
|
||||
</template>
|
||||
<template v-if="col.prop === 'status'" #default="scope">
|
||||
<el-tag :type="statusTagType(scope.row.status)" size="small">
|
||||
{{ formatStatus(scope.row.status) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
<template v-else-if="col.prop === 'type'" #default="scope">
|
||||
{{ scope.row.type === '1' ? '段段清' : scope.row.type === '2' ? '正常毕业' : scope.row.type || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
<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="Stugraducheck">
|
||||
import { reactive, ref, computed, onMounted } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { BasicTableProps, useTable } from '/@/hooks/table'
|
||||
import { fetchList } from '/@/api/stuwork/stugraducheck'
|
||||
import { getDeptList } from '/@/api/basic/basicclass'
|
||||
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
|
||||
import {
|
||||
List,
|
||||
Calendar,
|
||||
UserFilled,
|
||||
Document,
|
||||
Menu,
|
||||
Search,
|
||||
Grid
|
||||
} from '@element-plus/icons-vue'
|
||||
import { useTableColumnControl } from '/@/hooks/tableColumn'
|
||||
|
||||
const route = useRoute()
|
||||
const searchFormRef = ref()
|
||||
const columnControlRef = ref<any>()
|
||||
const showSearch = ref(true)
|
||||
const deptList = ref<any[]>([])
|
||||
|
||||
// 毕业年份:当前年前后各 5 年
|
||||
const graduYearOptions = computed(() => {
|
||||
const y = new Date().getFullYear()
|
||||
return Array.from({ length: 11 }, (_, i) => y - 5 + i)
|
||||
})
|
||||
|
||||
// 表格列配置(与其它页面一致,icon 写在列上)
|
||||
const tableColumns = [
|
||||
{ prop: 'stuNo', label: '学号', icon: UserFilled },
|
||||
{ prop: 'realName', label: '姓名', icon: UserFilled },
|
||||
{ prop: 'classNo', label: '班号', icon: Grid },
|
||||
{ prop: 'majorName', label: '专业名称', icon: Document, minWidth: 140 },
|
||||
{ prop: 'graduYear', label: '毕业年份', icon: Calendar },
|
||||
{ prop: 'status', label: '毕业状态', icon: Document },
|
||||
{ prop: 'type', label: '毕业类型', icon: Document },
|
||||
{ prop: 'scoreCondition', label: '学分情况', icon: Document, minWidth: 100 },
|
||||
{ prop: 'skillCondition', label: '技能情况', icon: Document, minWidth: 100 },
|
||||
{ prop: 'conductCondition', label: '操行情况', icon: Document, minWidth: 100 }
|
||||
]
|
||||
|
||||
const {
|
||||
visibleColumns,
|
||||
visibleColumnsSorted,
|
||||
checkColumnVisible,
|
||||
handleColumnChange,
|
||||
handleColumnOrderChange
|
||||
} = useTableColumnControl(tableColumns, route.path)
|
||||
|
||||
// 搜索表单(与接口文档一致:graduYear, status, type, stuNo, realName, deptCode, classCode 等)
|
||||
const searchForm = reactive({
|
||||
graduYear: '',
|
||||
status: '',
|
||||
type: '',
|
||||
stuNo: '',
|
||||
realName: '',
|
||||
deptCode: '',
|
||||
classNo: ''
|
||||
})
|
||||
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
queryForm: searchForm,
|
||||
pageList: fetchList,
|
||||
props: {
|
||||
item: 'records',
|
||||
totalCount: 'total'
|
||||
},
|
||||
createdIsNeed: true
|
||||
})
|
||||
|
||||
const {
|
||||
getDataList,
|
||||
currentChangeHandle,
|
||||
sizeChangeHandle,
|
||||
tableStyle
|
||||
} = useTable(state)
|
||||
|
||||
const formatStatus = (status: string) => {
|
||||
const map: Record<string, string> = { '0': '待确认', '1': '确认毕业', '-1': '不可毕业' }
|
||||
return map[status] || status || '-'
|
||||
}
|
||||
|
||||
const statusTagType = (status: string) => {
|
||||
const map: Record<string, string> = { '0': 'warning', '1': 'success', '-1': 'danger' }
|
||||
return map[status] || 'info'
|
||||
}
|
||||
|
||||
const handleSearch = () => {
|
||||
getDataList()
|
||||
}
|
||||
|
||||
const handleReset = () => {
|
||||
searchFormRef.value?.resetFields()
|
||||
searchForm.graduYear = ''
|
||||
searchForm.status = ''
|
||||
searchForm.type = ''
|
||||
searchForm.stuNo = ''
|
||||
searchForm.realName = ''
|
||||
searchForm.deptCode = ''
|
||||
searchForm.classNo = ''
|
||||
getDataList()
|
||||
}
|
||||
|
||||
const loadDeptList = async () => {
|
||||
try {
|
||||
const res = await getDeptList()
|
||||
deptList.value = Array.isArray(res.data) ? res.data : []
|
||||
} catch (err) {
|
||||
deptList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadDeptList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '/@/assets/styles/modern-page.scss';
|
||||
</style>
|
||||
@@ -188,9 +188,13 @@ const handleClassChange = async (classCode: string) => {
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await queryStudentListByClass({ classCode, current: 1, size: 10000 })
|
||||
if (res.data && Array.isArray(res.data.records)) {
|
||||
studentList.value = res.data.records
|
||||
// 接口文档:GET 仅 classCode,返回 data 为数组
|
||||
const res = await queryStudentListByClass({ classCode })
|
||||
const data = res?.data
|
||||
if (Array.isArray(data)) {
|
||||
studentList.value = data
|
||||
} else if (data?.records && Array.isArray(data.records)) {
|
||||
studentList.value = data.records
|
||||
} else {
|
||||
studentList.value = []
|
||||
}
|
||||
|
||||
@@ -181,9 +181,13 @@ const handleClassChange = async (classCode: string) => {
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await queryStudentListByClass({ classCode, current: 1, size: 10000 })
|
||||
if (res.data && Array.isArray(res.data.records)) {
|
||||
studentList.value = res.data.records
|
||||
// 接口文档:GET,仅 classCode;返回 data 为数组,非 data.records
|
||||
const res = await queryStudentListByClass({ classCode })
|
||||
const data = res?.data
|
||||
if (Array.isArray(data)) {
|
||||
studentList.value = data
|
||||
} else if (data?.records && Array.isArray(data.records)) {
|
||||
studentList.value = data.records
|
||||
} else {
|
||||
studentList.value = []
|
||||
}
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
<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="schoolYear">
|
||||
<el-form-item label="学年" prop="schoolYear">
|
||||
<el-select
|
||||
v-model="searchForm.schoolYear"
|
||||
placeholder="?????"
|
||||
placeholder="请选择学年"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 200px">
|
||||
@@ -27,10 +27,10 @@
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="??" prop="schoolTerm">
|
||||
<el-form-item label="学期" prop="schoolTerm">
|
||||
<el-select
|
||||
v-model="searchForm.schoolTerm"
|
||||
placeholder="?????"
|
||||
placeholder="请选择学期"
|
||||
clearable
|
||||
style="width: 200px">
|
||||
<el-option
|
||||
@@ -41,10 +41,10 @@
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="??" prop="deptCode">
|
||||
<el-form-item label="学院" prop="deptCode">
|
||||
<el-select
|
||||
v-model="searchForm.deptCode"
|
||||
placeholder="?????"
|
||||
placeholder="请选择学院"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 200px">
|
||||
@@ -56,10 +56,10 @@
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="??" prop="classCode">
|
||||
<el-form-item label="班级" prop="classCode">
|
||||
<el-select
|
||||
v-model="searchForm.classCode"
|
||||
placeholder="?????"
|
||||
placeholder="请选择班级"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 200px">
|
||||
@@ -71,34 +71,34 @@
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="??" prop="realName">
|
||||
<el-form-item label="姓名" prop="realName">
|
||||
<el-input
|
||||
v-model="searchForm.realName"
|
||||
placeholder="?????"
|
||||
placeholder="请输入姓名"
|
||||
clearable
|
||||
style="width: 200px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="??" prop="stuNo">
|
||||
<el-form-item label="学号" prop="stuNo">
|
||||
<el-input
|
||||
v-model="searchForm.stuNo"
|
||||
placeholder="?????"
|
||||
placeholder="请输入学号"
|
||||
clearable
|
||||
style="width: 200px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="????" prop="punlishMonth">
|
||||
<el-form-item label="处分月份" prop="punlishMonth">
|
||||
<el-date-picker
|
||||
v-model="searchForm.punlishMonth"
|
||||
type="month"
|
||||
placeholder="??????"
|
||||
placeholder="请选择处分月份"
|
||||
format="YYYY-MM"
|
||||
value-format="YYYY-MM"
|
||||
clearable
|
||||
style="width: 200px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="????" prop="punlishLevel">
|
||||
<el-form-item label="处分等级" prop="punlishLevel">
|
||||
<el-select
|
||||
v-model="searchForm.punlishLevel"
|
||||
placeholder="???????"
|
||||
placeholder="请选择处分等级"
|
||||
clearable
|
||||
style="width: 200px">
|
||||
<el-option
|
||||
@@ -110,33 +110,33 @@
|
||||
</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-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="FolderAdd"
|
||||
type="primary"
|
||||
@click="formDialogRef.openDialog()">
|
||||
??
|
||||
新增
|
||||
</el-button>
|
||||
<el-button
|
||||
icon="Download"
|
||||
type="success"
|
||||
class="ml10"
|
||||
@click="handleExport">
|
||||
??
|
||||
导出
|
||||
</el-button>
|
||||
<right-toolbar
|
||||
v-model:showSearch="showSearch"
|
||||
@@ -152,7 +152,7 @@
|
||||
@order-change="handleColumnOrderChange"
|
||||
>
|
||||
<template #trigger>
|
||||
<el-tooltip class="item" effect="dark" content="???" placement="top">
|
||||
<el-tooltip class="item" effect="dark" content="列设置" placement="top">
|
||||
<el-button circle style="margin-left: 0;">
|
||||
<el-icon><Menu /></el-icon>
|
||||
</el-button>
|
||||
@@ -164,7 +164,7 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- ?? -->
|
||||
<!-- 表格 -->
|
||||
<el-table
|
||||
:data="state.dataList"
|
||||
v-loading="state.loading"
|
||||
@@ -172,7 +172,7 @@
|
||||
:cell-style="tableStyle.cellStyle"
|
||||
:header-cell-style="tableStyle.headerCellStyle"
|
||||
class="modern-table">
|
||||
<el-table-column type="index" label="??" width="70" align="center">
|
||||
<el-table-column type="index" label="序号" width="70" align="center">
|
||||
<template #header>
|
||||
<el-icon><List /></el-icon>
|
||||
</template>
|
||||
@@ -182,7 +182,7 @@
|
||||
</el-table-column>
|
||||
<template v-for="col in visibleColumnsSorted" :key="col.prop || col.label">
|
||||
<el-table-column
|
||||
v-if="checkColumnVisible(col.prop || '') && col.prop !== '??'"
|
||||
v-if="checkColumnVisible(col.prop || '') && col.prop !== '序号'"
|
||||
:prop="col.prop"
|
||||
:label="col.label"
|
||||
show-overflow-tooltip
|
||||
@@ -193,40 +193,40 @@
|
||||
<el-icon><component :is="columnConfigMap[col.prop]?.icon || Calendar" /></el-icon>
|
||||
<span style="margin-left: 4px">{{ col.label }}</span>
|
||||
</template>
|
||||
<!-- ???? -->
|
||||
<!-- 学期 -->
|
||||
<template v-if="col.prop === 'schoolTerm'" #default="scope">
|
||||
<el-tag size="small" type="primary" effect="plain">
|
||||
{{ formatSchoolTerm(scope.row.schoolTerm) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
<!-- ?????? -->
|
||||
<!-- 处分开始日期 -->
|
||||
<template v-else-if="col.prop === 'punlishStartDate'" #default="scope">
|
||||
<span>{{ scope.row.punlishStartDate ? formatDate(scope.row.punlishStartDate) : '-' }}</span>
|
||||
</template>
|
||||
<!-- ?????? -->
|
||||
<!-- 处分结束日期 -->
|
||||
<template v-else-if="col.prop === 'punlishEndDate'" #default="scope">
|
||||
<span>{{ scope.row.punlishEndDate ? formatDate(scope.row.punlishEndDate) : '-' }}</span>
|
||||
</template>
|
||||
<!-- ?????? -->
|
||||
<!-- 处分等级 -->
|
||||
<template v-else-if="col.prop === 'punlishLevel'" #default="scope">
|
||||
<el-tag size="small" type="danger" effect="plain">
|
||||
{{ formatPunlishLevel(scope.row.punlishLevel) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
<!-- ???? -->
|
||||
<!-- 状态 -->
|
||||
<template v-else-if="col.prop === 'publishStatus'" #default="scope">
|
||||
<StatusTag
|
||||
:value="scope.row.publishStatus"
|
||||
:options="[{ label: '??', value: '1' }, { label: '???', value: '2' }, { label: '???', value: '3' }]"
|
||||
:options="[{ label: '已发布', value: '1' }, { label: '已撤销', value: '2' }, { label: '已取消', value: '3' }]"
|
||||
:type-map="{ '1': { type: 'danger', effect: 'light' }, '2': { type: 'success', effect: 'light' }, '3': { type: 'info', effect: 'light' } }"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
<el-table-column label="??" width="250" align="center" fixed="right">
|
||||
<el-table-column label="操作" width="250" align="center" fixed="right">
|
||||
<template #header>
|
||||
<el-icon><Setting /></el-icon>
|
||||
<span style="margin-left: 4px">??</span>
|
||||
<span style="margin-left: 4px">操作</span>
|
||||
</template>
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
@@ -234,34 +234,34 @@
|
||||
link
|
||||
type="primary"
|
||||
@click="handleReport(scope.row)">
|
||||
????
|
||||
处分报告
|
||||
</el-button>
|
||||
<el-button
|
||||
icon="CircleClose"
|
||||
link
|
||||
type="warning"
|
||||
@click="handleCancel(scope.row)">
|
||||
????
|
||||
撤销处分
|
||||
</el-button>
|
||||
<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"
|
||||
@@ -271,7 +271,7 @@
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<!-- ??/???? -->
|
||||
<!-- 新增/编辑 -->
|
||||
<form-dialog ref="formDialogRef" @refresh="getDataList" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -294,7 +294,7 @@ import { useTableColumnControl } from '/@/hooks/tableColumn'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
const StatusTag = defineAsyncComponent(() => import('/@/components/StatusTag/index.vue'))
|
||||
|
||||
// ???
|
||||
// 定义变量
|
||||
const route = useRoute()
|
||||
const searchFormRef = ref()
|
||||
const columnControlRef = ref<any>()
|
||||
@@ -307,22 +307,22 @@ const punlishLevelList = ref<any[]>([])
|
||||
const publishStatusList = ref<any[]>([])
|
||||
const formDialogRef = ref()
|
||||
|
||||
// ???
|
||||
// 表格列配置
|
||||
const tableColumns = [
|
||||
{ prop: 'schoolYear', label: '??' },
|
||||
{ prop: 'schoolTerm', label: '??' },
|
||||
{ prop: 'deptName', label: '??' },
|
||||
{ prop: 'classNo', label: '??' },
|
||||
{ prop: 'stuRealName', label: '??' },
|
||||
{ prop: 'stuNo', label: '??' },
|
||||
{ prop: 'punlishStartDate', label: '??????', width: 180 },
|
||||
{ prop: 'punlishEndDate', label: '???', width: 180 },
|
||||
{ prop: 'punlishLevel', label: '????' },
|
||||
{ prop: 'punlishContent', label: '????', minWidth: 150 },
|
||||
{ prop: 'publishStatus', label: '????' }
|
||||
{ prop: 'schoolYear', label: '学年' },
|
||||
{ prop: 'schoolTerm', label: '学期' },
|
||||
{ prop: 'deptName', label: '学院' },
|
||||
{ prop: 'classNo', label: '班级' },
|
||||
{ prop: 'stuRealName', label: '姓名' },
|
||||
{ prop: 'stuNo', label: '学号' },
|
||||
{ prop: 'punlishStartDate', label: '处分开始日期', width: 180 },
|
||||
{ prop: 'punlishEndDate', label: '结束日期', width: 180 },
|
||||
{ prop: 'punlishLevel', label: '处分等级' },
|
||||
{ prop: 'punlishContent', label: '处分内容', minWidth: 150 },
|
||||
{ prop: 'publishStatus', label: '发布状态' }
|
||||
]
|
||||
|
||||
// ?????
|
||||
// 列配置映射
|
||||
const columnConfigMap: Record<string, { icon: any }> = {
|
||||
schoolYear: { icon: Calendar },
|
||||
schoolTerm: { icon: Clock },
|
||||
@@ -337,16 +337,16 @@ const columnConfigMap: Record<string, { icon: any }> = {
|
||||
publishStatus: { icon: CircleCheck }
|
||||
}
|
||||
|
||||
// ????? hook
|
||||
// 表格列控制 hook
|
||||
const {
|
||||
visibleColumns,
|
||||
visibleColumnsSorted,
|
||||
checkColumnVisible,
|
||||
handleColumnChange,
|
||||
handleColumnOrderChange
|
||||
} = useTableColumnControl(tableColumns, route.path)
|
||||
} = useTableColumnControl(tableColumns)
|
||||
|
||||
// ????
|
||||
// 搜索表单
|
||||
const searchForm = reactive({
|
||||
schoolYear: '',
|
||||
schoolTerm: '',
|
||||
@@ -359,11 +359,11 @@ const searchForm = reactive({
|
||||
punlishMonthArray: [] as string[]
|
||||
})
|
||||
|
||||
// ?? useTable
|
||||
// 使用 useTable
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
queryForm: searchForm,
|
||||
pageList: async (params: any) => {
|
||||
// ???????? API
|
||||
// 处理处分月份参数 API
|
||||
const queryParams = { ...params }
|
||||
if (queryParams.punlishMonth) {
|
||||
queryParams.punlishMonth = [queryParams.punlishMonth]
|
||||
@@ -388,7 +388,7 @@ const {
|
||||
tableStyle
|
||||
} = useTable(state)
|
||||
|
||||
// ?????
|
||||
// 格式化学期
|
||||
const formatSchoolTerm = (value: string | number) => {
|
||||
if (value === null || value === undefined || value === '') {
|
||||
return '-'
|
||||
@@ -397,36 +397,36 @@ const formatSchoolTerm = (value: string | number) => {
|
||||
return dictItem ? dictItem.label : value
|
||||
}
|
||||
|
||||
// ?????
|
||||
// 格式化日期
|
||||
const formatDate = (dateStr: string) => {
|
||||
if (!dateStr) return '-'
|
||||
// ??????????
|
||||
// 处理日期时间格式
|
||||
if (dateStr.includes(' ')) {
|
||||
return dateStr.split(' ')[0]
|
||||
}
|
||||
return dateStr
|
||||
}
|
||||
|
||||
// ???????
|
||||
// 格式化处分等级
|
||||
const formatPunlishLevel = (value: string) => {
|
||||
if (!value) return '-'
|
||||
const item = punlishLevelList.value.find((item: any) => item.value === value)
|
||||
return item ? item.label : value
|
||||
}
|
||||
|
||||
// ???????
|
||||
// 格式化发布状态
|
||||
const formatPublishStatus = (value: string) => {
|
||||
if (!value) return '-'
|
||||
const item = publishStatusList.value.find((item: any) => item.value === value)
|
||||
return item ? item.label : value
|
||||
}
|
||||
|
||||
// ??
|
||||
// 查询
|
||||
const handleSearch = () => {
|
||||
getDataList()
|
||||
}
|
||||
|
||||
// ??
|
||||
// 重置
|
||||
const handleReset = () => {
|
||||
searchFormRef.value?.resetFields()
|
||||
searchForm.schoolYear = ''
|
||||
@@ -440,65 +440,65 @@ const handleReset = () => {
|
||||
getDataList()
|
||||
}
|
||||
|
||||
// ??
|
||||
// 编辑
|
||||
const handleEdit = (row: any) => {
|
||||
formDialogRef.value?.openDialog('edit', row)
|
||||
}
|
||||
|
||||
// ??
|
||||
// 删除
|
||||
const handleDelete = async (row: any) => {
|
||||
try {
|
||||
await useMessageBox().confirm('???????????')
|
||||
await useMessageBox().confirm('确定要删除该记录吗?')
|
||||
await delObj([row.id])
|
||||
useMessage().success('????')
|
||||
useMessage().success('删除成功')
|
||||
getDataList()
|
||||
} catch (err: any) {
|
||||
if (err !== 'cancel') {
|
||||
useMessage().error(err.msg || '????')
|
||||
useMessage().error(err.msg || '删除失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ????
|
||||
// 处分报告
|
||||
const handleReport = (row: any) => {
|
||||
useMessage().info('?????????')
|
||||
// TODO: ??????
|
||||
useMessage().info('功能开发中')
|
||||
// TODO: 实现处分报告
|
||||
}
|
||||
|
||||
// ????
|
||||
// 撤销处分
|
||||
const handleCancel = async (row: any) => {
|
||||
const { confirm } = useMessageBox()
|
||||
try {
|
||||
await confirm('??????????')
|
||||
await confirm('确定要撤销该处分吗?')
|
||||
|
||||
// ??????
|
||||
// 获取详情数据
|
||||
const res = await getDetail(row.id)
|
||||
if (!res.data) {
|
||||
useMessage().error('????????')
|
||||
useMessage().error('获取数据失败')
|
||||
return
|
||||
}
|
||||
|
||||
// ? publishStatus ?? "0" ????
|
||||
// 将 publishStatus 设置为 "0" 表示撤销
|
||||
const updateData = {
|
||||
...res.data,
|
||||
publishStatus: '0'
|
||||
}
|
||||
|
||||
await revokePunishment(updateData)
|
||||
useMessage().success('??????')
|
||||
useMessage().success('撤销成功')
|
||||
getDataList()
|
||||
} catch (err: any) {
|
||||
if (err !== 'cancel') {
|
||||
useMessage().error(err.msg || '??????')
|
||||
useMessage().error(err.msg || '撤销失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ??
|
||||
// 导出
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
const params: any = { ...searchForm }
|
||||
// ???????? API
|
||||
// 处理处分月份参数 API
|
||||
if (params.punlishMonth) {
|
||||
params.punlishMonth = [params.punlishMonth]
|
||||
} else {
|
||||
@@ -506,23 +506,23 @@ const handleExport = async () => {
|
||||
}
|
||||
delete params.punlishMonthArray
|
||||
const res = await exportData(params)
|
||||
// ????
|
||||
// 下载文件
|
||||
const blob = new Blob([res.data])
|
||||
const elink = document.createElement('a')
|
||||
elink.download = '????.xlsx'
|
||||
elink.download = '学生处分.xlsx'
|
||||
elink.style.display = 'none'
|
||||
elink.href = URL.createObjectURL(blob)
|
||||
document.body.appendChild(elink)
|
||||
elink.click()
|
||||
URL.revokeObjectURL(elink.href)
|
||||
document.body.removeChild(elink)
|
||||
useMessage().success('????')
|
||||
useMessage().success('导出成功')
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg || '????')
|
||||
useMessage().error(err.msg || '导出失败')
|
||||
}
|
||||
}
|
||||
|
||||
// ??????
|
||||
// 获取学年列表
|
||||
const getSchoolYearList = async () => {
|
||||
try {
|
||||
const res = await queryAllSchoolYear()
|
||||
@@ -534,7 +534,7 @@ const getSchoolYearList = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// ??????
|
||||
// 获取学期字典
|
||||
const getSchoolTermDict = async () => {
|
||||
try {
|
||||
const res = await getDicts('school_term')
|
||||
@@ -549,7 +549,7 @@ const getSchoolTermDict = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// ??????
|
||||
// 获取学院列表
|
||||
const getDeptListData = async () => {
|
||||
try {
|
||||
const res = await getDeptListByLevelTwo()
|
||||
@@ -561,7 +561,7 @@ const getDeptListData = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// ??????
|
||||
// 获取班级列表
|
||||
const getClassListData = async () => {
|
||||
try {
|
||||
const res = await getClassListByRole()
|
||||
@@ -573,7 +573,7 @@ const getClassListData = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// ????????
|
||||
// 获取处分等级字典
|
||||
const getPunlishLevelDict = async () => {
|
||||
try {
|
||||
const res = await getDicts('punlish_level')
|
||||
@@ -588,7 +588,7 @@ const getPunlishLevelDict = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// ????????
|
||||
// 获取发布状态字典
|
||||
const getPublishStatusDict = async () => {
|
||||
try {
|
||||
const res = await getDicts('publish_status')
|
||||
|
||||
@@ -305,13 +305,18 @@ const getClassListData = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 获取学生列表
|
||||
// 获取学生列表(接口文档:GET 仅 classCode,返回 data 为数组)
|
||||
const getStudentListData = async () => {
|
||||
if (!form.classCode) return
|
||||
try {
|
||||
const res = await queryStudentListByClass(form.classCode)
|
||||
if (res.data) {
|
||||
studentList.value = Array.isArray(res.data) ? res.data : []
|
||||
const res = await queryStudentListByClass({ classCode: form.classCode })
|
||||
const data = res?.data
|
||||
if (Array.isArray(data)) {
|
||||
studentList.value = data
|
||||
} else if (data?.records && Array.isArray(data.records)) {
|
||||
studentList.value = data.records
|
||||
} else {
|
||||
studentList.value = []
|
||||
}
|
||||
} catch (err) {
|
||||
studentList.value = []
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
:model="form"
|
||||
:rules="dataRules"
|
||||
label-width="120px"
|
||||
v-loading="loading">
|
||||
v-loading="loading"
|
||||
:key="form.turnoverType">
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="学年" prop="schoolYear">
|
||||
@@ -47,7 +48,7 @@
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-col :span="12" class="mb20" v-if="!isDropoutType">
|
||||
<el-form-item label="原班级" prop="oldClassCode">
|
||||
<el-select
|
||||
v-model="form.oldClassCode"
|
||||
@@ -66,7 +67,7 @@
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-col :span="12" class="mb20" v-if="!isDropoutType">
|
||||
<el-form-item label="现班级" prop="newClassCode">
|
||||
<el-select
|
||||
v-model="form.newClassCode"
|
||||
@@ -90,7 +91,8 @@
|
||||
v-model="form.turnoverType"
|
||||
placeholder="请选择异动类型"
|
||||
clearable
|
||||
style="width: 100%">
|
||||
style="width: 100%"
|
||||
@change="handleTurnoverTypeChange">
|
||||
<el-option
|
||||
v-for="item in turnoverTypeList"
|
||||
:key="item.value"
|
||||
@@ -299,30 +301,52 @@ const selectedStudentsText = computed(() => {
|
||||
return `已选择 ${form.selectedStudents.length} 名学生`
|
||||
})
|
||||
|
||||
// 定义校验规则
|
||||
const dataRules = {
|
||||
schoolYear: [
|
||||
{ required: true, message: '请选择学年', trigger: 'change' }
|
||||
],
|
||||
schoolTerm: [
|
||||
{ required: true, message: '请选择学期', trigger: 'change' }
|
||||
],
|
||||
oldClassCode: [
|
||||
{ required: true, message: '请选择原班级', trigger: 'change' }
|
||||
],
|
||||
newClassCode: [
|
||||
{ required: true, message: '请选择现班级', trigger: 'change' }
|
||||
],
|
||||
turnoverType: [
|
||||
{ required: true, message: '请选择异动类型', trigger: 'change' }
|
||||
],
|
||||
turnoverDate: [
|
||||
{ required: true, message: '请选择异动时间', trigger: 'change' }
|
||||
],
|
||||
selectedStudents: [
|
||||
{ required: true, message: '请至少选择一个学生', trigger: 'change', type: 'array', min: 1 }
|
||||
]
|
||||
}
|
||||
// 判断是否为退学类型
|
||||
const isDropoutType = computed(() => {
|
||||
if (!form.turnoverType) return false
|
||||
// 查找异动类型字典中 label 包含"退学"的项
|
||||
const dropoutItem = turnoverTypeList.value.find((item: any) => {
|
||||
const label = item.label || item.dictLabel || item.name || ''
|
||||
return label.includes('退学')
|
||||
})
|
||||
if (dropoutItem) {
|
||||
return dropoutItem.value === form.turnoverType || dropoutItem.dictValue === form.turnoverType || dropoutItem.code === form.turnoverType
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
// 定义校验规则(动态)
|
||||
const dataRules = computed(() => {
|
||||
const rules: any = {
|
||||
schoolYear: [
|
||||
{ required: true, message: '请选择学年', trigger: 'change' }
|
||||
],
|
||||
schoolTerm: [
|
||||
{ required: true, message: '请选择学期', trigger: 'change' }
|
||||
],
|
||||
turnoverType: [
|
||||
{ required: true, message: '请选择异动类型', trigger: 'change' }
|
||||
],
|
||||
turnoverDate: [
|
||||
{ required: true, message: '请选择异动时间', trigger: 'change' }
|
||||
],
|
||||
selectedStudents: [
|
||||
{ required: true, message: '请至少选择一个学生', trigger: 'change', type: 'array', min: 1 }
|
||||
]
|
||||
}
|
||||
|
||||
// 如果不是退学类型,原班级和现班级为必填
|
||||
if (!isDropoutType.value) {
|
||||
rules.oldClassCode = [
|
||||
{ required: true, message: '请选择原班级', trigger: 'change' }
|
||||
]
|
||||
rules.newClassCode = [
|
||||
{ required: true, message: '请选择现班级', trigger: 'change' }
|
||||
]
|
||||
}
|
||||
|
||||
return rules
|
||||
})
|
||||
|
||||
// 原班级变化时,更新学生搜索条件
|
||||
const handleOldClassChange = () => {
|
||||
@@ -331,14 +355,30 @@ const handleOldClassChange = () => {
|
||||
form.selectedStudents = []
|
||||
}
|
||||
|
||||
// 异动类型变化时处理
|
||||
const handleTurnoverTypeChange = () => {
|
||||
// 如果切换为退学类型,清空原班级和现班级
|
||||
if (isDropoutType.value) {
|
||||
form.oldClassCode = ''
|
||||
form.newClassCode = ''
|
||||
// 清空已选学生(因为退学不需要原班级,学生选择逻辑会受影响)
|
||||
form.selectedStudents = []
|
||||
}
|
||||
// 重新验证表单
|
||||
nextTick(() => {
|
||||
dataFormRef.value?.clearValidate(['oldClassCode', 'newClassCode'])
|
||||
})
|
||||
}
|
||||
|
||||
// 打开学生选择弹窗
|
||||
const openStudentDialog = () => {
|
||||
if (!form.oldClassCode) {
|
||||
// 如果是退学类型,不需要原班级
|
||||
if (!isDropoutType.value && !form.oldClassCode) {
|
||||
useMessage().warning('请先选择原班级')
|
||||
return
|
||||
}
|
||||
studentDialogVisible.value = true
|
||||
studentSearchForm.classCode = form.oldClassCode
|
||||
studentSearchForm.classCode = form.oldClassCode || ''
|
||||
studentSearchForm.classNo = ''
|
||||
studentSearchForm.stuNo = ''
|
||||
studentSearchForm.realName = ''
|
||||
@@ -354,10 +394,13 @@ const handleStudentSearch = async () => {
|
||||
const params: any = {
|
||||
current: studentPagination.currentPage,
|
||||
size: studentPagination.pageSize,
|
||||
classCode: studentSearchForm.classCode || undefined,
|
||||
stuNo: studentSearchForm.stuNo || undefined,
|
||||
realName: studentSearchForm.realName || undefined
|
||||
}
|
||||
// 如果不是退学类型且有原班级,才添加 classCode 条件
|
||||
if (!isDropoutType.value && studentSearchForm.classCode) {
|
||||
params.classCode = studentSearchForm.classCode
|
||||
}
|
||||
const res = await getStudentList(params)
|
||||
if (res.data && res.data.records) {
|
||||
studentTableData.value = res.data.records
|
||||
@@ -494,8 +537,6 @@ const onSubmit = async () => {
|
||||
const submitData: any = {
|
||||
schoolYear: form.schoolYear,
|
||||
schoolTerm: form.schoolTerm,
|
||||
oldClassCode: form.oldClassCode,
|
||||
newClassCode: form.newClassCode,
|
||||
turnoverType: form.turnoverType,
|
||||
turnYear: form.turnYear || '',
|
||||
turnoverDate: form.turnoverDate,
|
||||
@@ -506,6 +547,12 @@ const onSubmit = async () => {
|
||||
}))
|
||||
}
|
||||
|
||||
// 如果不是退学类型,才添加原班级和现班级
|
||||
if (!isDropoutType.value) {
|
||||
submitData.oldClassCode = form.oldClassCode
|
||||
submitData.newClassCode = form.newClassCode
|
||||
}
|
||||
|
||||
// 编辑时需要包含id和单个学生信息
|
||||
if (operType.value === 'edit') {
|
||||
submitData.id = form.id
|
||||
|
||||
@@ -207,7 +207,7 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
<el-table-column label="操作" width="150" align="center" fixed="right">
|
||||
<el-table-column label="操作" width="220" align="center" fixed="right">
|
||||
<template #header>
|
||||
<el-icon><Setting /></el-icon>
|
||||
<span style="margin-left: 4px">操作</span>
|
||||
@@ -220,6 +220,13 @@
|
||||
@click="handleEdit(scope.row)">
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
icon="RefreshLeft"
|
||||
link
|
||||
type="warning"
|
||||
@click="handleCancel(scope.row)">
|
||||
异动撤销
|
||||
</el-button>
|
||||
<el-button
|
||||
icon="Delete"
|
||||
link
|
||||
@@ -250,7 +257,7 @@
|
||||
import { reactive, ref, onMounted, computed, nextTick } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { BasicTableProps, useTable } from "/@/hooks/table";
|
||||
import { fetchList, delObj, exportData } from "/@/api/stuwork/stuturnover";
|
||||
import { fetchList, delObj, exportData, cancelObj } from "/@/api/stuwork/stuturnover";
|
||||
import { queryAllSchoolYear } from "/@/api/basic/basicyear";
|
||||
import { getDicts } from "/@/api/admin/dict";
|
||||
import { getDeptListByLevelTwo } from "/@/api/basic/basicdept";
|
||||
@@ -258,7 +265,7 @@ import { list as getClassList } from "/@/api/basic/basicclass";
|
||||
import { useMessage, useMessageBox } from "/@/hooks/message";
|
||||
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
|
||||
import FormDialog from './form.vue'
|
||||
import { List, Calendar, Clock, OfficeBuilding, Grid, CreditCard, Avatar, Collection, Document, Setting, Menu, Search } from '@element-plus/icons-vue'
|
||||
import { List, Calendar, Clock, OfficeBuilding, Grid, CreditCard, Avatar, Collection, Document, Setting, Menu, Search, RefreshLeft } from '@element-plus/icons-vue'
|
||||
import { useTableColumnControl } from '/@/hooks/tableColumn'
|
||||
|
||||
|
||||
@@ -400,6 +407,20 @@ const handleEdit = (row: any) => {
|
||||
formDialogRef.value?.openDialog('edit', row)
|
||||
}
|
||||
|
||||
// 异动撤销
|
||||
const handleCancel = async (row: any) => {
|
||||
try {
|
||||
await useMessageBox().confirm('确定要撤销该条学籍异动吗?')
|
||||
await cancelObj([row.id])
|
||||
useMessage().success('撤销成功')
|
||||
getDataList()
|
||||
} catch (err: any) {
|
||||
if (err !== 'cancel') {
|
||||
useMessage().error(err?.msg || '撤销失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 删除
|
||||
const handleDelete = async (row: any) => {
|
||||
try {
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<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>
|
||||
@@ -15,7 +15,7 @@
|
||||
<el-form-item label="班级" prop="classCode">
|
||||
<el-select
|
||||
v-model="state.queryForm.classCode"
|
||||
placeholder="请选择学年"
|
||||
placeholder="请选择班级"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 200px">
|
||||
@@ -27,10 +27,10 @@
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="??" prop="classNo">
|
||||
<el-form-item label="班级编号" prop="classNo">
|
||||
<el-input
|
||||
v-model="state.queryForm.classNo"
|
||||
placeholder="请输入姓名"
|
||||
placeholder="请输入班级编号"
|
||||
clearable
|
||||
style="width: 200px" />
|
||||
</el-form-item>
|
||||
@@ -41,17 +41,17 @@
|
||||
clearable
|
||||
style="width: 200px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="????" prop="serNo">
|
||||
<el-form-item label="序列号" prop="serNo">
|
||||
<el-input
|
||||
v-model="state.queryForm.serNo"
|
||||
placeholder="请输入奖项名称"
|
||||
placeholder="请输入序列号"
|
||||
clearable
|
||||
style="width: 200px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="入学年份" prop="grade">
|
||||
<el-select
|
||||
v-model="state.queryForm.grade"
|
||||
placeholder="请输入奖项名称"
|
||||
placeholder="请选择入学年份"
|
||||
clearable
|
||||
style="width: 200px">
|
||||
<el-option
|
||||
@@ -63,40 +63,40 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" plain icon="Search" @click="getDataList">??</el-button>
|
||||
<el-button type="primary" plain icon="Search" @click="getDataList">查询</el-button>
|
||||
<el-button icon="Refresh" @click="handleReset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-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="formDialogRef.openDialog()">
|
||||
??
|
||||
新增
|
||||
</el-button>
|
||||
<el-button
|
||||
icon="Upload"
|
||||
type="success"
|
||||
class="ml10"
|
||||
@click="handleImport">
|
||||
??
|
||||
导入
|
||||
</el-button>
|
||||
<el-button
|
||||
icon="Download"
|
||||
type="warning"
|
||||
class="ml10"
|
||||
@click="handleExport">
|
||||
??
|
||||
导出
|
||||
</el-button>
|
||||
<right-toolbar
|
||||
v-model:showSearch="showSearch"
|
||||
@@ -153,11 +153,11 @@
|
||||
<el-icon><component :is="columnConfigMap[col.prop]?.icon || Calendar" /></el-icon>
|
||||
<span style="margin-left: 4px">{{ col.label }}</span>
|
||||
</template>
|
||||
<!-- ????????? -->
|
||||
<!-- 入团时间格式化 -->
|
||||
<template v-if="col.prop === 'enterTime'" #default="scope">
|
||||
<span>{{ parseTime(scope.row.enterTime, '{y}-{m}-{d}') }}</span>
|
||||
</template>
|
||||
<!-- ????????? -->
|
||||
<!-- 职务格式化 -->
|
||||
<template v-else-if="col.prop === 'position'" #default="scope">
|
||||
<el-tag v-if="scope.row.position" size="small" type="primary" effect="plain">
|
||||
{{ scope.row.position }}
|
||||
@@ -200,12 +200,12 @@
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<!-- ??/?????? -->
|
||||
<!-- 新增/编辑对话框 -->
|
||||
<form-dialog ref="formDialogRef" @refresh="getDataList" />
|
||||
|
||||
<!-- ???? -->
|
||||
<!-- 导入对话框 -->
|
||||
<el-dialog
|
||||
title="????"
|
||||
title="导入数据"
|
||||
v-model="importDialogVisible"
|
||||
:close-on-click-modal="false"
|
||||
width="500px">
|
||||
@@ -220,18 +220,18 @@
|
||||
drag>
|
||||
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
|
||||
<div class="el-upload__text">
|
||||
????????<em>????</em>
|
||||
将文件拖到此处,或<em>点击上传</em>
|
||||
</div>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip">
|
||||
???? xlsx/xls ??
|
||||
只能上传 xlsx/xls 文件
|
||||
</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="importDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="handleImportSubmit" :loading="importLoading">??</el-button>
|
||||
<el-button type="primary" @click="handleImportSubmit" :loading="importLoading">确认</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
@@ -254,7 +254,7 @@ import { useTableColumnControl } from '/@/hooks/tableColumn'
|
||||
import type { UploadFile, UploadFiles } from 'element-plus';
|
||||
import FormDialog from './form.vue'
|
||||
|
||||
// ??????
|
||||
// 定义变量
|
||||
const route = useRoute()
|
||||
const formDialogRef = ref()
|
||||
const columnControlRef = ref<any>()
|
||||
@@ -267,19 +267,19 @@ const importDialogVisible = ref(false)
|
||||
const importLoading = ref(false)
|
||||
const fileList = ref<UploadFile[]>([])
|
||||
|
||||
// ?????
|
||||
// 表格列配置
|
||||
const tableColumns = [
|
||||
{ prop: 'deptName', label: '????', minWidth: 150 },
|
||||
{ prop: 'classNo', label: '??', width: 120 },
|
||||
{ prop: 'stuNo', label: '??', width: 120 },
|
||||
{ prop: 'realName', label: '??', width: 100 },
|
||||
{ prop: 'phone', label: '???', width: 120 },
|
||||
{ prop: 'enterTime', label: '????', width: 120 },
|
||||
{ prop: 'serNo', label: '????', width: 120 },
|
||||
{ prop: 'position', label: '????', width: 120 }
|
||||
{ prop: 'deptName', label: '学院名称', minWidth: 150 },
|
||||
{ prop: 'classNo', label: '班级', width: 120 },
|
||||
{ prop: 'stuNo', label: '学号', width: 120 },
|
||||
{ prop: 'realName', label: '姓名', width: 100 },
|
||||
{ prop: 'phone', label: '手机号', width: 120 },
|
||||
{ prop: 'enterTime', label: '入团时间', width: 120 },
|
||||
{ prop: 'serNo', label: '序列号', width: 120 },
|
||||
{ prop: 'position', label: '职务名称', width: 120 }
|
||||
]
|
||||
|
||||
// ?????????????
|
||||
// 列配置映射
|
||||
const columnConfigMap: Record<string, { icon: any }> = {
|
||||
deptName: { icon: OfficeBuilding },
|
||||
classNo: { icon: Grid },
|
||||
@@ -291,22 +291,22 @@ const columnConfigMap: Record<string, { icon: any }> = {
|
||||
position: { icon: Briefcase }
|
||||
}
|
||||
|
||||
// ???????hook
|
||||
// 表格列控制hook
|
||||
const {
|
||||
visibleColumns,
|
||||
visibleColumnsSorted,
|
||||
checkColumnVisible,
|
||||
handleColumnChange,
|
||||
handleColumnOrderChange
|
||||
} = useTableColumnControl(tableColumns, route.path)
|
||||
} = useTableColumnControl(tableColumns)
|
||||
|
||||
// ????
|
||||
// 表格样式
|
||||
const tableStyle = {
|
||||
cellStyle: { padding: '8px 0' },
|
||||
headerCellStyle: { background: '#f5f7fa', color: '#606266', fontWeight: 'bold' }
|
||||
}
|
||||
|
||||
// ?? useTable
|
||||
// 使用 useTable
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
queryForm: {
|
||||
classCode: '',
|
||||
@@ -331,7 +331,7 @@ const {
|
||||
tableStyle: _tableStyle
|
||||
} = useTable(state)
|
||||
|
||||
// ??
|
||||
// 重置
|
||||
const handleReset = () => {
|
||||
searchFormRef.value?.resetFields()
|
||||
state.queryForm.classCode = ''
|
||||
@@ -342,70 +342,70 @@ const handleReset = () => {
|
||||
getDataList()
|
||||
}
|
||||
|
||||
// ??
|
||||
// 编辑
|
||||
const handleEdit = (row: any) => {
|
||||
formDialogRef.value?.openDialog('edit', row)
|
||||
}
|
||||
|
||||
// ??
|
||||
// 删除
|
||||
const handleDelete = async (row: any) => {
|
||||
const { confirm } = useMessageBox()
|
||||
try {
|
||||
await confirm('???????????')
|
||||
await confirm('确定要删除该记录吗?')
|
||||
await delObj([row.id])
|
||||
useMessage().success('????')
|
||||
useMessage().success('删除成功')
|
||||
getDataList()
|
||||
} catch (err: any) {
|
||||
if (err !== 'cancel') {
|
||||
useMessage().error(err.msg || '????')
|
||||
useMessage().error(err.msg || '删除失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ??
|
||||
// 导入
|
||||
const handleImport = () => {
|
||||
importDialogVisible.value = true
|
||||
fileList.value = []
|
||||
}
|
||||
|
||||
// ????
|
||||
// 文件变化
|
||||
const handleFileChange = (file: UploadFile, files: UploadFiles) => {
|
||||
fileList.value = [file]
|
||||
}
|
||||
|
||||
// ??????
|
||||
// 文件超出限制
|
||||
const handleExceed = () => {
|
||||
useMessage().warning('????????')
|
||||
useMessage().warning('文件数量超出限制')
|
||||
}
|
||||
|
||||
// ????
|
||||
// 提交导入
|
||||
const handleImportSubmit = async () => {
|
||||
if (fileList.value.length === 0) {
|
||||
useMessage().warning('?????????')
|
||||
useMessage().warning('请先选择要上传的文件')
|
||||
return
|
||||
}
|
||||
|
||||
const file = fileList.value[0].raw
|
||||
if (!file) {
|
||||
useMessage().warning('?????')
|
||||
useMessage().warning('文件无效')
|
||||
return
|
||||
}
|
||||
|
||||
importLoading.value = true
|
||||
try {
|
||||
await importExcel(file as File)
|
||||
useMessage().success('????')
|
||||
useMessage().success('导入成功')
|
||||
importDialogVisible.value = false
|
||||
fileList.value = []
|
||||
getDataList()
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg || '????')
|
||||
useMessage().error(err.msg || '导入失败')
|
||||
} finally {
|
||||
importLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// ??
|
||||
// 导出
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
const res = await exportExcel(state.queryForm)
|
||||
@@ -413,18 +413,18 @@ const handleExport = async () => {
|
||||
const url = window.URL.createObjectURL(blob)
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.download = `??_${parseTime(new Date(), '{y}{m}{d}{h}{i}{s}')}.xlsx`
|
||||
link.download = `学生团组织_${parseTime(new Date(), '{y}{m}{d}{h}{i}{s}')}.xlsx`
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
window.URL.revokeObjectURL(url)
|
||||
useMessage().success('????')
|
||||
useMessage().success('导出成功')
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg || '????')
|
||||
useMessage().error(err.msg || '导出失败')
|
||||
}
|
||||
}
|
||||
|
||||
// ??????
|
||||
// 获取班级列表
|
||||
const getClassListData = async () => {
|
||||
try {
|
||||
const res = await getClassListByRole()
|
||||
@@ -438,7 +438,7 @@ const getClassListData = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// ????????
|
||||
// 获取入学年份列表
|
||||
const getGradeListData = async () => {
|
||||
try {
|
||||
const res = await getGradeList()
|
||||
|
||||
@@ -58,6 +58,7 @@ import { ref, reactive, nextTick, onMounted } from 'vue'
|
||||
import { useMessage } from '/@/hooks/message'
|
||||
import { addObj } from '/@/api/stuwork/stuworkstudyalternate'
|
||||
import { getClassListByRole } from '/@/api/basic/basicclass'
|
||||
import { queryAllStudentByClassCode } from '/@/api/basic/basicstudent'
|
||||
|
||||
const emit = defineEmits(['refresh'])
|
||||
|
||||
@@ -94,7 +95,7 @@ const openDialog = async () => {
|
||||
})
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
// 提交表单(接口文档要求 classCode、dateRange、stuNoList)
|
||||
const onSubmit = async () => {
|
||||
if (!dataFormRef.value) return
|
||||
|
||||
@@ -108,15 +109,27 @@ const onSubmit = async () => {
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
// 按班级获取学生列表,提取学号作为 stuNoList(接口必传)
|
||||
const stuRes = await queryAllStudentByClassCode(form.classCode)
|
||||
const list = stuRes?.data && Array.isArray(stuRes.data) ? stuRes.data : []
|
||||
const stuNoList = list.map((s: any) => s.stuNo).filter(Boolean)
|
||||
if (stuNoList.length === 0) {
|
||||
useMessage().warning('该班级暂无学生,无法新增工学交替')
|
||||
loading.value = false
|
||||
return
|
||||
}
|
||||
await addObj({
|
||||
classCode: form.classCode,
|
||||
dateRange: form.dateRange
|
||||
dateRange: form.dateRange,
|
||||
stuNoList
|
||||
})
|
||||
useMessage().success('新增成功')
|
||||
visible.value = false
|
||||
emit('refresh')
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg || '新增失败')
|
||||
if (!err?._messageShown) {
|
||||
useMessage().error(err?.msg || '新增失败')
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
@@ -170,7 +170,7 @@
|
||||
:min-width="col.minWidth"
|
||||
:width="col.width">
|
||||
<template #header>
|
||||
<el-icon><component :is="columnConfigMap[col.prop]?.icon || Calendar" /></el-icon>
|
||||
<el-icon><component :is="columnConfigMap[col.prop || '']?.icon || Calendar" /></el-icon>
|
||||
<span style="margin-left: 4px">{{ col.label }}</span>
|
||||
</template>
|
||||
<!-- 学期列特殊模板 -->
|
||||
@@ -290,7 +290,7 @@ const {
|
||||
checkColumnVisible,
|
||||
handleColumnChange,
|
||||
handleColumnOrderChange
|
||||
} = useTableColumnControl(tableColumns, route.path)
|
||||
} = useTableColumnControl(tableColumns, { storageKey: route.path })
|
||||
|
||||
// 搜索表单
|
||||
const searchForm = reactive({
|
||||
@@ -303,10 +303,20 @@ const searchForm = reactive({
|
||||
dateRange: null as [string, string] | null
|
||||
})
|
||||
|
||||
// 配置 useTable
|
||||
// 配置 useTable(接口返回 data.tableData.records / data.tableData.total,需包装以适配 hook 的 res.data[props.item] 取数)
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
queryForm: searchForm,
|
||||
pageList: fetchList,
|
||||
pageList: async (params: any) => {
|
||||
const res = await fetchList(params)
|
||||
const data = res?.data
|
||||
const tableData = data?.tableData
|
||||
return {
|
||||
data: {
|
||||
'tableData.records': tableData?.records ?? [],
|
||||
'tableData.total': tableData?.total ?? data?.total ?? 0
|
||||
}
|
||||
}
|
||||
},
|
||||
props: {
|
||||
item: 'tableData.records',
|
||||
totalCount: 'tableData.total'
|
||||
@@ -397,13 +407,25 @@ const getSchoolTermList = async () => {
|
||||
try {
|
||||
const res = await getDicts('school_term')
|
||||
if (res.data && Array.isArray(res.data)) {
|
||||
schoolTermList.value = res.data
|
||||
schoolTermList.value = res.data.map((item: any) => ({
|
||||
label: item.label ?? item.dictLabel ?? item.name,
|
||||
value: item.value ?? item.dictValue ?? item.code
|
||||
}))
|
||||
} else {
|
||||
schoolTermList.value = []
|
||||
}
|
||||
} catch (err) {
|
||||
schoolTermList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
// 学期列展示:按字典转成文案
|
||||
const formatSchoolTerm = (value: string | number) => {
|
||||
if (value === '' || value == null) return '-'
|
||||
const item = schoolTermList.value.find((t: any) => String(t.value) === String(value))
|
||||
return item ? item.label : value
|
||||
}
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
getDeptListData()
|
||||
|
||||
253
src/views/stuwork/termactivity/form.vue
Normal file
253
src/views/stuwork/termactivity/form.vue
Normal file
@@ -0,0 +1,253 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
:title="form.id ? '编辑' : '新增'"
|
||||
v-model="visible"
|
||||
:width="800"
|
||||
:close-on-click-modal="false"
|
||||
draggable>
|
||||
<el-form
|
||||
ref="dataFormRef"
|
||||
:model="form"
|
||||
:rules="dataRules"
|
||||
label-width="100px"
|
||||
v-loading="loading">
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="学年" prop="schoolYear">
|
||||
<el-select
|
||||
v-model="form.schoolYear"
|
||||
placeholder="请选择学年"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in schoolYearList"
|
||||
:key="item.year"
|
||||
:label="item.year"
|
||||
:value="item.year" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="学期" prop="schoolTerm">
|
||||
<el-select
|
||||
v-model="form.schoolTerm"
|
||||
placeholder="请选择学期"
|
||||
clearable
|
||||
style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in schoolTermList"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item label="标题" prop="title">
|
||||
<el-input
|
||||
v-model="form.title"
|
||||
placeholder="请输入标题"
|
||||
style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="作者" prop="author">
|
||||
<el-input
|
||||
v-model="form.author"
|
||||
placeholder="请输入作者"
|
||||
style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item label="内容" prop="content">
|
||||
<Editor
|
||||
v-model:getHtml="form.content"
|
||||
:height="'400'"
|
||||
placeholder="请输入内容" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="visible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="onSubmit" :disabled="loading">确 认</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="TermActivityFormDialog">
|
||||
import { ref, reactive, nextTick, onMounted } from 'vue'
|
||||
import { useMessage } from '/@/hooks/message'
|
||||
import { addObj, editObj, getDetail } from '/@/api/stuwork/termactivity'
|
||||
import { queryAllSchoolYear } from '/@/api/basic/basicyear'
|
||||
import { getDicts } from '/@/api/admin/dict'
|
||||
import Editor from '/@/components/Editor/index.vue'
|
||||
|
||||
const emit = defineEmits(['refresh'])
|
||||
|
||||
// 定义变量内容
|
||||
const dataFormRef = ref()
|
||||
const visible = ref(false)
|
||||
const loading = ref(false)
|
||||
const operType = ref('add') // add 或 edit
|
||||
const schoolYearList = ref<any[]>([])
|
||||
const schoolTermList = ref<any[]>([])
|
||||
|
||||
// 提交表单数据
|
||||
const form = reactive({
|
||||
id: '',
|
||||
schoolYear: '',
|
||||
schoolTerm: '',
|
||||
title: '',
|
||||
content: '',
|
||||
author: ''
|
||||
})
|
||||
|
||||
// 定义校验规则
|
||||
const dataRules = {
|
||||
schoolYear: [
|
||||
{ required: true, message: '请选择学年', trigger: 'change' }
|
||||
],
|
||||
schoolTerm: [
|
||||
{ required: true, message: '请选择学期', trigger: 'change' }
|
||||
],
|
||||
title: [
|
||||
{ required: true, message: '请输入标题', trigger: 'blur' }
|
||||
],
|
||||
content: [
|
||||
{ required: true, message: '请输入内容', trigger: 'blur' }
|
||||
],
|
||||
author: [
|
||||
{ required: true, message: '请输入作者', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
|
||||
// 打开弹窗
|
||||
const openDialog = async (type: string = 'add', row?: any) => {
|
||||
visible.value = true
|
||||
operType.value = type
|
||||
|
||||
// 重置表单数据
|
||||
nextTick(() => {
|
||||
dataFormRef.value?.resetFields()
|
||||
form.id = ''
|
||||
form.schoolYear = ''
|
||||
form.schoolTerm = ''
|
||||
form.title = ''
|
||||
form.content = ''
|
||||
form.author = ''
|
||||
|
||||
// 编辑时填充数据
|
||||
if (type === 'edit' && row) {
|
||||
form.id = row.id
|
||||
form.schoolYear = row.schoolYear || ''
|
||||
form.schoolTerm = row.schoolTerm || ''
|
||||
form.title = row.title || ''
|
||||
form.content = row.content || ''
|
||||
form.author = row.author || ''
|
||||
|
||||
// 如果需要获取详情
|
||||
if (row.id && !row.content) {
|
||||
loading.value = true
|
||||
getDetail(row.id).then((res: any) => {
|
||||
if (res.data) {
|
||||
form.schoolYear = res.data.schoolYear || form.schoolYear
|
||||
form.schoolTerm = res.data.schoolTerm || form.schoolTerm
|
||||
form.title = res.data.title || ''
|
||||
form.content = res.data.content || ''
|
||||
form.author = res.data.author || ''
|
||||
}
|
||||
}).finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 学年列表(班级管理-学年接口)
|
||||
const getSchoolYearList = async () => {
|
||||
try {
|
||||
const res = await queryAllSchoolYear()
|
||||
schoolYearList.value = res?.data && Array.isArray(res.data) ? res.data : []
|
||||
} catch (err) {
|
||||
schoolYearList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
// 学期字典(系统通用)
|
||||
const getSchoolTermDict = async () => {
|
||||
try {
|
||||
const res = await getDicts('school_term')
|
||||
if (res?.data && Array.isArray(res.data)) {
|
||||
schoolTermList.value = res.data.map((item: any) => ({
|
||||
label: item.label ?? item.dictLabel ?? item.name,
|
||||
value: item.value ?? item.dictValue ?? item.code
|
||||
}))
|
||||
} else {
|
||||
schoolTermList.value = []
|
||||
}
|
||||
} catch (err) {
|
||||
schoolTermList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
const onSubmit = async () => {
|
||||
if (!dataFormRef.value) return
|
||||
|
||||
await dataFormRef.value.validate(async (valid: boolean) => {
|
||||
if (!valid) return
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
const submitData = {
|
||||
schoolYear: form.schoolYear,
|
||||
schoolTerm: form.schoolTerm,
|
||||
title: form.title,
|
||||
content: form.content,
|
||||
author: form.author
|
||||
}
|
||||
|
||||
if (operType.value === 'add') {
|
||||
await addObj(submitData)
|
||||
useMessage().success('新增成功')
|
||||
} else {
|
||||
await editObj({
|
||||
id: form.id,
|
||||
...submitData
|
||||
})
|
||||
useMessage().success('编辑成功')
|
||||
}
|
||||
visible.value = false
|
||||
emit('refresh')
|
||||
} catch (err: any) {
|
||||
if (!err?._messageShown) {
|
||||
useMessage().error(err?.msg || (operType.value === 'add' ? '新增失败' : '编辑失败'))
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 初始化:加载学年、学期
|
||||
onMounted(() => {
|
||||
getSchoolYearList()
|
||||
getSchoolTermDict()
|
||||
})
|
||||
|
||||
// 暴露方法
|
||||
defineExpose({
|
||||
openDialog
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.mb20 {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
</style>
|
||||
366
src/views/stuwork/termactivity/index.vue
Normal file
366
src/views/stuwork/termactivity/index.vue
Normal file
@@ -0,0 +1,366 @@
|
||||
<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="state.queryForm"
|
||||
ref="searchFormRef"
|
||||
:inline="true"
|
||||
@keyup.enter="getDataList"
|
||||
class="search-form">
|
||||
<el-form-item label="学年" prop="schoolYear">
|
||||
<el-select
|
||||
v-model="state.queryForm.schoolYear"
|
||||
placeholder="请选择学年"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 200px">
|
||||
<el-option
|
||||
v-for="item in schoolYearList"
|
||||
:key="item.year"
|
||||
:label="item.year"
|
||||
:value="item.year">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="学期" prop="schoolTerm">
|
||||
<el-select
|
||||
v-model="state.queryForm.schoolTerm"
|
||||
placeholder="请选择学期"
|
||||
clearable
|
||||
style="width: 200px">
|
||||
<el-option
|
||||
v-for="item in schoolTermList"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="标题" prop="title">
|
||||
<el-input
|
||||
v-model="state.queryForm.title"
|
||||
placeholder="请输入标题"
|
||||
clearable
|
||||
style="width: 200px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="作者" prop="author">
|
||||
<el-input
|
||||
v-model="state.queryForm.author"
|
||||
placeholder="请输入作者"
|
||||
clearable
|
||||
style="width: 200px" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" plain icon="Search" @click="getDataList">查询</el-button>
|
||||
<el-button icon="Refresh" @click="handleReset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-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="FolderAdd"
|
||||
type="primary"
|
||||
@click="formDialogRef.openDialog()">
|
||||
新增
|
||||
</el-button>
|
||||
<right-toolbar
|
||||
v-model:showSearch="showSearch"
|
||||
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-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 #header>
|
||||
<el-icon><List /></el-icon>
|
||||
</template>
|
||||
<template #default="{ $index }">
|
||||
{{ $index + 1 + ((state.pagination?.current || 1) - 1) * (state.pagination?.size || 10) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<template v-for="col in visibleColumnsSorted" :key="col.prop || col.label">
|
||||
<el-table-column
|
||||
v-if="checkColumnVisible(col.prop || '') && col.prop !== '操作'"
|
||||
:prop="col.prop"
|
||||
:label="col.label"
|
||||
:width="col.width"
|
||||
:min-width="col.minWidth"
|
||||
:show-overflow-tooltip="col.showOverflowTooltip !== false"
|
||||
:align="col.align || 'center'">
|
||||
<template #header>
|
||||
<el-icon v-if="col.icon"><component :is="col.icon" /></el-icon>
|
||||
<span :style="{ marginLeft: col.icon ? '4px' : '0' }">{{ col.label }}</span>
|
||||
</template>
|
||||
<!-- 学期列特殊模板 -->
|
||||
<template v-if="col.prop === 'schoolTerm'" #default="scope">
|
||||
<el-tag size="small" type="primary" effect="plain">
|
||||
{{ formatSchoolTerm(scope.row.schoolTerm) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
<!-- 内容列特殊模板(截断显示) -->
|
||||
<template v-else-if="col.prop === 'content'" #default="scope">
|
||||
<el-tooltip
|
||||
v-if="scope.row.content"
|
||||
:content="scope.row.content.replace(/<[^>]*>/g, '')"
|
||||
placement="top"
|
||||
effect="dark">
|
||||
<div class="content-preview" v-html="getContentPreview(scope.row.content)"></div>
|
||||
</el-tooltip>
|
||||
<span v-else class="text-gray-400">-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
<el-table-column label="操作" width="180" align="center" fixed="right">
|
||||
<template #header>
|
||||
<el-icon><Setting /></el-icon>
|
||||
<span style="margin-left: 4px">操作</span>
|
||||
</template>
|
||||
<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>
|
||||
|
||||
<!-- 新增/编辑弹窗 -->
|
||||
<form-dialog ref="formDialogRef" @refresh="getDataList" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="TermActivity">
|
||||
import { reactive, ref, computed, onMounted } from 'vue'
|
||||
import { BasicTableProps, useTable } from "/@/hooks/table";
|
||||
import { fetchList, delObj } from "/@/api/stuwork/termactivity";
|
||||
import { useMessage, useMessageBox } from "/@/hooks/message";
|
||||
import { queryAllSchoolYear } from '/@/api/basic/basicyear'
|
||||
import { getDicts } from '/@/api/admin/dict'
|
||||
import FormDialog from './form.vue'
|
||||
import { Search, Document, List, Setting, Menu, Calendar, EditPen, User, Clock } from '@element-plus/icons-vue'
|
||||
import RightToolbar from '/@/components/RightToolbar/index.vue'
|
||||
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
|
||||
import { useTableColumnControl } from '/@/hooks/tableColumn'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
const route = useRoute()
|
||||
const formDialogRef = ref()
|
||||
const showSearch = ref(true)
|
||||
const searchFormRef = ref()
|
||||
const schoolYearList = ref<any[]>([])
|
||||
const schoolTermList = ref<any[]>([])
|
||||
|
||||
// 表格列配置
|
||||
const tableColumns = [
|
||||
{ prop: 'schoolYear', label: '学年', width: 120, icon: Calendar },
|
||||
{ prop: 'schoolTerm', label: '学期', width: 100, icon: Calendar },
|
||||
{ prop: 'title', label: '标题', minWidth: 200, icon: Document },
|
||||
{ prop: 'content', label: '内容', minWidth: 300, icon: EditPen },
|
||||
{ prop: 'author', label: '作者', width: 120, icon: User },
|
||||
{ prop: 'createTime', label: '创建时间', width: 180, icon: Clock }
|
||||
]
|
||||
|
||||
// 使用表格列控制
|
||||
const {
|
||||
visibleColumns,
|
||||
visibleColumnsSorted,
|
||||
checkColumnVisible,
|
||||
handleColumnChange,
|
||||
handleColumnOrderChange,
|
||||
loadSavedConfig
|
||||
} = useTableColumnControl(tableColumns, { storageKey: route.path })
|
||||
|
||||
// 立即加载配置,确保初始化时列可见
|
||||
loadSavedConfig()
|
||||
|
||||
// 配置 useTable
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
queryForm: {
|
||||
schoolYear: '',
|
||||
schoolTerm: '',
|
||||
title: '',
|
||||
author: ''
|
||||
},
|
||||
pageList: async (params: any) => {
|
||||
const res = await fetchList(params)
|
||||
const data = res?.data
|
||||
return {
|
||||
data: {
|
||||
records: data?.records ?? [],
|
||||
total: data?.total ?? 0
|
||||
}
|
||||
}
|
||||
},
|
||||
props: {
|
||||
item: 'records',
|
||||
totalCount: 'total'
|
||||
},
|
||||
createdIsNeed: true,
|
||||
isPage: true
|
||||
})
|
||||
|
||||
// table hook
|
||||
const {
|
||||
getDataList,
|
||||
currentChangeHandle,
|
||||
sizeChangeHandle,
|
||||
tableStyle
|
||||
} = useTable(state)
|
||||
|
||||
// 格式化学期
|
||||
const formatSchoolTerm = (value: string | number) => {
|
||||
if (value === null || value === undefined || value === '') {
|
||||
return '-'
|
||||
}
|
||||
const dictItem = schoolTermList.value.find(item => item.value == value)
|
||||
return dictItem ? dictItem.label : value
|
||||
}
|
||||
|
||||
// 获取内容预览(去除HTML标签,截断)
|
||||
const getContentPreview = (content: string) => {
|
||||
if (!content) return '-'
|
||||
const text = content.replace(/<[^>]*>/g, '')
|
||||
return text.length > 50 ? text.substring(0, 50) + '...' : text
|
||||
}
|
||||
|
||||
// 重置
|
||||
const handleReset = () => {
|
||||
searchFormRef.value?.resetFields()
|
||||
state.queryForm.schoolYear = ''
|
||||
state.queryForm.schoolTerm = ''
|
||||
state.queryForm.title = ''
|
||||
state.queryForm.author = ''
|
||||
getDataList()
|
||||
}
|
||||
|
||||
// 编辑
|
||||
const handleEdit = (row: any) => {
|
||||
formDialogRef.value?.openDialog('edit', row)
|
||||
}
|
||||
|
||||
// 删除
|
||||
const handleDelete = async (row: any) => {
|
||||
const { confirm } = useMessageBox()
|
||||
try {
|
||||
await confirm(`确定要删除"${row.title || '该项'}"吗?`)
|
||||
await delObj([row.id])
|
||||
useMessage().success('删除成功')
|
||||
getDataList()
|
||||
} catch (err: any) {
|
||||
if (err !== 'cancel') {
|
||||
useMessage().error(err?.msg || '删除失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 获取学年列表
|
||||
const getSchoolYearList = async () => {
|
||||
try {
|
||||
const res = await queryAllSchoolYear()
|
||||
schoolYearList.value = res?.data && Array.isArray(res.data) ? res.data : []
|
||||
} catch (err) {
|
||||
schoolYearList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
// 获取学期字典
|
||||
const getSchoolTermDict = async () => {
|
||||
try {
|
||||
const res = await getDicts('school_term')
|
||||
if (res?.data && Array.isArray(res.data)) {
|
||||
schoolTermList.value = res.data.map((item: any) => ({
|
||||
label: item.label ?? item.dictLabel ?? item.name,
|
||||
value: item.value ?? item.dictValue ?? item.code
|
||||
}))
|
||||
} else {
|
||||
schoolTermList.value = []
|
||||
}
|
||||
} catch (err) {
|
||||
schoolTermList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
getSchoolYearList()
|
||||
getSchoolTermDict()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '/@/assets/styles/modern-page.scss';
|
||||
|
||||
.content-preview {
|
||||
max-width: 300px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
@@ -1,21 +1,21 @@
|
||||
<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="state.queryForm" ref="searchFormRef" :inline="true" @keyup.enter="getDataList" class="search-form">
|
||||
<el-form-item label="??" prop="termId">
|
||||
<el-form-item label="学期" prop="termId">
|
||||
<el-select
|
||||
v-model="state.queryForm.termId"
|
||||
placeholder="?????"
|
||||
placeholder="请选择学期"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 200px">
|
||||
@@ -27,10 +27,10 @@
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="??" prop="deptCode">
|
||||
<el-form-item label="学院" prop="deptCode">
|
||||
<el-select
|
||||
v-model="state.queryForm.deptCode"
|
||||
placeholder="?????"
|
||||
placeholder="请选择学院"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 200px">
|
||||
@@ -42,10 +42,10 @@
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="????" prop="grade">
|
||||
<el-form-item label="入学年份" prop="grade">
|
||||
<el-select
|
||||
v-model="state.queryForm.grade"
|
||||
placeholder="???????"
|
||||
placeholder="请选择入学年份"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 200px">
|
||||
@@ -57,19 +57,19 @@
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="??" prop="gradeCurr">
|
||||
<el-form-item label="年级" prop="gradeCurr">
|
||||
<el-input-number
|
||||
v-model="state.queryForm.gradeCurr"
|
||||
placeholder="?????"
|
||||
placeholder="请输入年级"
|
||||
:min="1"
|
||||
:max="10"
|
||||
clearable
|
||||
style="width: 200px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="??" prop="classCode">
|
||||
<el-form-item label="班级" prop="classCode">
|
||||
<el-select
|
||||
v-model="state.queryForm.classCode"
|
||||
placeholder="?????"
|
||||
placeholder="请选择班级"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 200px">
|
||||
@@ -81,10 +81,10 @@
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="????" prop="checkStatus">
|
||||
<el-form-item label="审核状态" prop="checkStatus">
|
||||
<el-select
|
||||
v-model="state.queryForm.checkStatus"
|
||||
placeholder="???????"
|
||||
placeholder="请选择审核状态"
|
||||
clearable
|
||||
style="width: 200px">
|
||||
<el-option
|
||||
@@ -96,26 +96,26 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" plain icon="Search" @click="getDataList">??</el-button>
|
||||
<el-button icon="Refresh" @click="handleReset">??</el-button>
|
||||
<el-button type="primary" plain icon="Search" @click="getDataList">查询</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>
|
||||
<el-button
|
||||
icon="Download"
|
||||
@@ -123,7 +123,7 @@
|
||||
class="ml10"
|
||||
:loading="exportLoading"
|
||||
@click="handleExport">
|
||||
??
|
||||
导出
|
||||
</el-button>
|
||||
<right-toolbar
|
||||
v-model:showSearch="showSearch"
|
||||
@@ -139,7 +139,7 @@
|
||||
@order-change="handleColumnOrderChange"
|
||||
>
|
||||
<template #trigger>
|
||||
<el-tooltip class="item" effect="dark" content="???" placement="top">
|
||||
<el-tooltip class="item" effect="dark" content="列设置" placement="top">
|
||||
<el-button circle style="margin-left: 0;">
|
||||
<el-icon><Menu /></el-icon>
|
||||
</el-button>
|
||||
@@ -151,7 +151,7 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- ?? -->
|
||||
<!-- 表格 -->
|
||||
<el-table
|
||||
:data="state.dataList"
|
||||
v-loading="state.loading"
|
||||
@@ -159,7 +159,7 @@
|
||||
:cell-style="tableStyle.cellStyle"
|
||||
:header-cell-style="tableStyle.headerCellStyle"
|
||||
class="modern-table">
|
||||
<el-table-column type="index" label="??" width="70" align="center">
|
||||
<el-table-column type="index" label="序号" width="70" align="center">
|
||||
<template #header>
|
||||
<el-icon><List /></el-icon>
|
||||
</template>
|
||||
@@ -169,7 +169,7 @@
|
||||
</el-table-column>
|
||||
<template v-for="col in visibleColumnsSorted" :key="col.prop || col.label">
|
||||
<el-table-column
|
||||
v-if="checkColumnVisible(col.prop || '') && col.prop !== '??'"
|
||||
v-if="checkColumnVisible(col.prop || '') && col.prop !== '序号'"
|
||||
:prop="col.prop"
|
||||
:label="col.label"
|
||||
show-overflow-tooltip
|
||||
@@ -180,39 +180,39 @@
|
||||
<el-icon><component :is="columnConfigMap[col.prop]?.icon || Calendar" /></el-icon>
|
||||
<span style="margin-left: 4px">{{ col.label }}</span>
|
||||
</template>
|
||||
<!-- ????????-->
|
||||
<!-- 年级格式化-->
|
||||
<template v-if="col.prop === 'gradeCurr'" #default="scope">
|
||||
<el-tag v-if="scope.row.gradeCurr !== undefined && scope.row.gradeCurr !== null" size="small" type="primary" effect="plain">
|
||||
{{ scope.row.gradeCurr }}
|
||||
</el-tag>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
<!-- ????????-->
|
||||
<!-- 性别格式化-->
|
||||
<template v-else-if="col.prop === 'gender'" #default="scope">
|
||||
<GenderTag :sex="scope.row.gender" />
|
||||
</template>
|
||||
<!-- ????????-->
|
||||
<!-- 学历格式化-->
|
||||
<template v-else-if="col.prop === 'education'" #default="scope">
|
||||
<el-tag v-if="scope.row.education" size="small" type="info" effect="plain">
|
||||
{{ formatEducation(scope.row.education) }}
|
||||
</el-tag>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
<!-- ????????-->
|
||||
<!-- 专业层次格式化-->
|
||||
<template v-else-if="col.prop === 'majorLevel'" #default="scope">
|
||||
<el-tag v-if="scope.row.majorLevel" size="small" type="warning" effect="plain">
|
||||
{{ formatMajorLevel(scope.row.majorLevel) }}
|
||||
</el-tag>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
<!-- ????????-->
|
||||
<!-- 金额格式化-->
|
||||
<template v-else-if="col.prop === 'money'" #default="scope">
|
||||
<el-tag v-if="scope.row.money !== undefined && scope.row.money !== null" size="small" type="success" effect="plain">
|
||||
?{{ scope.row.money }}
|
||||
¥{{ scope.row.money }}
|
||||
</el-tag>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
<!-- ????????? -->
|
||||
<!-- 审核状态格式化 -->
|
||||
<template v-else-if="col.prop === 'checkStatus'" #default="scope">
|
||||
<StatusTag
|
||||
:value="scope.row.checkStatus"
|
||||
@@ -222,10 +222,10 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
<el-table-column label="??" width="150" align="center" fixed="right">
|
||||
<el-table-column label="操作" width="150" align="center" fixed="right">
|
||||
<template #header>
|
||||
<el-icon><Setting /></el-icon>
|
||||
<span style="margin-left: 4px">??</span>
|
||||
<span style="margin-left: 4px">操作</span>
|
||||
</template>
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
@@ -233,20 +233,20 @@
|
||||
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"
|
||||
@@ -256,7 +256,7 @@
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<!-- ??/?????? -->
|
||||
<!-- 新增/编辑对话框 -->
|
||||
<!-- <form-dialog ref="formDialogRef" @refresh="getDataList" /> -->
|
||||
</div>
|
||||
</template>
|
||||
@@ -282,11 +282,11 @@ import {
|
||||
import { useTableColumnControl } from '/@/hooks/tableColumn'
|
||||
|
||||
|
||||
// ????
|
||||
// 异步组件
|
||||
const GenderTag = defineAsyncComponent(() => import('/@/components/GenderTag/index.vue'))
|
||||
const StatusTag = defineAsyncComponent(() => import('/@/components/StatusTag/index.vue'))
|
||||
|
||||
// ??????
|
||||
// 定义变量
|
||||
const route = useRoute()
|
||||
const searchFormRef = ref()
|
||||
const columnControlRef = ref<any>()
|
||||
@@ -301,29 +301,29 @@ const genderList = ref<any[]>([])
|
||||
const educationList = ref<any[]>([])
|
||||
const majorLevelList = ref<any[]>([])
|
||||
const checkStatusList = ref<any[]>([
|
||||
{ label: '???', value: '0' },
|
||||
{ label: '????', value: '1' }
|
||||
{ label: '未审核', value: '0' },
|
||||
{ label: '已审核', value: '1' }
|
||||
])
|
||||
|
||||
// ??????
|
||||
// 表格列配置
|
||||
const tableColumns = [
|
||||
{ prop: 'deptName', label: '??', minWidth: 150 },
|
||||
{ prop: 'majorName', label: '??', minWidth: 150 },
|
||||
{ prop: 'teacherName', label: '???', width: 100 },
|
||||
{ prop: 'grade', label: '????', width: 100 },
|
||||
{ prop: 'gradeCurr', label: '??', width: 80 },
|
||||
{ prop: 'classNo', label: '??', width: 120 },
|
||||
{ prop: 'stuNo', label: '??', width: 120 },
|
||||
{ prop: 'realName', label: '??', width: 100 },
|
||||
{ prop: 'gender', label: '??', width: 80 },
|
||||
{ prop: 'education', label: '??', width: 100 },
|
||||
{ prop: 'majorLevel', label: '??', width: 100 },
|
||||
{ prop: 'phone', label: '????', width: 120 },
|
||||
{ prop: 'money', label: '??', width: 100 },
|
||||
{ prop: 'checkStatus', label: '????', width: 100 }
|
||||
{ prop: 'deptName', label: '学院', minWidth: 150 },
|
||||
{ prop: 'majorName', label: '专业', minWidth: 150 },
|
||||
{ prop: 'teacherName', label: '班主任', width: 100 },
|
||||
{ prop: 'grade', label: '入学年份', width: 100 },
|
||||
{ prop: 'gradeCurr', label: '年级', width: 80 },
|
||||
{ prop: 'classNo', label: '班级', width: 120 },
|
||||
{ prop: 'stuNo', label: '学号', width: 120 },
|
||||
{ prop: 'realName', label: '姓名', width: 100 },
|
||||
{ prop: 'gender', label: '性别', width: 80 },
|
||||
{ prop: 'education', label: '学历', width: 100 },
|
||||
{ prop: 'majorLevel', label: '层次', width: 100 },
|
||||
{ prop: 'phone', label: '联系电话', width: 120 },
|
||||
{ prop: 'money', label: '金额', width: 100 },
|
||||
{ prop: 'checkStatus', label: '审核状态', width: 100 }
|
||||
]
|
||||
|
||||
// ????????????
|
||||
// 列配置映射
|
||||
const columnConfigMap: Record<string, { icon: any }> = {
|
||||
deptName: { icon: OfficeBuilding },
|
||||
majorName: { icon: Reading },
|
||||
@@ -341,22 +341,22 @@ const columnConfigMap: Record<string, { icon: any }> = {
|
||||
checkStatus: { icon: CircleCheck }
|
||||
}
|
||||
|
||||
// ???????hook
|
||||
// 表格列控制hook
|
||||
const {
|
||||
visibleColumns,
|
||||
visibleColumnsSorted,
|
||||
checkColumnVisible,
|
||||
handleColumnChange,
|
||||
handleColumnOrderChange
|
||||
} = useTableColumnControl(tableColumns, route.path)
|
||||
} = useTableColumnControl(tableColumns)
|
||||
|
||||
// ????
|
||||
// 表格样式
|
||||
const tableStyle = {
|
||||
cellStyle: { padding: '8px 0' },
|
||||
headerCellStyle: { background: '#f5f7fa', color: '#606266', fontWeight: 'bold' }
|
||||
}
|
||||
|
||||
// ?? useTable
|
||||
// 使用 useTable
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
queryForm: {
|
||||
termId: '',
|
||||
@@ -382,7 +382,7 @@ const {
|
||||
tableStyle: _tableStyle
|
||||
} = useTable(state)
|
||||
|
||||
// ?????
|
||||
// 格式化性别
|
||||
const formatGender = (value: string | number) => {
|
||||
if (value === null || value === undefined || value === '') {
|
||||
return '-'
|
||||
@@ -391,7 +391,7 @@ const formatGender = (value: string | number) => {
|
||||
return dictItem ? dictItem.label : value
|
||||
}
|
||||
|
||||
// ??????
|
||||
// 格式化学历
|
||||
const formatEducation = (value: string | number) => {
|
||||
if (value === null || value === undefined || value === '') {
|
||||
return '-'
|
||||
@@ -400,7 +400,7 @@ const formatEducation = (value: string | number) => {
|
||||
return dictItem ? dictItem.label : value
|
||||
}
|
||||
|
||||
// ??????
|
||||
// 格式化层次
|
||||
const formatMajorLevel = (value: string | number) => {
|
||||
if (value === null || value === undefined || value === '') {
|
||||
return '-'
|
||||
@@ -409,7 +409,7 @@ const formatMajorLevel = (value: string | number) => {
|
||||
return dictItem ? dictItem.label : value
|
||||
}
|
||||
|
||||
// ????????
|
||||
// 格式化审核状态
|
||||
const formatCheckStatus = (value: string | number) => {
|
||||
if (value === null || value === undefined || value === '') {
|
||||
return '-'
|
||||
@@ -418,7 +418,7 @@ const formatCheckStatus = (value: string | number) => {
|
||||
return dictItem ? dictItem.label : value
|
||||
}
|
||||
|
||||
// ??
|
||||
// 重置
|
||||
const handleReset = () => {
|
||||
searchFormRef.value?.resetFields()
|
||||
state.queryForm.termId = ''
|
||||
@@ -430,40 +430,40 @@ const handleReset = () => {
|
||||
getDataList()
|
||||
}
|
||||
|
||||
// ??
|
||||
// 新增
|
||||
const handleAdd = () => {
|
||||
useMessage().info('?????')
|
||||
useMessage().info('功能开发中')
|
||||
// formDialogRef.value?.openDialog('add')
|
||||
}
|
||||
|
||||
// ??
|
||||
// 编辑
|
||||
const handleEdit = (row: any) => {
|
||||
useMessage().info('?????')
|
||||
useMessage().info('功能开发中')
|
||||
// formDialogRef.value?.openDialog('edit', row.id)
|
||||
}
|
||||
|
||||
// ??
|
||||
// 删除
|
||||
const handleDelete = async (row: any) => {
|
||||
try {
|
||||
const { confirm } = useMessageBox()
|
||||
await confirm(`???????? ${row.stuNo} ????????`)
|
||||
await confirm(`确定要删除学号为 ${row.stuNo} 的学生记录吗?`)
|
||||
await delObj(row.id)
|
||||
useMessage().success('????')
|
||||
useMessage().success('删除成功')
|
||||
getDataList()
|
||||
} catch (err: any) {
|
||||
if (err !== 'cancel') {
|
||||
useMessage().error(err.msg || '????')
|
||||
useMessage().error(err.msg || '删除失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ??
|
||||
// 导出
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
exportLoading.value = true
|
||||
const res = await exportExcel(state.queryForm)
|
||||
const blob = new Blob([res.data as BlobPart])
|
||||
const fileName = `?????_${new Date().getTime()}.xls`
|
||||
const fileName = `免学费学生_${new Date().getTime()}.xls`
|
||||
const elink = document.createElement('a')
|
||||
elink.download = fileName
|
||||
elink.style.display = 'none'
|
||||
@@ -472,15 +472,15 @@ const handleExport = async () => {
|
||||
elink.click()
|
||||
URL.revokeObjectURL(elink.href)
|
||||
document.body.removeChild(elink)
|
||||
useMessage().success('????')
|
||||
useMessage().success('导出成功')
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg || '????')
|
||||
useMessage().error(err.msg || '导出失败')
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// ??????
|
||||
// 获取学期列表
|
||||
const getTermList = async () => {
|
||||
try {
|
||||
const res = await request({
|
||||
@@ -497,7 +497,7 @@ const getTermList = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// ??????
|
||||
// 获取学院列表
|
||||
const getDeptListData = async () => {
|
||||
try {
|
||||
const res = await getDeptList()
|
||||
@@ -511,7 +511,7 @@ const getDeptListData = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// ??????
|
||||
// 获取班级列表
|
||||
const getClassListData = async () => {
|
||||
try {
|
||||
const res = await getClassListByRole()
|
||||
@@ -525,7 +525,7 @@ const getClassListData = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// ????????
|
||||
// 获取入学年份列表
|
||||
const getGradeList = async () => {
|
||||
try {
|
||||
const res = await queryAllSchoolYear()
|
||||
@@ -539,7 +539,7 @@ const getGradeList = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// ??????
|
||||
// 获取性别字典
|
||||
const getGenderDict = async () => {
|
||||
try {
|
||||
const res = await getDicts('sexy')
|
||||
@@ -556,7 +556,7 @@ const getGenderDict = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// ??????
|
||||
// 获取学历字典
|
||||
const getEducationDict = async () => {
|
||||
try {
|
||||
const res = await getDicts('pre_school_education')
|
||||
@@ -573,7 +573,7 @@ const getEducationDict = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// ??????
|
||||
// 获取层次字典
|
||||
const getMajorLevelDict = async () => {
|
||||
try {
|
||||
const res = await getDicts('basic_major_level')
|
||||
@@ -590,7 +590,7 @@ const getMajorLevelDict = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// ????
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
getTermList()
|
||||
getDeptListData()
|
||||
|
||||
@@ -1,40 +1,40 @@
|
||||
<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="effectiveMoney">
|
||||
<el-form-item label="有效金额" prop="effectiveMoney">
|
||||
<el-input-number
|
||||
v-model="searchForm.effectiveMoney"
|
||||
placeholder="???????"
|
||||
placeholder="请输入有效金额"
|
||||
:precision="2"
|
||||
:min="0"
|
||||
clearable
|
||||
style="width: 200px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="????" prop="isLiveNum">
|
||||
<el-form-item label="是否住人" prop="isLiveNum">
|
||||
<el-select
|
||||
v-model="searchForm.isLiveNum"
|
||||
placeholder="?????"
|
||||
placeholder="请选择"
|
||||
clearable
|
||||
style="width: 200px">
|
||||
<el-option label="??0" :value="true" />
|
||||
<el-option label="??0" :value="false" />
|
||||
<el-option label="等于0" :value="true" />
|
||||
<el-option label="不等于0" :value="false" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="??" prop="buildNo">
|
||||
<el-form-item label="楼号" prop="buildNo">
|
||||
<el-select
|
||||
v-model="searchForm.buildNo"
|
||||
placeholder="?????"
|
||||
placeholder="请选择楼号"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 200px">
|
||||
@@ -46,48 +46,48 @@
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="???" prop="roomNo">
|
||||
<el-form-item label="房间号" prop="roomNo">
|
||||
<el-input
|
||||
v-model="searchForm.roomNo"
|
||||
placeholder="??????"
|
||||
placeholder="请输入房间号"
|
||||
clearable
|
||||
style="width: 200px" />
|
||||
</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-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="FolderAdd"
|
||||
type="primary"
|
||||
@click="formDialogRef.openDialog()">
|
||||
????
|
||||
新增明细
|
||||
</el-button>
|
||||
<el-button
|
||||
icon="Download"
|
||||
type="success"
|
||||
class="ml10"
|
||||
@click="handleExport">
|
||||
????
|
||||
导出数据
|
||||
</el-button>
|
||||
<el-button
|
||||
icon="Setting"
|
||||
type="warning"
|
||||
class="ml10"
|
||||
@click="handleInitWaterOrder">
|
||||
??????????
|
||||
批量初始化订单
|
||||
</el-button>
|
||||
<right-toolbar
|
||||
v-model:showSearch="showSearch"
|
||||
@@ -103,7 +103,7 @@
|
||||
@order-change="handleColumnOrderChange"
|
||||
>
|
||||
<template #trigger>
|
||||
<el-tooltip class="item" effect="dark" content="???" placement="top">
|
||||
<el-tooltip class="item" effect="dark" content="列设置" placement="top">
|
||||
<el-button circle style="margin-left: 0;">
|
||||
<el-icon><Menu /></el-icon>
|
||||
</el-button>
|
||||
@@ -115,7 +115,7 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- ?? -->
|
||||
<!-- 表格 -->
|
||||
<el-table
|
||||
:data="state.dataList"
|
||||
v-loading="state.loading"
|
||||
@@ -124,7 +124,7 @@
|
||||
:header-cell-style="tableStyle.headerCellStyle"
|
||||
class="modern-table"
|
||||
@sort-change="sortChangeHandle">
|
||||
<el-table-column type="index" label="??" width="70" align="center">
|
||||
<el-table-column type="index" label="序号" width="70" align="center">
|
||||
<template #header>
|
||||
<el-icon><List /></el-icon>
|
||||
</template>
|
||||
@@ -134,7 +134,7 @@
|
||||
</el-table-column>
|
||||
<template v-for="col in visibleColumnsSorted" :key="col.prop || col.label">
|
||||
<el-table-column
|
||||
v-if="checkColumnVisible(col.prop || '') && col.prop !== '??'"
|
||||
v-if="checkColumnVisible(col.prop || '') && col.prop !== '序号'"
|
||||
:prop="col.prop"
|
||||
:label="col.label"
|
||||
show-overflow-tooltip
|
||||
@@ -145,53 +145,53 @@
|
||||
<el-icon><component :is="columnConfigMap[col.prop]?.icon || Money" /></el-icon>
|
||||
<span style="margin-left: 4px">{{ col.label }}</span>
|
||||
</template>
|
||||
<!-- ???????? -->
|
||||
<!-- 床位号格式化 -->
|
||||
<template v-if="col.prop === 'bedNum'" #default="scope">
|
||||
<el-tag v-if="scope.row.bedNum" size="small" type="info" effect="plain">
|
||||
{{ scope.row.bedNum }}??
|
||||
{{ scope.row.bedNum }}号床
|
||||
</el-tag>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
<!-- ??????????-->
|
||||
<!-- 住人数量格式化-->
|
||||
<template v-else-if="col.prop === 'liveNum'" #default="scope">
|
||||
<el-tag v-if="scope.row.liveNum !== undefined && scope.row.liveNum !== null" size="small" type="success" effect="plain">
|
||||
{{ scope.row.liveNum }}
|
||||
</el-tag>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
<!-- ??????????-->
|
||||
<!-- 期初余额格式化-->
|
||||
<template v-else-if="col.prop === 'oddbMoney'" #default="scope">
|
||||
<el-tag v-if="scope.row.oddbMoney" size="small" type="success" effect="plain">
|
||||
?{{ Number(scope.row.oddbMoney).toFixed(2) }}
|
||||
¥{{ Number(scope.row.oddbMoney).toFixed(2) }}
|
||||
</el-tag>
|
||||
<span v-else>?0.00</span>
|
||||
<span v-else>¥0.00</span>
|
||||
</template>
|
||||
<!-- ????????? -->
|
||||
<!-- 充值金额格式化 -->
|
||||
<template v-else-if="col.prop === 'rechargeMoney'" #default="scope">
|
||||
<el-tag v-if="scope.row.rechargeMoney" size="small" type="primary" effect="plain">
|
||||
?{{ Number(scope.row.rechargeMoney).toFixed(2) }}
|
||||
¥{{ Number(scope.row.rechargeMoney).toFixed(2) }}
|
||||
</el-tag>
|
||||
<span v-else>?0.00</span>
|
||||
<span v-else>¥0.00</span>
|
||||
</template>
|
||||
<!-- ??????????-->
|
||||
<!-- 消费金额格式化-->
|
||||
<template v-else-if="col.prop === 'costMoney'" #default="scope">
|
||||
<el-tag v-if="scope.row.costMoney" size="small" type="warning" effect="plain">
|
||||
?{{ Number(scope.row.costMoney).toFixed(2) }}
|
||||
¥{{ Number(scope.row.costMoney).toFixed(2) }}
|
||||
</el-tag>
|
||||
<span v-else>?0.00</span>
|
||||
<span v-else>¥0.00</span>
|
||||
</template>
|
||||
<!-- ??????????-->
|
||||
<!-- 有效金额格式化-->
|
||||
<template v-else-if="col.prop === 'effectiveMoney'" #default="scope">
|
||||
<el-tag size="small" :type="Number(scope.row.effectiveMoney) < 0 ? 'danger' : 'success'" effect="plain">
|
||||
?{{ scope.row.effectiveMoney ? Number(scope.row.effectiveMoney).toFixed(2) : '0.00' }}
|
||||
¥{{ scope.row.effectiveMoney ? Number(scope.row.effectiveMoney).toFixed(2) : '0.00' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
<el-table-column label="??" width="200" align="center" fixed="right">
|
||||
<el-table-column label="操作" width="200" align="center" fixed="right">
|
||||
<template #header>
|
||||
<el-icon><Setting /></el-icon>
|
||||
<span style="margin-left: 4px">??</span>
|
||||
<span style="margin-left: 4px">操作</span>
|
||||
</template>
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
@@ -199,27 +199,27 @@
|
||||
link
|
||||
type="primary"
|
||||
@click="handleEdit(scope.row)">
|
||||
??
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
icon="View"
|
||||
link
|
||||
type="info"
|
||||
@click="handleViewDetail(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"
|
||||
@@ -229,28 +229,28 @@
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<!-- ???? -->
|
||||
<!-- 新增/编辑对话框 -->
|
||||
<FormDialog ref="formDialogRef" @refresh="getDataList()" />
|
||||
|
||||
<!-- ???? -->
|
||||
<!-- 详情对话框 -->
|
||||
<DetailDialog ref="detailDialogRef" />
|
||||
|
||||
<!-- ??????????-->
|
||||
<el-dialog v-model="initDialogVisible" title="???????" :width="500" :close-on-click-modal="false" draggable>
|
||||
<!-- 批量初始化对话框-->
|
||||
<el-dialog v-model="initDialogVisible" title="批量初始化订单" :width="500" :close-on-click-modal="false" draggable>
|
||||
<el-form ref="initFormRef" :model="initForm" :rules="initRules" label-width="120px">
|
||||
<el-form-item label="????" prop="costMoney">
|
||||
<el-form-item label="消费金额" prop="costMoney">
|
||||
<el-input-number
|
||||
v-model="initForm.costMoney"
|
||||
:precision="2"
|
||||
:min="0"
|
||||
placeholder="???????"
|
||||
placeholder="请输入消费金额"
|
||||
style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="initDialogVisible = false">??</el-button>
|
||||
<el-button type="primary" @click="confirmInit" :loading="initLoading">??</el-button>
|
||||
<el-button @click="initDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="confirmInit" :loading="initLoading">确认</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
@@ -271,7 +271,7 @@ import { List, OfficeBuilding, House, UserFilled, Money, Setting, Menu, Search,
|
||||
import { useTableColumnControl } from '/@/hooks/tableColumn'
|
||||
|
||||
|
||||
// ??????
|
||||
// 定义变量
|
||||
const route = useRoute()
|
||||
const searchFormRef = ref()
|
||||
const columnControlRef = ref<any>()
|
||||
@@ -283,19 +283,19 @@ const buildingList = ref<any[]>([])
|
||||
const initDialogVisible = ref(false)
|
||||
const initLoading = ref(false)
|
||||
|
||||
// ??????
|
||||
// 表格列配置
|
||||
const tableColumns = [
|
||||
{ prop: 'buildNo', label: '??' },
|
||||
{ prop: 'roomNo', label: '???' },
|
||||
{ prop: 'bedNum', label: '???' },
|
||||
{ prop: 'liveNum', label: '????' },
|
||||
{ prop: 'oddbMoney', label: '????' },
|
||||
{ prop: 'rechargeMoney', label: '????' },
|
||||
{ prop: 'costMoney', label: '????' },
|
||||
{ prop: 'effectiveMoney', label: '????' }
|
||||
{ prop: 'buildNo', label: '楼号' },
|
||||
{ prop: 'roomNo', label: '房间号' },
|
||||
{ prop: 'bedNum', label: '床位号' },
|
||||
{ prop: 'liveNum', label: '住人数量' },
|
||||
{ prop: 'oddbMoney', label: '期初余额' },
|
||||
{ prop: 'rechargeMoney', label: '充值金额' },
|
||||
{ prop: 'costMoney', label: '消费金额' },
|
||||
{ prop: 'effectiveMoney', label: '有效金额' }
|
||||
]
|
||||
|
||||
// ????????????
|
||||
// 列配置映射
|
||||
const columnConfigMap: Record<string, { icon: any }> = {
|
||||
buildNo: { icon: OfficeBuilding },
|
||||
roomNo: { icon: House },
|
||||
@@ -307,16 +307,16 @@ const columnConfigMap: Record<string, { icon: any }> = {
|
||||
effectiveMoney: { icon: Money }
|
||||
}
|
||||
|
||||
// ???????hook
|
||||
// 表格列控制hook
|
||||
const {
|
||||
visibleColumns,
|
||||
visibleColumnsSorted,
|
||||
checkColumnVisible,
|
||||
handleColumnChange,
|
||||
handleColumnOrderChange
|
||||
} = useTableColumnControl(tableColumns, route.path)
|
||||
} = useTableColumnControl(tableColumns)
|
||||
|
||||
// ????
|
||||
// 搜索表单
|
||||
const searchForm = reactive({
|
||||
effectiveMoney: undefined as number | undefined,
|
||||
isLiveNum: undefined as boolean | undefined,
|
||||
@@ -324,20 +324,20 @@ const searchForm = reactive({
|
||||
roomNo: ''
|
||||
})
|
||||
|
||||
// ??????
|
||||
// 初始化表单
|
||||
const initForm = reactive({
|
||||
costMoney: 0
|
||||
})
|
||||
|
||||
// ??????????
|
||||
// 初始化表单规则
|
||||
const initRules = {
|
||||
costMoney: [
|
||||
{ required: true, message: '???????', trigger: 'blur' },
|
||||
{ type: 'number', min: 0, message: '??????????0', trigger: 'blur' }
|
||||
{ required: true, message: '请输入消费金额', trigger: 'blur' },
|
||||
{ type: 'number', min: 0, message: '消费金额必须大于等于0', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
|
||||
// ?? useTable
|
||||
// 使用 useTable
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
queryForm: searchForm,
|
||||
pageList: fetchList,
|
||||
@@ -357,12 +357,12 @@ const {
|
||||
tableStyle
|
||||
} = useTable(state)
|
||||
|
||||
// ??
|
||||
// 查询
|
||||
const handleSearch = () => {
|
||||
getDataList()
|
||||
}
|
||||
|
||||
// ??
|
||||
// 重置
|
||||
const handleReset = () => {
|
||||
searchFormRef.value?.formRef?.resetFields()
|
||||
searchForm.effectiveMoney = undefined
|
||||
@@ -372,43 +372,43 @@ const handleReset = () => {
|
||||
getDataList()
|
||||
}
|
||||
|
||||
// ??
|
||||
// 编辑
|
||||
const handleEdit = (row: any) => {
|
||||
formDialogRef.value.openDialog('edit', row)
|
||||
}
|
||||
|
||||
// ????
|
||||
// 查看详情
|
||||
const handleViewDetail = (row: any) => {
|
||||
detailDialogRef.value.openDialog(row.roomNo)
|
||||
}
|
||||
|
||||
// ??
|
||||
// 删除
|
||||
const handleDelete = async (row: any) => {
|
||||
try {
|
||||
await useMessageBox().confirm('???????????')
|
||||
await useMessageBox().confirm('确定要删除该记录吗?')
|
||||
await delObjs([row.id])
|
||||
useMessage().success('????')
|
||||
useMessage().success('删除成功')
|
||||
getDataList()
|
||||
} catch (err: any) {
|
||||
if (err !== 'cancel') {
|
||||
useMessage().error(err.msg || '????')
|
||||
useMessage().error(err.msg || '删除失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ??
|
||||
// 导出
|
||||
const handleExport = () => {
|
||||
// TODO: ????
|
||||
useMessage().warning('???????')
|
||||
// TODO: 实现导出
|
||||
useMessage().warning('功能开发中')
|
||||
}
|
||||
|
||||
// ??????????
|
||||
// 批量初始化订单
|
||||
const handleInitWaterOrder = () => {
|
||||
initDialogVisible.value = true
|
||||
initForm.costMoney = 0
|
||||
}
|
||||
|
||||
// ??????
|
||||
// 确认初始化
|
||||
const confirmInit = async () => {
|
||||
if (!initFormRef.value) return
|
||||
|
||||
@@ -418,18 +418,18 @@ const confirmInit = async () => {
|
||||
initLoading.value = true
|
||||
try {
|
||||
await initWaterOrder({ costMoney: initForm.costMoney })
|
||||
useMessage().success('?????')
|
||||
useMessage().success('初始化成功')
|
||||
initDialogVisible.value = false
|
||||
getDataList()
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg || '?????')
|
||||
useMessage().error(err.msg || '初始化失败')
|
||||
} finally {
|
||||
initLoading.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ??????
|
||||
// 获取楼号列表
|
||||
const getBuildingListData = async () => {
|
||||
try {
|
||||
const res = await getBuildingList()
|
||||
@@ -441,7 +441,7 @@ const getBuildingListData = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// ????
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
getBuildingListData()
|
||||
nextTick(() => {
|
||||
@@ -454,4 +454,3 @@ onMounted(() => {
|
||||
<style scoped lang="scss">
|
||||
@import '/@/assets/styles/modern-page.scss';
|
||||
</style>
|
||||
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
<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="roomNo">
|
||||
<el-form-item label="宿舍号" prop="roomNo">
|
||||
<el-input
|
||||
v-model="searchForm.roomNo"
|
||||
placeholder="??????"
|
||||
placeholder="请输入宿舍号"
|
||||
clearable
|
||||
style="width: 200px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="??" prop="year">
|
||||
<el-form-item label="学年" prop="year">
|
||||
<el-select
|
||||
v-model="searchForm.year"
|
||||
placeholder="?????"
|
||||
placeholder="请选择学年"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 200px">
|
||||
@@ -34,10 +34,10 @@
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="??" prop="period">
|
||||
<el-form-item label="学期" prop="period">
|
||||
<el-select
|
||||
v-model="searchForm.period"
|
||||
placeholder="?????"
|
||||
placeholder="请选择学期"
|
||||
clearable
|
||||
style="width: 200px">
|
||||
<el-option
|
||||
@@ -49,26 +49,26 @@
|
||||
</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-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="FolderAdd"
|
||||
type="primary"
|
||||
@click="formDialogRef.openDialog()">
|
||||
????
|
||||
新增订单
|
||||
</el-button>
|
||||
<right-toolbar
|
||||
v-model:showSearch="showSearch"
|
||||
@@ -84,7 +84,7 @@
|
||||
@order-change="handleColumnOrderChange"
|
||||
>
|
||||
<template #trigger>
|
||||
<el-tooltip class="item" effect="dark" content="???" placement="top">
|
||||
<el-tooltip class="item" effect="dark" content="列设置" placement="top">
|
||||
<el-button circle style="margin-left: 0;">
|
||||
<el-icon><Menu /></el-icon>
|
||||
</el-button>
|
||||
@@ -96,7 +96,7 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- ?? -->
|
||||
<!-- 表格 -->
|
||||
<el-table
|
||||
:data="state.dataList"
|
||||
v-loading="state.loading"
|
||||
@@ -105,7 +105,7 @@
|
||||
:header-cell-style="tableStyle.headerCellStyle"
|
||||
class="modern-table"
|
||||
@sort-change="sortChangeHandle">
|
||||
<el-table-column type="index" label="??" width="70" align="center">
|
||||
<el-table-column type="index" label="序号" width="70" align="center">
|
||||
<template #header>
|
||||
<el-icon><List /></el-icon>
|
||||
</template>
|
||||
@@ -115,7 +115,7 @@
|
||||
</el-table-column>
|
||||
<template v-for="col in visibleColumnsSorted" :key="col.prop || col.label">
|
||||
<el-table-column
|
||||
v-if="checkColumnVisible(col.prop || '') && col.prop !== '??'"
|
||||
v-if="checkColumnVisible(col.prop || '') && col.prop !== '序号'"
|
||||
:prop="col.prop"
|
||||
:label="col.label"
|
||||
show-overflow-tooltip
|
||||
@@ -124,40 +124,40 @@
|
||||
<el-icon><component :is="columnConfigMap[col.prop]?.icon || Calendar" /></el-icon>
|
||||
<span style="margin-left: 4px">{{ col.label }}</span>
|
||||
</template>
|
||||
<!-- ????????-->
|
||||
<!-- 学期格式化-->
|
||||
<template v-if="col.prop === 'period'" #default="scope">
|
||||
<el-tag size="small" type="primary" effect="plain">
|
||||
{{ formatSchoolTerm(scope.row.period) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
<!-- ????????-->
|
||||
<!-- 类型格式化-->
|
||||
<template v-else-if="col.prop === 'type'" #default="scope">
|
||||
<el-tag size="small" type="info" effect="plain">
|
||||
{{ formatType(scope.row.type) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
<!-- ????????? -->
|
||||
<!-- 支付方式格式化 -->
|
||||
<template v-else-if="col.prop === 'paymentCode'" #default="scope">
|
||||
<el-tag size="small" type="warning" effect="plain">
|
||||
{{ formatPaymentType(scope.row.paymentCode) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
<!-- ????????-->
|
||||
<!-- 金额格式化-->
|
||||
<template v-else-if="col.prop === 'money'" #default="scope">
|
||||
<el-tag v-if="scope.row.money" size="small" type="success" effect="plain">
|
||||
?{{ scope.row.money }}
|
||||
¥{{ scope.row.money }}
|
||||
</el-tag>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
<!-- ????????? -->
|
||||
<!-- 充值状态格式化 -->
|
||||
<template v-else-if="col.prop === 'chargeState'" #default="scope">
|
||||
<StatusTag
|
||||
:value="scope.row.chargeState"
|
||||
:options="[{ label: '??', value: '1' }, { label: '??', value: '0' }]"
|
||||
:options="[{ label: '已充值', value: '1' }, { label: '未充值', value: '0' }]"
|
||||
:type-map="{ '1': { type: 'success', effect: 'light' }, '0': { type: 'danger', effect: 'light' } }"
|
||||
/>
|
||||
</template>
|
||||
<!-- ??????? -->
|
||||
<!-- 订单状态 -->
|
||||
<template v-else-if="col.prop === 'state'" #default="scope">
|
||||
<el-tag size="small" type="info" effect="plain">
|
||||
{{ formatState(scope.row.state) }}
|
||||
@@ -165,10 +165,10 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
<el-table-column label="??" width="150" align="center" fixed="right">
|
||||
<el-table-column label="操作" width="150" align="center" fixed="right">
|
||||
<template #header>
|
||||
<el-icon><Setting /></el-icon>
|
||||
<span style="margin-left: 4px">??</span>
|
||||
<span style="margin-left: 4px">操作</span>
|
||||
</template>
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
@@ -176,20 +176,20 @@
|
||||
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"
|
||||
@@ -199,7 +199,7 @@
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<!-- ???? -->
|
||||
<!-- 新增/编辑对话框 -->
|
||||
<FormDialog ref="formDialogRef" @refresh="getDataList()" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -220,7 +220,7 @@ import { useTableColumnControl } from '/@/hooks/tableColumn'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
const StatusTag = defineAsyncComponent(() => import('/@/components/StatusTag/index.vue'))
|
||||
|
||||
// ??????
|
||||
// 定义变量
|
||||
const route = useRoute()
|
||||
const searchFormRef = ref()
|
||||
const columnControlRef = ref<any>()
|
||||
@@ -233,22 +233,22 @@ const paymentTypeList = ref<any[]>([])
|
||||
const chargeStateList = ref<any[]>([])
|
||||
const stateList = ref<any[]>([])
|
||||
|
||||
// ??????
|
||||
// 表格列配置
|
||||
const tableColumns = [
|
||||
{ prop: 'roomNo', label: '???' },
|
||||
{ prop: 'year', label: '??' },
|
||||
{ prop: 'period', label: '??' },
|
||||
{ prop: 'orderNum', label: '???' },
|
||||
{ prop: 'type', label: '??' },
|
||||
{ prop: 'paymentCode', label: '????' },
|
||||
{ prop: 'money', label: '??' },
|
||||
{ prop: 'chargeAccount', label: '??????' },
|
||||
{ prop: 'chargeRealname', label: '????????' },
|
||||
{ prop: 'chargeState', label: '????' },
|
||||
{ prop: 'state', label: '??' }
|
||||
{ prop: 'roomNo', label: '宿舍号' },
|
||||
{ prop: 'year', label: '学年' },
|
||||
{ prop: 'period', label: '学期' },
|
||||
{ prop: 'orderNum', label: '订单号' },
|
||||
{ prop: 'type', label: '类型' },
|
||||
{ prop: 'paymentCode', label: '支付方式' },
|
||||
{ prop: 'money', label: '金额' },
|
||||
{ prop: 'chargeAccount', label: '充值账号' },
|
||||
{ prop: 'chargeRealname', label: '充值人姓名' },
|
||||
{ prop: 'chargeState', label: '充值状态' },
|
||||
{ prop: 'state', label: '状态' }
|
||||
]
|
||||
|
||||
// ????????????
|
||||
// 列配置映射
|
||||
const columnConfigMap: Record<string, { icon: any }> = {
|
||||
roomNo: { icon: House },
|
||||
year: { icon: Calendar },
|
||||
@@ -263,23 +263,23 @@ const columnConfigMap: Record<string, { icon: any }> = {
|
||||
state: { icon: CircleCheck }
|
||||
}
|
||||
|
||||
// ???????hook
|
||||
// 表格列控制hook
|
||||
const {
|
||||
visibleColumns,
|
||||
visibleColumnsSorted,
|
||||
checkColumnVisible,
|
||||
handleColumnChange,
|
||||
handleColumnOrderChange
|
||||
} = useTableColumnControl(tableColumns, route.path)
|
||||
} = useTableColumnControl(tableColumns)
|
||||
|
||||
// ????
|
||||
// 搜索表单
|
||||
const searchForm = reactive({
|
||||
roomNo: '',
|
||||
year: '',
|
||||
period: ''
|
||||
})
|
||||
|
||||
// ?? useTable
|
||||
// 使用 useTable
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
queryForm: searchForm,
|
||||
pageList: fetchList,
|
||||
@@ -299,12 +299,12 @@ const {
|
||||
tableStyle
|
||||
} = useTable(state)
|
||||
|
||||
// ??
|
||||
// 查询
|
||||
const handleSearch = () => {
|
||||
getDataList()
|
||||
}
|
||||
|
||||
// ??
|
||||
// 重置
|
||||
const handleReset = () => {
|
||||
searchFormRef.value?.formRef?.resetFields()
|
||||
searchForm.roomNo = ''
|
||||
@@ -313,27 +313,27 @@ const handleReset = () => {
|
||||
getDataList()
|
||||
}
|
||||
|
||||
// ??
|
||||
// 编辑
|
||||
const handleEdit = (row: any) => {
|
||||
formDialogRef.value.openDialog('edit', row)
|
||||
}
|
||||
|
||||
// ??
|
||||
// 删除
|
||||
const handleDelete = async (row: any) => {
|
||||
try {
|
||||
await useMessageBox().confirm('??????????')
|
||||
await useMessageBox().confirm('确定要删除该订单吗?')
|
||||
const { delObjs } = await import("/@/api/stuwork/waterorder")
|
||||
await delObjs([row.id])
|
||||
useMessage().success('????')
|
||||
useMessage().success('删除成功')
|
||||
getDataList()
|
||||
} catch (err: any) {
|
||||
if (err !== 'cancel') {
|
||||
useMessage().error(err.msg || '????')
|
||||
useMessage().error(err.msg || '删除失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ??????
|
||||
// 格式化学期
|
||||
const formatSchoolTerm = (value: string | number) => {
|
||||
if (value === null || value === undefined || value === '') {
|
||||
return '-'
|
||||
@@ -342,7 +342,7 @@ const formatSchoolTerm = (value: string | number) => {
|
||||
return dictItem ? dictItem.label : value
|
||||
}
|
||||
|
||||
// ??????
|
||||
// 格式化类型
|
||||
const formatType = (value: string | number) => {
|
||||
if (value === null || value === undefined || value === '') {
|
||||
return '-'
|
||||
@@ -351,7 +351,7 @@ const formatType = (value: string | number) => {
|
||||
return dictItem ? dictItem.label : value
|
||||
}
|
||||
|
||||
// ????????
|
||||
// 格式化支付方式
|
||||
const formatPaymentType = (value: string | number) => {
|
||||
if (value === null || value === undefined || value === '') {
|
||||
return '-'
|
||||
@@ -360,7 +360,7 @@ const formatPaymentType = (value: string | number) => {
|
||||
return dictItem ? dictItem.label : value
|
||||
}
|
||||
|
||||
// ????????
|
||||
// 格式化充值状态
|
||||
const formatChargeState = (value: string | number) => {
|
||||
if (value === null || value === undefined || value === '') {
|
||||
return '-'
|
||||
@@ -369,7 +369,7 @@ const formatChargeState = (value: string | number) => {
|
||||
return dictItem ? dictItem.label : value
|
||||
}
|
||||
|
||||
// ??????
|
||||
// 格式化状态
|
||||
const formatState = (value: string | number) => {
|
||||
if (value === null || value === undefined || value === '') {
|
||||
return '-'
|
||||
@@ -378,7 +378,7 @@ const formatState = (value: string | number) => {
|
||||
return dictItem ? dictItem.label : value
|
||||
}
|
||||
|
||||
// ??????
|
||||
// 获取学年列表
|
||||
const getSchoolYearList = async () => {
|
||||
try {
|
||||
const res = await queryAllSchoolYear()
|
||||
@@ -390,7 +390,7 @@ const getSchoolYearList = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// ??????
|
||||
// 获取学期字典
|
||||
const getSchoolTermDict = async () => {
|
||||
try {
|
||||
const res = await getDicts('school_term')
|
||||
@@ -405,7 +405,7 @@ const getSchoolTermDict = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// ??????
|
||||
// 获取类型字典
|
||||
const getTypeDict = async () => {
|
||||
try {
|
||||
const res = await getDicts('dorm_water_source_type')
|
||||
@@ -420,7 +420,7 @@ const getTypeDict = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// ?????????
|
||||
// 获取支付方式字典
|
||||
const getPaymentTypeDict = async () => {
|
||||
try {
|
||||
const res = await getDicts('dorm_water_payment_type')
|
||||
@@ -435,7 +435,7 @@ const getPaymentTypeDict = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// ?????????
|
||||
// 获取充值状态字典
|
||||
const getChargeStateDict = async () => {
|
||||
try {
|
||||
const res = await getDicts('dorm_water_charge_state')
|
||||
@@ -450,7 +450,7 @@ const getChargeStateDict = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// ???????
|
||||
// 获取状态字典
|
||||
const getStateDict = async () => {
|
||||
try {
|
||||
const res = await getDicts('dorm_water_state')
|
||||
@@ -465,7 +465,7 @@ const getStateDict = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// ????
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
getSchoolYearList()
|
||||
getSchoolTermDict()
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user