feat: 从 git 历史迁移业务管理前端页面
从 commit 0b7d625 恢复并迁移以下页面:
基础数据管理:
- 年级管理 (business/base/grade)
- 班级管理 (business/base/class)
- 学科管理 (business/base/subject)
- 区域管理 (business/base/region)
业务功能:
- 学校管理 (business/school) + 3个组件
- 会员管理 (business/member) + 1个组件
- 学生管理 (business/student) + 2个组件
- 应用管理 (business/application) + 1个组件
API 路径修改:/api/xxx → /business/xxx
@author pangu
This commit is contained in:
parent
1939180830
commit
0e75c175b5
|
|
@ -0,0 +1,180 @@
|
||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
v-model="visible"
|
||||||
|
:title="dialogTitle"
|
||||||
|
width="600px"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
destroy-on-close
|
||||||
|
@closed="handleClosed"
|
||||||
|
>
|
||||||
|
<el-form
|
||||||
|
ref="formRef"
|
||||||
|
:model="form"
|
||||||
|
:rules="rules"
|
||||||
|
label-width="100px"
|
||||||
|
>
|
||||||
|
<el-form-item label="应用名称" prop="appName">
|
||||||
|
<el-input v-model="form.appName" placeholder="请输入应用名称" maxlength="50" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="应用编码" prop="appCode">
|
||||||
|
<el-input v-model="form.appCode" placeholder="保存后自动生成" disabled />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="应用描述" prop="description">
|
||||||
|
<el-input
|
||||||
|
v-model="form.description"
|
||||||
|
type="textarea"
|
||||||
|
placeholder="请输入应用描述"
|
||||||
|
:rows="3"
|
||||||
|
maxlength="200"
|
||||||
|
show-word-limit
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="联系人" prop="contactName">
|
||||||
|
<el-input v-model="form.contactName" placeholder="请输入联系人" maxlength="20" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="联系电话" prop="contactPhone">
|
||||||
|
<el-input v-model="form.contactPhone" placeholder="请输入联系电话" maxlength="11" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="状态" prop="status">
|
||||||
|
<el-switch
|
||||||
|
v-model="form.status"
|
||||||
|
active-value="0"
|
||||||
|
inactive-value="1"
|
||||||
|
active-text="正常"
|
||||||
|
inactive-text="停用"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="接口授权" prop="apiAuth">
|
||||||
|
<el-checkbox-group v-model="form.apiAuth">
|
||||||
|
<el-checkbox
|
||||||
|
v-for="item in apiAuthOptions"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.value"
|
||||||
|
>
|
||||||
|
{{ item.label }}
|
||||||
|
</el-checkbox>
|
||||||
|
</el-checkbox-group>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="visible = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="handleSubmit" :loading="submitLoading">确定</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
/**
|
||||||
|
* 应用管理 - 新增/编辑弹窗
|
||||||
|
* @author pangu
|
||||||
|
*/
|
||||||
|
import { addApplication, getApiAuthOptions, updateApplication } from '@/api/pangu/application'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import { computed, nextTick, reactive, ref } from 'vue'
|
||||||
|
|
||||||
|
const emit = defineEmits(['success'])
|
||||||
|
|
||||||
|
const visible = ref(false)
|
||||||
|
const submitLoading = ref(false)
|
||||||
|
const formRef = ref(null)
|
||||||
|
const isEdit = ref(false)
|
||||||
|
const apiAuthOptions = ref([])
|
||||||
|
|
||||||
|
const form = reactive({
|
||||||
|
id: null,
|
||||||
|
appName: '',
|
||||||
|
appCode: '',
|
||||||
|
description: '',
|
||||||
|
contactName: '',
|
||||||
|
contactPhone: '',
|
||||||
|
status: '0',
|
||||||
|
apiAuth: []
|
||||||
|
})
|
||||||
|
|
||||||
|
const rules = {
|
||||||
|
appName: [
|
||||||
|
{ required: true, message: '请输入应用名称', trigger: 'blur' }
|
||||||
|
],
|
||||||
|
contactPhone: [
|
||||||
|
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
const dialogTitle = computed(() => isEdit.value ? '编辑应用' : '新增应用')
|
||||||
|
|
||||||
|
// 加载接口授权选项
|
||||||
|
const loadAuthOptions = async () => {
|
||||||
|
try {
|
||||||
|
const res = await getApiAuthOptions()
|
||||||
|
if (res.code === 200) {
|
||||||
|
apiAuthOptions.value = res.data
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载接口授权选项失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 打开弹窗
|
||||||
|
const open = (row = null) => {
|
||||||
|
visible.value = true
|
||||||
|
loadAuthOptions()
|
||||||
|
|
||||||
|
if (row) {
|
||||||
|
isEdit.value = true
|
||||||
|
nextTick(() => {
|
||||||
|
Object.assign(form, {
|
||||||
|
id: row.id,
|
||||||
|
appName: row.appName,
|
||||||
|
appCode: row.appCode,
|
||||||
|
description: row.description || '',
|
||||||
|
contactName: row.contactName || '',
|
||||||
|
contactPhone: row.contactPhone || '',
|
||||||
|
status: row.status || '0',
|
||||||
|
apiAuth: row.apiAuth || []
|
||||||
|
})
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
isEdit.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交表单
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
try {
|
||||||
|
await formRef.value.validate()
|
||||||
|
submitLoading.value = true
|
||||||
|
|
||||||
|
const api = isEdit.value ? updateApplication : addApplication
|
||||||
|
const res = await api(form)
|
||||||
|
|
||||||
|
if (res.code === 200) {
|
||||||
|
ElMessage.success(isEdit.value ? '修改成功' : '新增成功')
|
||||||
|
visible.value = false
|
||||||
|
emit('success')
|
||||||
|
} else {
|
||||||
|
ElMessage.error(res.msg || '操作失败')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('表单验证失败或提交失败:', error)
|
||||||
|
} finally {
|
||||||
|
submitLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭弹窗后重置表单
|
||||||
|
const handleClosed = () => {
|
||||||
|
formRef.value?.resetFields()
|
||||||
|
Object.assign(form, {
|
||||||
|
id: null,
|
||||||
|
appName: '',
|
||||||
|
appCode: '',
|
||||||
|
description: '',
|
||||||
|
contactName: '',
|
||||||
|
contactPhone: '',
|
||||||
|
status: '0',
|
||||||
|
apiAuth: []
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ open })
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,194 @@
|
||||||
|
<template>
|
||||||
|
<div class="app-container">
|
||||||
|
<!-- 搜索区域 -->
|
||||||
|
<el-card shadow="never" class="search-wrapper">
|
||||||
|
<el-form :model="queryParams" :inline="true">
|
||||||
|
<el-form-item label="应用名称">
|
||||||
|
<el-input v-model="queryParams.appName" placeholder="请输入应用名称" clearable style="width: 200px" @keyup.enter="handleQuery" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="应用编码">
|
||||||
|
<el-input v-model="queryParams.appCode" placeholder="请输入应用编码" clearable style="width: 150px" @keyup.enter="handleQuery" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="状态">
|
||||||
|
<el-select v-model="queryParams.status" placeholder="全部" clearable style="width: 100px">
|
||||||
|
<el-option label="正常" value="0" />
|
||||||
|
<el-option label="停用" value="1" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" :icon="Search" @click="handleQuery">搜索</el-button>
|
||||||
|
<el-button :icon="Refresh" @click="resetQuery">重置</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<!-- 表格区域 -->
|
||||||
|
<el-card shadow="never" style="margin-top: 12px">
|
||||||
|
<el-row :gutter="10" style="margin-bottom: 12px">
|
||||||
|
<el-col :span="1.5">
|
||||||
|
<el-button type="primary" :icon="Plus" @click="handleAdd">新增</el-button>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<el-table v-loading="loading" :data="tableData" border stripe :header-cell-style="{ background: '#f5f7fa', color: '#606266' }" style="width: 100%">
|
||||||
|
<el-table-column prop="appName" label="应用名称" min-width="150" show-overflow-tooltip />
|
||||||
|
<el-table-column prop="appCode" label="应用编码" width="120" />
|
||||||
|
<el-table-column prop="apis" label="授权接口" min-width="250">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag v-for="api in (row.apis || []).slice(0, 3)" :key="api" size="small" style="margin-right: 4px; margin-bottom: 2px">
|
||||||
|
{{ api }}
|
||||||
|
</el-tag>
|
||||||
|
<el-tag v-if="(row.apis || []).length > 3" size="small" type="info">+{{ row.apis.length - 3 }}</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="status" label="状态" width="80" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag :type="row.status === '0' ? 'success' : 'danger'">
|
||||||
|
{{ row.status === '0' ? '正常' : '停用' }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="createTime" label="创建时间" width="160" />
|
||||||
|
<el-table-column prop="createBy" label="创建人" width="100" />
|
||||||
|
<el-table-column label="操作" width="180" fixed="right" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-button type="primary" link :icon="Edit" @click="handleEdit(row)">编辑</el-button>
|
||||||
|
<el-button type="warning" link :icon="Key" @click="handleResetSecret(row)">重置密钥</el-button>
|
||||||
|
<el-button type="danger" link :icon="Delete" @click="handleDelete(row)">删除</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<!-- 分页 -->
|
||||||
|
<el-pagination
|
||||||
|
v-model:current-page="queryParams.pageNum"
|
||||||
|
v-model:page-size="queryParams.pageSize"
|
||||||
|
:page-sizes="[10, 20, 50, 100]"
|
||||||
|
:total="total"
|
||||||
|
layout="total, sizes, prev, pager, next, jumper"
|
||||||
|
style="margin-top: 16px; justify-content: flex-end"
|
||||||
|
@size-change="handleQuery"
|
||||||
|
@current-change="handleQuery"
|
||||||
|
/>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<!-- 新增/编辑弹窗 -->
|
||||||
|
<AppDialog ref="appDialogRef" @success="handleQuery" />
|
||||||
|
|
||||||
|
<!-- 密钥弹窗 -->
|
||||||
|
<SecretDialog ref="secretDialogRef" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
/**
|
||||||
|
* 应用管理页面
|
||||||
|
* @author pangu
|
||||||
|
*/
|
||||||
|
import { Delete, Edit, Key, Plus, Refresh, Search } from '@element-plus/icons-vue'
|
||||||
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
|
import { onMounted, ref } from 'vue'
|
||||||
|
import request from '@/utils/request'
|
||||||
|
import AppDialog from './components/AppDialog.vue'
|
||||||
|
import SecretDialog from './components/SecretDialog.vue'
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
const tableData = ref([])
|
||||||
|
const total = ref(0)
|
||||||
|
|
||||||
|
const queryParams = ref({
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
appName: '',
|
||||||
|
appCode: '',
|
||||||
|
status: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const appDialogRef = ref()
|
||||||
|
const secretDialogRef = ref()
|
||||||
|
|
||||||
|
// 获取应用列表
|
||||||
|
const getList = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const res = await request.get('/business/application/list', { params: queryParams.value })
|
||||||
|
if (res.code === 200) {
|
||||||
|
tableData.value = res.rows
|
||||||
|
total.value = res.total
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 搜索
|
||||||
|
const handleQuery = () => {
|
||||||
|
queryParams.value.pageNum = 1
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置
|
||||||
|
const resetQuery = () => {
|
||||||
|
queryParams.value = {
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
appName: '',
|
||||||
|
appCode: '',
|
||||||
|
status: ''
|
||||||
|
}
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增
|
||||||
|
const handleAdd = () => {
|
||||||
|
appDialogRef.value?.open()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 编辑
|
||||||
|
const handleEdit = (row) => {
|
||||||
|
appDialogRef.value?.open(row)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置密钥
|
||||||
|
const handleResetSecret = async (row) => {
|
||||||
|
ElMessageBox.confirm(`确定要重置应用"${row.appName}"的密钥吗?`, '提示', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
}).then(async () => {
|
||||||
|
const res = await request.put(`/api/application/resetSecret/${row.id}`)
|
||||||
|
if (res.code === 200) {
|
||||||
|
ElMessage.success('重置成功')
|
||||||
|
secretDialogRef.value?.open(res.data)
|
||||||
|
}
|
||||||
|
}).catch(() => {})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除
|
||||||
|
const handleDelete = (row) => {
|
||||||
|
ElMessageBox.confirm(`确定要删除应用"${row.appName}"吗?`, '提示', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
}).then(async () => {
|
||||||
|
const res = await request.delete(`/api/application/${row.id}`)
|
||||||
|
if (res.code === 200) {
|
||||||
|
ElMessage.success('删除成功')
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
}).catch(() => {})
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getList()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.app-container {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
.search-wrapper {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,205 @@
|
||||||
|
<template>
|
||||||
|
<div class="app-container">
|
||||||
|
<!-- 搜索区域 -->
|
||||||
|
<el-card shadow="never" class="search-wrapper">
|
||||||
|
<el-form :model="queryParams" :inline="true">
|
||||||
|
<el-form-item label="班级名称">
|
||||||
|
<el-input v-model="queryParams.className" placeholder="请输入班级名称" clearable style="width: 200px" @keyup.enter="handleQuery" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="状态">
|
||||||
|
<el-select v-model="queryParams.status" placeholder="全部" clearable style="width: 100px">
|
||||||
|
<el-option label="正常" value="0" />
|
||||||
|
<el-option label="停用" value="1" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" :icon="Search" @click="handleQuery">搜索</el-button>
|
||||||
|
<el-button :icon="Refresh" @click="resetQuery">重置</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<!-- 表格区域 -->
|
||||||
|
<el-card shadow="never" style="margin-top: 12px">
|
||||||
|
<el-row :gutter="10" style="margin-bottom: 12px">
|
||||||
|
<el-col :span="1.5">
|
||||||
|
<el-button type="primary" :icon="Plus" @click="handleAdd">新增</el-button>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<el-table v-loading="loading" :data="tableData" border stripe :header-cell-style="{ background: '#f5f7fa', color: '#606266' }" style="width: 100%">
|
||||||
|
<el-table-column prop="className" label="班级名称" min-width="150" />
|
||||||
|
<el-table-column prop="classCode" label="班级编码" width="120" />
|
||||||
|
<el-table-column prop="sort" label="排序" width="80" align="center" />
|
||||||
|
<el-table-column prop="status" label="状态" width="80" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag :type="row.status === '0' ? 'success' : 'danger'">
|
||||||
|
{{ row.status === '0' ? '正常' : '停用' }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="createTime" label="创建时间" width="160" />
|
||||||
|
<el-table-column label="操作" width="150" fixed="right" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-button type="primary" link :icon="Edit" @click="handleEdit(row)">编辑</el-button>
|
||||||
|
<el-button type="danger" link :icon="Delete" @click="handleDelete(row)">删除</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<!-- 分页 -->
|
||||||
|
<el-pagination
|
||||||
|
v-model:current-page="queryParams.pageNum"
|
||||||
|
v-model:page-size="queryParams.pageSize"
|
||||||
|
:page-sizes="[10, 20, 50, 100]"
|
||||||
|
:total="total"
|
||||||
|
layout="total, sizes, prev, pager, next, jumper"
|
||||||
|
style="margin-top: 16px; justify-content: flex-end"
|
||||||
|
@size-change="handleQuery"
|
||||||
|
@current-change="handleQuery"
|
||||||
|
/>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<!-- 新增/编辑弹窗 -->
|
||||||
|
<el-dialog v-model="dialogVisible" :title="dialogTitle" width="500px" :close-on-click-modal="false" destroy-on-close>
|
||||||
|
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
|
||||||
|
<el-form-item label="班级名称" prop="className">
|
||||||
|
<el-input v-model="form.className" placeholder="请输入班级名称" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="班级编码" prop="classCode">
|
||||||
|
<el-input v-model="form.classCode" placeholder="自动生成" disabled />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="排序" prop="sort">
|
||||||
|
<el-input-number v-model="form.sort" :min="0" :max="999" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="状态" prop="status">
|
||||||
|
<el-switch v-model="form.status" active-value="0" inactive-value="1" active-text="正常" inactive-text="停用" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="dialogVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="handleSubmit">确定</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
/**
|
||||||
|
* 班级管理页面
|
||||||
|
* @author pangu
|
||||||
|
*/
|
||||||
|
import { Delete, Edit, Plus, Refresh, Search } from '@element-plus/icons-vue'
|
||||||
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
|
import { onMounted, ref } from 'vue'
|
||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
const tableData = ref([])
|
||||||
|
const total = ref(0)
|
||||||
|
|
||||||
|
const queryParams = ref({
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
className: '',
|
||||||
|
status: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
// 弹窗相关
|
||||||
|
const dialogVisible = ref(false)
|
||||||
|
const dialogTitle = ref('')
|
||||||
|
const formRef = ref()
|
||||||
|
const form = ref({
|
||||||
|
id: null,
|
||||||
|
className: '',
|
||||||
|
classCode: '',
|
||||||
|
sort: 0,
|
||||||
|
status: '0'
|
||||||
|
})
|
||||||
|
|
||||||
|
const rules = {
|
||||||
|
className: [{ required: true, message: '请输入班级名称', trigger: 'blur' }]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取班级列表
|
||||||
|
const getList = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const res = await request.get('/business/class/list', { params: queryParams.value })
|
||||||
|
if (res.code === 200) {
|
||||||
|
tableData.value = res.rows
|
||||||
|
total.value = res.total
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 搜索
|
||||||
|
const handleQuery = () => {
|
||||||
|
queryParams.value.pageNum = 1
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置
|
||||||
|
const resetQuery = () => {
|
||||||
|
queryParams.value = { pageNum: 1, pageSize: 10, className: '', status: '' }
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增
|
||||||
|
const handleAdd = () => {
|
||||||
|
dialogTitle.value = '新增班级'
|
||||||
|
form.value = { id: null, className: '', classCode: 'C' + Date.now(), sort: 0, status: '0' }
|
||||||
|
dialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 编辑
|
||||||
|
const handleEdit = (row) => {
|
||||||
|
dialogTitle.value = '编辑班级'
|
||||||
|
form.value = { ...row }
|
||||||
|
dialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
await formRef.value?.validate()
|
||||||
|
const isEdit = !!form.value.id
|
||||||
|
const res = isEdit
|
||||||
|
? await request.put('/business/class', form.value)
|
||||||
|
: await request.post('/business/class', form.value)
|
||||||
|
if (res.code === 200) {
|
||||||
|
ElMessage.success(isEdit ? '修改成功' : '新增成功')
|
||||||
|
dialogVisible.value = false
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除
|
||||||
|
const handleDelete = (row) => {
|
||||||
|
ElMessageBox.confirm(`确定要删除班级"${row.className}"吗?`, '提示', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
}).then(async () => {
|
||||||
|
const res = await request.delete(`/api/class/${row.id}`)
|
||||||
|
if (res.code === 200) {
|
||||||
|
ElMessage.success('删除成功')
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
}).catch(() => {})
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getList()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.app-container {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
.search-wrapper {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,205 @@
|
||||||
|
<template>
|
||||||
|
<div class="app-container">
|
||||||
|
<!-- 搜索区域 -->
|
||||||
|
<el-card shadow="never" class="search-wrapper">
|
||||||
|
<el-form :model="queryParams" :inline="true">
|
||||||
|
<el-form-item label="年级名称">
|
||||||
|
<el-input v-model="queryParams.gradeName" placeholder="请输入年级名称" clearable style="width: 200px" @keyup.enter="handleQuery" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="状态">
|
||||||
|
<el-select v-model="queryParams.status" placeholder="全部" clearable style="width: 100px">
|
||||||
|
<el-option label="正常" value="0" />
|
||||||
|
<el-option label="停用" value="1" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" :icon="Search" @click="handleQuery">搜索</el-button>
|
||||||
|
<el-button :icon="Refresh" @click="resetQuery">重置</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<!-- 表格区域 -->
|
||||||
|
<el-card shadow="never" style="margin-top: 12px">
|
||||||
|
<el-row :gutter="10" style="margin-bottom: 12px">
|
||||||
|
<el-col :span="1.5">
|
||||||
|
<el-button type="primary" :icon="Plus" @click="handleAdd">新增</el-button>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<el-table v-loading="loading" :data="tableData" border stripe :header-cell-style="{ background: '#f5f7fa', color: '#606266' }" style="width: 100%">
|
||||||
|
<el-table-column prop="name" label="年级名称" min-width="150" />
|
||||||
|
<el-table-column prop="code" label="年级编码" width="120" />
|
||||||
|
<el-table-column prop="sort" label="排序" width="80" align="center" />
|
||||||
|
<el-table-column prop="status" label="状态" width="80" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag :type="row.status === '0' ? 'success' : 'danger'">
|
||||||
|
{{ row.status === '0' ? '正常' : '停用' }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="createTime" label="创建时间" width="160" />
|
||||||
|
<el-table-column label="操作" width="150" fixed="right" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-button type="primary" link :icon="Edit" @click="handleEdit(row)">编辑</el-button>
|
||||||
|
<el-button type="danger" link :icon="Delete" @click="handleDelete(row)">删除</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<!-- 分页 -->
|
||||||
|
<el-pagination
|
||||||
|
v-model:current-page="queryParams.pageNum"
|
||||||
|
v-model:page-size="queryParams.pageSize"
|
||||||
|
:page-sizes="[10, 20, 50, 100]"
|
||||||
|
:total="total"
|
||||||
|
layout="total, sizes, prev, pager, next, jumper"
|
||||||
|
style="margin-top: 16px; justify-content: flex-end"
|
||||||
|
@size-change="handleQuery"
|
||||||
|
@current-change="handleQuery"
|
||||||
|
/>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<!-- 新增/编辑弹窗 -->
|
||||||
|
<el-dialog v-model="dialogVisible" :title="dialogTitle" width="500px" :close-on-click-modal="false" destroy-on-close>
|
||||||
|
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
|
||||||
|
<el-form-item label="年级名称" prop="name">
|
||||||
|
<el-input v-model="form.name" placeholder="请输入年级名称" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="年级编码" prop="code">
|
||||||
|
<el-input v-model="form.code" placeholder="自动生成" disabled />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="排序" prop="sort">
|
||||||
|
<el-input-number v-model="form.sort" :min="0" :max="999" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="状态" prop="status">
|
||||||
|
<el-switch v-model="form.status" active-value="0" inactive-value="1" active-text="正常" inactive-text="停用" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="dialogVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="handleSubmit">确定</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
/**
|
||||||
|
* 年级管理页面
|
||||||
|
* @author pangu
|
||||||
|
*/
|
||||||
|
import { Delete, Edit, Plus, Refresh, Search } from '@element-plus/icons-vue'
|
||||||
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
|
import { onMounted, ref } from 'vue'
|
||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
const tableData = ref([])
|
||||||
|
const total = ref(0)
|
||||||
|
|
||||||
|
const queryParams = ref({
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
gradeName: '',
|
||||||
|
status: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
// 弹窗相关
|
||||||
|
const dialogVisible = ref(false)
|
||||||
|
const dialogTitle = ref('')
|
||||||
|
const formRef = ref()
|
||||||
|
const form = ref({
|
||||||
|
id: null,
|
||||||
|
name: '',
|
||||||
|
code: '',
|
||||||
|
sort: 0,
|
||||||
|
status: '0'
|
||||||
|
})
|
||||||
|
|
||||||
|
const rules = {
|
||||||
|
name: [{ required: true, message: '请输入年级名称', trigger: 'blur' }]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取年级列表
|
||||||
|
const getList = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const res = await request.get('/business/grade/list', { params: queryParams.value })
|
||||||
|
if (res.code === 200) {
|
||||||
|
tableData.value = res.rows
|
||||||
|
total.value = res.total
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 搜索
|
||||||
|
const handleQuery = () => {
|
||||||
|
queryParams.value.pageNum = 1
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置
|
||||||
|
const resetQuery = () => {
|
||||||
|
queryParams.value = { pageNum: 1, pageSize: 10, gradeName: '', status: '' }
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增
|
||||||
|
const handleAdd = () => {
|
||||||
|
dialogTitle.value = '新增年级'
|
||||||
|
form.value = { id: null, name: '', code: '', sort: 0, status: '0' }
|
||||||
|
dialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 编辑
|
||||||
|
const handleEdit = (row) => {
|
||||||
|
dialogTitle.value = '编辑年级'
|
||||||
|
form.value = { ...row }
|
||||||
|
dialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
await formRef.value?.validate()
|
||||||
|
const isEdit = !!form.value.id
|
||||||
|
const res = isEdit
|
||||||
|
? await request.put('/business/grade', form.value)
|
||||||
|
: await request.post('/business/grade', form.value)
|
||||||
|
if (res.code === 200) {
|
||||||
|
ElMessage.success(isEdit ? '修改成功' : '新增成功')
|
||||||
|
dialogVisible.value = false
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除
|
||||||
|
const handleDelete = (row) => {
|
||||||
|
ElMessageBox.confirm(`确定要删除年级"${row.name}"吗?`, '提示', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
}).then(async () => {
|
||||||
|
const res = await request.delete(`/api/grade/${row.id}`)
|
||||||
|
if (res.code === 200) {
|
||||||
|
ElMessage.success('删除成功')
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
}).catch(() => {})
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getList()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.app-container {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
.search-wrapper {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,229 @@
|
||||||
|
<template>
|
||||||
|
<div class="app-container">
|
||||||
|
<!-- 搜索区域 -->
|
||||||
|
<el-card shadow="never" class="search-wrapper">
|
||||||
|
<el-form :model="queryParams" :inline="true">
|
||||||
|
<el-form-item label="区域名称">
|
||||||
|
<el-input v-model="queryParams.regionName" placeholder="请输入区域名称" clearable style="width: 200px" @keyup.enter="handleQuery" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="状态">
|
||||||
|
<el-select v-model="queryParams.status" placeholder="全部" clearable style="width: 100px">
|
||||||
|
<el-option label="正常" value="0" />
|
||||||
|
<el-option label="停用" value="1" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" :icon="Search" @click="handleQuery">搜索</el-button>
|
||||||
|
<el-button :icon="Refresh" @click="resetQuery">重置</el-button>
|
||||||
|
<el-button type="info" :icon="Sort" @click="toggleExpand">{{ isExpand ? '折叠' : '展开' }}</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<!-- 表格区域 -->
|
||||||
|
<el-card shadow="never" style="margin-top: 12px">
|
||||||
|
<el-row :gutter="10" style="margin-bottom: 12px">
|
||||||
|
<el-col :span="1.5">
|
||||||
|
<el-button type="primary" :icon="Plus" @click="handleAdd()">新增</el-button>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<el-table
|
||||||
|
v-loading="loading"
|
||||||
|
:data="tableData"
|
||||||
|
row-key="id"
|
||||||
|
border
|
||||||
|
:default-expand-all="isExpand"
|
||||||
|
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
|
||||||
|
:header-cell-style="{ background: '#f5f7fa', color: '#606266' }"
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
|
<el-table-column prop="regionName" label="区域名称" min-width="200" />
|
||||||
|
<el-table-column prop="regionCode" label="区域编码" width="120" />
|
||||||
|
<el-table-column prop="sort" label="排序" width="80" align="center" />
|
||||||
|
<el-table-column prop="status" label="状态" width="80" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag :type="row.status === '0' ? 'success' : 'danger'">
|
||||||
|
{{ row.status === '0' ? '正常' : '停用' }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="createTime" label="创建时间" width="160" />
|
||||||
|
<el-table-column label="操作" width="200" fixed="right" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-button type="primary" link :icon="Plus" @click="handleAdd(row)">新增</el-button>
|
||||||
|
<el-button type="primary" link :icon="Edit" @click="handleEdit(row)">编辑</el-button>
|
||||||
|
<el-button type="danger" link :icon="Delete" @click="handleDelete(row)">删除</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<!-- 新增/编辑弹窗 -->
|
||||||
|
<el-dialog v-model="dialogVisible" :title="dialogTitle" width="500px" :close-on-click-modal="false" destroy-on-close>
|
||||||
|
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
|
||||||
|
<el-form-item label="上级区域" prop="parentId">
|
||||||
|
<el-tree-select
|
||||||
|
v-model="form.parentId"
|
||||||
|
:data="regionTreeOptions"
|
||||||
|
:props="{ value: 'id', label: 'regionName', children: 'children' }"
|
||||||
|
check-strictly
|
||||||
|
placeholder="请选择上级区域"
|
||||||
|
clearable
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="区域名称" prop="regionName">
|
||||||
|
<el-input v-model="form.regionName" placeholder="请输入区域名称" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="区域编码" prop="regionCode">
|
||||||
|
<el-input v-model="form.regionCode" placeholder="请输入区域编码" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="排序" prop="sort">
|
||||||
|
<el-input-number v-model="form.sort" :min="0" :max="999" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="状态" prop="status">
|
||||||
|
<el-switch v-model="form.status" active-value="0" inactive-value="1" active-text="正常" inactive-text="停用" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="dialogVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="handleSubmit">确定</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
/**
|
||||||
|
* 区域管理页面
|
||||||
|
* @author pangu
|
||||||
|
*/
|
||||||
|
import { Delete, Edit, Plus, Refresh, Search, Sort } from '@element-plus/icons-vue'
|
||||||
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
|
import { onMounted, ref } from 'vue'
|
||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
const tableData = ref([])
|
||||||
|
const isExpand = ref(true)
|
||||||
|
const regionTreeOptions = ref([])
|
||||||
|
|
||||||
|
const queryParams = ref({
|
||||||
|
regionName: '',
|
||||||
|
status: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
// 弹窗相关
|
||||||
|
const dialogVisible = ref(false)
|
||||||
|
const dialogTitle = ref('')
|
||||||
|
const formRef = ref()
|
||||||
|
const form = ref({
|
||||||
|
id: null,
|
||||||
|
parentId: null,
|
||||||
|
regionName: '',
|
||||||
|
regionCode: '',
|
||||||
|
sort: 0,
|
||||||
|
status: '0'
|
||||||
|
})
|
||||||
|
|
||||||
|
const rules = {
|
||||||
|
regionName: [{ required: true, message: '请输入区域名称', trigger: 'blur' }],
|
||||||
|
regionCode: [{ required: true, message: '请输入区域编码', trigger: 'blur' }]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取区域列表
|
||||||
|
const getList = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const res = await request.get('/business/region/list', { params: queryParams.value })
|
||||||
|
if (res.code === 200) {
|
||||||
|
tableData.value = res.data
|
||||||
|
// 构建树形选择器的数据
|
||||||
|
regionTreeOptions.value = [{ id: 0, regionName: '根节点', children: res.data }]
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 搜索
|
||||||
|
const handleQuery = () => {
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置
|
||||||
|
const resetQuery = () => {
|
||||||
|
queryParams.value = { regionName: '', status: '' }
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 展开/折叠
|
||||||
|
const toggleExpand = () => {
|
||||||
|
isExpand.value = !isExpand.value
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增
|
||||||
|
const handleAdd = (row) => {
|
||||||
|
dialogTitle.value = '新增区域'
|
||||||
|
form.value = {
|
||||||
|
id: null,
|
||||||
|
parentId: row ? row.id : 0,
|
||||||
|
regionName: '',
|
||||||
|
regionCode: '',
|
||||||
|
sort: 0,
|
||||||
|
status: '0'
|
||||||
|
}
|
||||||
|
dialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 编辑
|
||||||
|
const handleEdit = (row) => {
|
||||||
|
dialogTitle.value = '编辑区域'
|
||||||
|
form.value = { ...row }
|
||||||
|
dialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
await formRef.value?.validate()
|
||||||
|
const isEdit = !!form.value.id
|
||||||
|
const res = isEdit
|
||||||
|
? await request.put('/business/region', form.value)
|
||||||
|
: await request.post('/business/region', form.value)
|
||||||
|
if (res.code === 200) {
|
||||||
|
ElMessage.success(isEdit ? '修改成功' : '新增成功')
|
||||||
|
dialogVisible.value = false
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除
|
||||||
|
const handleDelete = (row) => {
|
||||||
|
ElMessageBox.confirm(`确定要删除区域"${row.regionName}"吗?删除后子区域也将被删除。`, '提示', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
}).then(async () => {
|
||||||
|
const res = await request.delete(`/api/region/${row.id}`)
|
||||||
|
if (res.code === 200) {
|
||||||
|
ElMessage.success('删除成功')
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
}).catch(() => {})
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getList()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.app-container {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
.search-wrapper {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,205 @@
|
||||||
|
<template>
|
||||||
|
<div class="app-container">
|
||||||
|
<!-- 搜索区域 -->
|
||||||
|
<el-card shadow="never" class="search-wrapper">
|
||||||
|
<el-form :model="queryParams" :inline="true">
|
||||||
|
<el-form-item label="学科名称">
|
||||||
|
<el-input v-model="queryParams.subjectName" placeholder="请输入学科名称" clearable style="width: 200px" @keyup.enter="handleQuery" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="状态">
|
||||||
|
<el-select v-model="queryParams.status" placeholder="全部" clearable style="width: 100px">
|
||||||
|
<el-option label="正常" value="0" />
|
||||||
|
<el-option label="停用" value="1" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" :icon="Search" @click="handleQuery">搜索</el-button>
|
||||||
|
<el-button :icon="Refresh" @click="resetQuery">重置</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<!-- 表格区域 -->
|
||||||
|
<el-card shadow="never" style="margin-top: 12px">
|
||||||
|
<el-row :gutter="10" style="margin-bottom: 12px">
|
||||||
|
<el-col :span="1.5">
|
||||||
|
<el-button type="primary" :icon="Plus" @click="handleAdd">新增</el-button>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<el-table v-loading="loading" :data="tableData" border stripe :header-cell-style="{ background: '#f5f7fa', color: '#606266' }" style="width: 100%">
|
||||||
|
<el-table-column prop="subjectName" label="学科名称" min-width="150" />
|
||||||
|
<el-table-column prop="subjectCode" label="学科编码" width="120" />
|
||||||
|
<el-table-column prop="sort" label="排序" width="80" align="center" />
|
||||||
|
<el-table-column prop="status" label="状态" width="80" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag :type="row.status === '0' ? 'success' : 'danger'">
|
||||||
|
{{ row.status === '0' ? '正常' : '停用' }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="createTime" label="创建时间" width="160" />
|
||||||
|
<el-table-column label="操作" width="150" fixed="right" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-button type="primary" link :icon="Edit" @click="handleEdit(row)">编辑</el-button>
|
||||||
|
<el-button type="danger" link :icon="Delete" @click="handleDelete(row)">删除</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<!-- 分页 -->
|
||||||
|
<el-pagination
|
||||||
|
v-model:current-page="queryParams.pageNum"
|
||||||
|
v-model:page-size="queryParams.pageSize"
|
||||||
|
:page-sizes="[10, 20, 50, 100]"
|
||||||
|
:total="total"
|
||||||
|
layout="total, sizes, prev, pager, next, jumper"
|
||||||
|
style="margin-top: 16px; justify-content: flex-end"
|
||||||
|
@size-change="handleQuery"
|
||||||
|
@current-change="handleQuery"
|
||||||
|
/>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<!-- 新增/编辑弹窗 -->
|
||||||
|
<el-dialog v-model="dialogVisible" :title="dialogTitle" width="500px" :close-on-click-modal="false" destroy-on-close>
|
||||||
|
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
|
||||||
|
<el-form-item label="学科名称" prop="subjectName">
|
||||||
|
<el-input v-model="form.subjectName" placeholder="请输入学科名称" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="学科编码" prop="subjectCode">
|
||||||
|
<el-input v-model="form.subjectCode" placeholder="自动生成" disabled />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="排序" prop="sort">
|
||||||
|
<el-input-number v-model="form.sort" :min="0" :max="999" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="状态" prop="status">
|
||||||
|
<el-switch v-model="form.status" active-value="0" inactive-value="1" active-text="正常" inactive-text="停用" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="dialogVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="handleSubmit">确定</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
/**
|
||||||
|
* 学科管理页面
|
||||||
|
* @author pangu
|
||||||
|
*/
|
||||||
|
import { Delete, Edit, Plus, Refresh, Search } from '@element-plus/icons-vue'
|
||||||
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
|
import { onMounted, ref } from 'vue'
|
||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
const tableData = ref([])
|
||||||
|
const total = ref(0)
|
||||||
|
|
||||||
|
const queryParams = ref({
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
subjectName: '',
|
||||||
|
status: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
// 弹窗相关
|
||||||
|
const dialogVisible = ref(false)
|
||||||
|
const dialogTitle = ref('')
|
||||||
|
const formRef = ref()
|
||||||
|
const form = ref({
|
||||||
|
id: null,
|
||||||
|
subjectName: '',
|
||||||
|
subjectCode: '',
|
||||||
|
sort: 0,
|
||||||
|
status: '0'
|
||||||
|
})
|
||||||
|
|
||||||
|
const rules = {
|
||||||
|
subjectName: [{ required: true, message: '请输入学科名称', trigger: 'blur' }]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取学科列表
|
||||||
|
const getList = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const res = await request.get('/business/subject/list', { params: queryParams.value })
|
||||||
|
if (res.code === 200) {
|
||||||
|
tableData.value = res.rows
|
||||||
|
total.value = res.total
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 搜索
|
||||||
|
const handleQuery = () => {
|
||||||
|
queryParams.value.pageNum = 1
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置
|
||||||
|
const resetQuery = () => {
|
||||||
|
queryParams.value = { pageNum: 1, pageSize: 10, subjectName: '', status: '' }
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增
|
||||||
|
const handleAdd = () => {
|
||||||
|
dialogTitle.value = '新增学科'
|
||||||
|
form.value = { id: null, subjectName: '', subjectCode: 'S' + Date.now(), sort: 0, status: '0' }
|
||||||
|
dialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 编辑
|
||||||
|
const handleEdit = (row) => {
|
||||||
|
dialogTitle.value = '编辑学科'
|
||||||
|
form.value = { ...row }
|
||||||
|
dialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
await formRef.value?.validate()
|
||||||
|
const isEdit = !!form.value.id
|
||||||
|
const res = isEdit
|
||||||
|
? await request.put('/business/subject', form.value)
|
||||||
|
: await request.post('/business/subject', form.value)
|
||||||
|
if (res.code === 200) {
|
||||||
|
ElMessage.success(isEdit ? '修改成功' : '新增成功')
|
||||||
|
dialogVisible.value = false
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除
|
||||||
|
const handleDelete = (row) => {
|
||||||
|
ElMessageBox.confirm(`确定要删除学科"${row.subjectName}"吗?`, '提示', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
}).then(async () => {
|
||||||
|
const res = await request.delete(`/api/subject/${row.id}`)
|
||||||
|
if (res.code === 200) {
|
||||||
|
ElMessage.success('删除成功')
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
}).catch(() => {})
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getList()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.app-container {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
.search-wrapper {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,383 @@
|
||||||
|
<!--
|
||||||
|
会员编辑弹窗
|
||||||
|
@author pangu
|
||||||
|
-->
|
||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
v-model="visible"
|
||||||
|
:title="memberId ? '编辑会员' : '新增会员'"
|
||||||
|
width="700px"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
destroy-on-close
|
||||||
|
@open="handleOpen"
|
||||||
|
>
|
||||||
|
<el-form
|
||||||
|
ref="formRef"
|
||||||
|
:model="form"
|
||||||
|
:rules="rules"
|
||||||
|
label-width="100px"
|
||||||
|
>
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="手机号" prop="phone">
|
||||||
|
<el-input v-model="form.phone" placeholder="请输入手机号" maxlength="11" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="昵称" prop="nickname">
|
||||||
|
<el-input v-model="form.nickname" placeholder="请输入昵称" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="性别" prop="gender">
|
||||||
|
<el-radio-group v-model="form.gender">
|
||||||
|
<el-radio value="1">男</el-radio>
|
||||||
|
<el-radio value="2">女</el-radio>
|
||||||
|
<el-radio value="0">未知</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="出生日期" prop="birthday">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="form.birthday"
|
||||||
|
type="date"
|
||||||
|
placeholder="请选择"
|
||||||
|
value-format="YYYY-MM-DD"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="身份类型" prop="identityType">
|
||||||
|
<el-radio-group v-model="form.identityType">
|
||||||
|
<el-radio value="1">家长</el-radio>
|
||||||
|
<el-radio value="2">教师</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="状态" prop="status">
|
||||||
|
<el-switch
|
||||||
|
v-model="form.status"
|
||||||
|
active-value="0"
|
||||||
|
inactive-value="1"
|
||||||
|
active-text="正常"
|
||||||
|
inactive-text="停用"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<!-- 教师身份时显示学校信息 -->
|
||||||
|
<template v-if="form.identityType === '2'">
|
||||||
|
<el-divider content-position="left">学校信息</el-divider>
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="区域" prop="regionId">
|
||||||
|
<el-cascader
|
||||||
|
v-model="form.regionIds"
|
||||||
|
:options="regionTree"
|
||||||
|
:props="{ value: 'id', label: 'label', checkStrictly: true }"
|
||||||
|
placeholder="请选择区域"
|
||||||
|
clearable
|
||||||
|
style="width: 100%"
|
||||||
|
@change="handleRegionChange"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="学校" prop="schoolId">
|
||||||
|
<el-select v-model="form.schoolId" placeholder="请选择学校" clearable style="width: 100%" @change="handleSchoolChange">
|
||||||
|
<el-option v-for="item in schoolList" :key="item.id" :label="item.name" :value="item.id" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="年级" prop="gradeId">
|
||||||
|
<el-select v-model="form.gradeId" placeholder="请选择年级" clearable style="width: 100%" @change="handleGradeChange">
|
||||||
|
<el-option v-for="item in gradeList" :key="item.id" :label="item.name" :value="item.id" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="班级" prop="classId">
|
||||||
|
<el-select v-model="form.classId" placeholder="请选择班级" clearable style="width: 100%">
|
||||||
|
<el-option v-for="item in classList" :key="item.id" :label="item.name" :value="item.id" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 绑定学生 -->
|
||||||
|
<el-divider content-position="left">绑定学生</el-divider>
|
||||||
|
<el-row style="margin-bottom: 12px;">
|
||||||
|
<el-button type="primary" size="small" :icon="Plus" @click="handleAddStudent">添加学生</el-button>
|
||||||
|
</el-row>
|
||||||
|
<el-table :data="form.students" border size="small" max-height="200">
|
||||||
|
<template #empty>
|
||||||
|
<el-empty description="暂无绑定学生" :image-size="60" />
|
||||||
|
</template>
|
||||||
|
<el-table-column prop="name" label="姓名" min-width="80" />
|
||||||
|
<el-table-column prop="studentNo" label="学号" width="120" />
|
||||||
|
<el-table-column prop="schoolName" label="学校" min-width="120" show-overflow-tooltip />
|
||||||
|
<el-table-column prop="gradeName" label="年级" width="80" />
|
||||||
|
<el-table-column prop="className" label="班级" width="60" />
|
||||||
|
<el-table-column label="操作" width="80" align="center">
|
||||||
|
<template #default="{ $index }">
|
||||||
|
<el-button link type="danger" size="small" @click="handleRemoveStudent($index)">移除</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="visible = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="handleSubmit" :loading="submitLoading">确定</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { addMember, getClassList, getGradeList, getMember, getRegionTree, getSchoolList, updateMember } from '@/api/pangu/member'
|
||||||
|
import { Plus } from '@element-plus/icons-vue'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import { computed, reactive, ref, watch } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
memberId: {
|
||||||
|
type: [Number, null],
|
||||||
|
default: null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue', 'success'])
|
||||||
|
|
||||||
|
const visible = computed({
|
||||||
|
get: () => props.modelValue,
|
||||||
|
set: (val) => emit('update:modelValue', val)
|
||||||
|
})
|
||||||
|
|
||||||
|
const formRef = ref(null)
|
||||||
|
const submitLoading = ref(false)
|
||||||
|
|
||||||
|
// 表单数据
|
||||||
|
const form = reactive({
|
||||||
|
phone: '',
|
||||||
|
nickname: '',
|
||||||
|
gender: '0',
|
||||||
|
birthday: '',
|
||||||
|
identityType: '1',
|
||||||
|
status: '0',
|
||||||
|
regionIds: [],
|
||||||
|
regionId: null,
|
||||||
|
schoolId: null,
|
||||||
|
gradeId: null,
|
||||||
|
classId: null,
|
||||||
|
students: []
|
||||||
|
})
|
||||||
|
|
||||||
|
// 表单验证规则
|
||||||
|
const rules = {
|
||||||
|
phone: [
|
||||||
|
{ required: true, message: '请输入手机号', trigger: 'blur' },
|
||||||
|
{ pattern: /^1[3-9]\d{9}$/, message: '手机号格式不正确', trigger: 'blur' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 下拉选项数据
|
||||||
|
const regionTree = ref([])
|
||||||
|
const schoolList = ref([])
|
||||||
|
const gradeList = ref([])
|
||||||
|
const classList = ref([])
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 弹窗打开时加载数据
|
||||||
|
*/
|
||||||
|
const handleOpen = async () => {
|
||||||
|
// 加载区域树
|
||||||
|
try {
|
||||||
|
const res = await getRegionTree()
|
||||||
|
regionTree.value = res.data || []
|
||||||
|
} catch (e) {
|
||||||
|
// 忽略错误
|
||||||
|
}
|
||||||
|
|
||||||
|
// 编辑模式加载会员数据
|
||||||
|
if (props.memberId) {
|
||||||
|
try {
|
||||||
|
const res = await getMember(props.memberId)
|
||||||
|
if (res.data) {
|
||||||
|
Object.assign(form, res.data)
|
||||||
|
// 加载关联数据
|
||||||
|
if (form.regionId) {
|
||||||
|
await loadSchoolList(form.regionId)
|
||||||
|
}
|
||||||
|
if (form.schoolId) {
|
||||||
|
await loadGradeList(form.schoolId)
|
||||||
|
}
|
||||||
|
if (form.gradeId) {
|
||||||
|
await loadClassList(form.gradeId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// 忽略错误
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
resetForm()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重置表单
|
||||||
|
*/
|
||||||
|
const resetForm = () => {
|
||||||
|
form.phone = ''
|
||||||
|
form.nickname = ''
|
||||||
|
form.gender = '0'
|
||||||
|
form.birthday = ''
|
||||||
|
form.identityType = '1'
|
||||||
|
form.status = '0'
|
||||||
|
form.regionIds = []
|
||||||
|
form.regionId = null
|
||||||
|
form.schoolId = null
|
||||||
|
form.gradeId = null
|
||||||
|
form.classId = null
|
||||||
|
form.students = []
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载学校列表
|
||||||
|
*/
|
||||||
|
const loadSchoolList = async (regionId) => {
|
||||||
|
try {
|
||||||
|
const res = await getSchoolList(regionId)
|
||||||
|
schoolList.value = res.data || []
|
||||||
|
} catch (e) {
|
||||||
|
schoolList.value = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载年级列表
|
||||||
|
*/
|
||||||
|
const loadGradeList = async (schoolId) => {
|
||||||
|
try {
|
||||||
|
const res = await getGradeList(schoolId)
|
||||||
|
gradeList.value = res.data || []
|
||||||
|
} catch (e) {
|
||||||
|
gradeList.value = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载班级列表
|
||||||
|
*/
|
||||||
|
const loadClassList = async (gradeId) => {
|
||||||
|
try {
|
||||||
|
const res = await getClassList(gradeId)
|
||||||
|
classList.value = res.data || []
|
||||||
|
} catch (e) {
|
||||||
|
classList.value = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 区域变更
|
||||||
|
*/
|
||||||
|
const handleRegionChange = (val) => {
|
||||||
|
form.regionId = val && val.length ? val[val.length - 1] : null
|
||||||
|
form.schoolId = null
|
||||||
|
form.gradeId = null
|
||||||
|
form.classId = null
|
||||||
|
schoolList.value = []
|
||||||
|
gradeList.value = []
|
||||||
|
classList.value = []
|
||||||
|
if (form.regionId) {
|
||||||
|
loadSchoolList(form.regionId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 学校变更
|
||||||
|
*/
|
||||||
|
const handleSchoolChange = () => {
|
||||||
|
form.gradeId = null
|
||||||
|
form.classId = null
|
||||||
|
gradeList.value = []
|
||||||
|
classList.value = []
|
||||||
|
if (form.schoolId) {
|
||||||
|
loadGradeList(form.schoolId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 年级变更
|
||||||
|
*/
|
||||||
|
const handleGradeChange = () => {
|
||||||
|
form.classId = null
|
||||||
|
classList.value = []
|
||||||
|
if (form.gradeId) {
|
||||||
|
loadClassList(form.gradeId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加学生(模拟,实际应该弹出学生选择器)
|
||||||
|
*/
|
||||||
|
const handleAddStudent = () => {
|
||||||
|
ElMessage.info('请选择要绑定的学生')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移除学生
|
||||||
|
*/
|
||||||
|
const handleRemoveStudent = (index) => {
|
||||||
|
form.students.splice(index, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提交表单
|
||||||
|
*/
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
try {
|
||||||
|
await formRef.value?.validate()
|
||||||
|
} catch (e) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
submitLoading.value = true
|
||||||
|
try {
|
||||||
|
if (props.memberId) {
|
||||||
|
await updateMember({ ...form, id: props.memberId })
|
||||||
|
ElMessage.success('修改成功')
|
||||||
|
} else {
|
||||||
|
await addMember(form)
|
||||||
|
ElMessage.success('新增成功')
|
||||||
|
}
|
||||||
|
visible.value = false
|
||||||
|
emit('success')
|
||||||
|
} finally {
|
||||||
|
submitLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听弹窗关闭,重置表单
|
||||||
|
watch(visible, (val) => {
|
||||||
|
if (!val) {
|
||||||
|
formRef.value?.resetFields()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,233 @@
|
||||||
|
<template>
|
||||||
|
<div class="app-container">
|
||||||
|
<!-- 搜索区域 -->
|
||||||
|
<el-card shadow="never" class="search-wrapper">
|
||||||
|
<el-form :model="queryParams" :inline="true">
|
||||||
|
<el-form-item label="手机号">
|
||||||
|
<el-input v-model="queryParams.phone" placeholder="请输入手机号" clearable style="width: 150px" @keyup.enter="handleQuery" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="昵称">
|
||||||
|
<el-input v-model="queryParams.nickname" placeholder="请输入昵称" clearable style="width: 150px" @keyup.enter="handleQuery" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="身份类型">
|
||||||
|
<el-select v-model="queryParams.identityType" placeholder="全部" clearable style="width: 120px">
|
||||||
|
<el-option label="家长" value="1" />
|
||||||
|
<el-option label="教师" value="2" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="状态">
|
||||||
|
<el-select v-model="queryParams.status" placeholder="全部" clearable style="width: 100px">
|
||||||
|
<el-option label="正常" value="0" />
|
||||||
|
<el-option label="停用" value="1" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="注册时间">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="dateRange"
|
||||||
|
type="daterange"
|
||||||
|
range-separator="-"
|
||||||
|
start-placeholder="开始日期"
|
||||||
|
end-placeholder="结束日期"
|
||||||
|
value-format="YYYY-MM-DD"
|
||||||
|
style="width: 240px"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" :icon="Search" @click="handleQuery">搜索</el-button>
|
||||||
|
<el-button :icon="Refresh" @click="resetQuery">重置</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<!-- 表格区域 -->
|
||||||
|
<el-card shadow="never" style="margin-top: 12px">
|
||||||
|
<el-row :gutter="10" style="margin-bottom: 12px">
|
||||||
|
<el-col :span="1.5">
|
||||||
|
<el-button type="primary" :icon="Plus" @click="handleAdd">新增</el-button>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<el-table v-loading="loading" :data="tableData" border stripe :header-cell-style="{ background: '#f5f7fa', color: '#606266' }" style="width: 100%">
|
||||||
|
<el-table-column prop="memberNo" label="会员编号" width="140" />
|
||||||
|
<el-table-column prop="phone" label="手机号" width="120">
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ maskPhone(row.phone) }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="nickname" label="昵称" min-width="100" show-overflow-tooltip />
|
||||||
|
<el-table-column prop="gender" label="性别" width="60" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ row.gender === '1' ? '男' : row.gender === '2' ? '女' : '未知' }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="birthday" label="出生日期" width="100" />
|
||||||
|
<el-table-column prop="identityType" label="身份类型" width="80" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag :type="row.identityType === '1' ? 'success' : 'warning'">
|
||||||
|
{{ row.identityType === '1' ? '家长' : '教师' }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="registerTime" label="注册时间" width="160" />
|
||||||
|
<el-table-column prop="registerSource" label="注册来源" width="80" align="center" />
|
||||||
|
<el-table-column prop="status" label="状态" width="70" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag :type="row.status === '0' ? 'success' : 'danger'">
|
||||||
|
{{ row.status === '0' ? '正常' : '停用' }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="操作" width="180" fixed="right" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-button type="primary" link :icon="Edit" @click="handleEdit(row)">编辑</el-button>
|
||||||
|
<el-button type="warning" link :icon="Key" @click="handleResetPwd(row)">重置密码</el-button>
|
||||||
|
<el-button type="danger" link :icon="Delete" @click="handleDelete(row)">删除</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<!-- 分页 -->
|
||||||
|
<el-pagination
|
||||||
|
v-model:current-page="queryParams.pageNum"
|
||||||
|
v-model:page-size="queryParams.pageSize"
|
||||||
|
:page-sizes="[10, 20, 50, 100]"
|
||||||
|
:total="total"
|
||||||
|
layout="total, sizes, prev, pager, next, jumper"
|
||||||
|
style="margin-top: 16px; justify-content: flex-end"
|
||||||
|
@size-change="handleQuery"
|
||||||
|
@current-change="handleQuery"
|
||||||
|
/>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<!-- 新增/编辑弹窗 -->
|
||||||
|
<MemberDialog ref="memberDialogRef" @success="handleQuery" />
|
||||||
|
|
||||||
|
<!-- 重置密码弹窗 -->
|
||||||
|
<ResetPwdDialog ref="resetPwdDialogRef" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
/**
|
||||||
|
* 会员管理页面
|
||||||
|
* @author pangu
|
||||||
|
*/
|
||||||
|
import { Delete, Edit, Key, Plus, Refresh, Search } from '@element-plus/icons-vue'
|
||||||
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
|
import { onMounted, ref } from 'vue'
|
||||||
|
import request from '@/utils/request'
|
||||||
|
import MemberDialog from './components/MemberDialog.vue'
|
||||||
|
import ResetPwdDialog from './components/ResetPwdDialog.vue'
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
const tableData = ref([])
|
||||||
|
const total = ref(0)
|
||||||
|
const dateRange = ref([])
|
||||||
|
|
||||||
|
const queryParams = ref({
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
phone: '',
|
||||||
|
nickname: '',
|
||||||
|
identityType: '',
|
||||||
|
status: '',
|
||||||
|
beginTime: '',
|
||||||
|
endTime: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const memberDialogRef = ref()
|
||||||
|
const resetPwdDialogRef = ref()
|
||||||
|
|
||||||
|
// 手机号脱敏
|
||||||
|
const maskPhone = (phone) => {
|
||||||
|
if (!phone || phone.length !== 11) return phone
|
||||||
|
return phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取会员列表
|
||||||
|
const getList = async () => {
|
||||||
|
loading.value = true
|
||||||
|
// 处理日期范围
|
||||||
|
if (dateRange.value && dateRange.value.length === 2) {
|
||||||
|
queryParams.value.beginTime = dateRange.value[0]
|
||||||
|
queryParams.value.endTime = dateRange.value[1]
|
||||||
|
} else {
|
||||||
|
queryParams.value.beginTime = ''
|
||||||
|
queryParams.value.endTime = ''
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const res = await request.get('/business/member/list', { params: queryParams.value })
|
||||||
|
if (res.code === 200) {
|
||||||
|
tableData.value = res.rows
|
||||||
|
total.value = res.total
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 搜索
|
||||||
|
const handleQuery = () => {
|
||||||
|
queryParams.value.pageNum = 1
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置
|
||||||
|
const resetQuery = () => {
|
||||||
|
queryParams.value = {
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
phone: '',
|
||||||
|
nickname: '',
|
||||||
|
identityType: '',
|
||||||
|
status: '',
|
||||||
|
beginTime: '',
|
||||||
|
endTime: ''
|
||||||
|
}
|
||||||
|
dateRange.value = []
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增
|
||||||
|
const handleAdd = () => {
|
||||||
|
memberDialogRef.value?.open()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 编辑
|
||||||
|
const handleEdit = (row) => {
|
||||||
|
memberDialogRef.value?.open(row)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置密码
|
||||||
|
const handleResetPwd = (row) => {
|
||||||
|
resetPwdDialogRef.value?.open(row)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除
|
||||||
|
const handleDelete = (row) => {
|
||||||
|
ElMessageBox.confirm(`确定要删除会员"${row.nickname}"吗?`, '提示', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
}).then(async () => {
|
||||||
|
const res = await request.delete(`/api/member/${row.id}`)
|
||||||
|
if (res.code === 200) {
|
||||||
|
ElMessage.success('删除成功')
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
}).catch(() => {})
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getList()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.app-container {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
.search-wrapper {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,140 @@
|
||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
v-model="dialogVisible"
|
||||||
|
title="新增班级"
|
||||||
|
width="500px"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
destroy-on-close
|
||||||
|
>
|
||||||
|
<div style="margin-bottom: 16px;">
|
||||||
|
<span style="font-weight: bold;">学校:</span>
|
||||||
|
<span>{{ currentSchool?.schoolName }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-form ref="formRef" :model="form" label-width="80px">
|
||||||
|
<el-form-item label="选择年级" prop="gradeId">
|
||||||
|
<el-select v-model="form.gradeId" placeholder="请先选择年级" style="width: 100%">
|
||||||
|
<el-option
|
||||||
|
v-for="item in gradeOptions"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="选择班级" prop="classIds">
|
||||||
|
<el-checkbox-group v-model="form.classIds">
|
||||||
|
<el-row :gutter="10">
|
||||||
|
<el-col :span="8" v-for="item in classOptions" :key="item.value">
|
||||||
|
<el-checkbox :label="item.value" style="margin-bottom: 8px;">
|
||||||
|
{{ item.label }}
|
||||||
|
</el-checkbox>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-checkbox-group>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="dialogVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="handleSubmit" :loading="submitLoading">确定</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
/**
|
||||||
|
* 新增班级弹窗
|
||||||
|
* @author pangu
|
||||||
|
*/
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import { onMounted, ref } from 'vue'
|
||||||
|
import { addGradeClass, getClassOptions, getGradeOptions } from '@/api/pangu/school'
|
||||||
|
|
||||||
|
const emit = defineEmits(['success'])
|
||||||
|
|
||||||
|
const dialogVisible = ref(false)
|
||||||
|
const submitLoading = ref(false)
|
||||||
|
const formRef = ref(null)
|
||||||
|
const currentSchool = ref(null)
|
||||||
|
const gradeOptions = ref([])
|
||||||
|
const classOptions = ref([])
|
||||||
|
|
||||||
|
// 表单数据
|
||||||
|
const form = ref({
|
||||||
|
schoolId: null,
|
||||||
|
gradeId: '',
|
||||||
|
classIds: []
|
||||||
|
})
|
||||||
|
|
||||||
|
// 获取年级选项
|
||||||
|
const fetchGradeOptions = async () => {
|
||||||
|
try {
|
||||||
|
const res = await getGradeOptions()
|
||||||
|
if (res.code === 200) {
|
||||||
|
gradeOptions.value = res.data
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取年级选项失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取班级选项
|
||||||
|
const fetchClassOptions = async () => {
|
||||||
|
try {
|
||||||
|
const res = await getClassOptions()
|
||||||
|
if (res.code === 200) {
|
||||||
|
classOptions.value = res.data
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取班级选项失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 打开弹窗
|
||||||
|
const open = (school) => {
|
||||||
|
dialogVisible.value = true
|
||||||
|
currentSchool.value = school
|
||||||
|
form.value = {
|
||||||
|
schoolId: school.id,
|
||||||
|
gradeId: '',
|
||||||
|
classIds: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交表单
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
if (!form.value.gradeId) {
|
||||||
|
ElMessage.warning('请选择年级')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (form.value.classIds.length === 0) {
|
||||||
|
ElMessage.warning('请至少选择一个班级')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
submitLoading.value = true
|
||||||
|
try {
|
||||||
|
const res = await addGradeClass(form.value)
|
||||||
|
if (res.code === 200) {
|
||||||
|
ElMessage.success('添加班级成功')
|
||||||
|
dialogVisible.value = false
|
||||||
|
emit('success')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('添加班级失败:', error)
|
||||||
|
} finally {
|
||||||
|
submitLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化
|
||||||
|
onMounted(() => {
|
||||||
|
fetchGradeOptions()
|
||||||
|
fetchClassOptions()
|
||||||
|
})
|
||||||
|
|
||||||
|
// 暴露方法给父组件
|
||||||
|
defineExpose({ open })
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,109 @@
|
||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
v-model="dialogVisible"
|
||||||
|
title="新增年级"
|
||||||
|
width="500px"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
destroy-on-close
|
||||||
|
>
|
||||||
|
<div style="margin-bottom: 16px;">
|
||||||
|
<span style="font-weight: bold;">学校:</span>
|
||||||
|
<span>{{ currentSchool?.schoolName }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-form ref="formRef" :model="form" label-width="80px">
|
||||||
|
<el-form-item label="选择年级" prop="gradeIds">
|
||||||
|
<el-checkbox-group v-model="form.gradeIds">
|
||||||
|
<el-row :gutter="10">
|
||||||
|
<el-col :span="8" v-for="item in gradeOptions" :key="item.value">
|
||||||
|
<el-checkbox :label="item.value" style="margin-bottom: 8px;">
|
||||||
|
{{ item.label }}
|
||||||
|
</el-checkbox>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-checkbox-group>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="dialogVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="handleSubmit" :loading="submitLoading">确定</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
/**
|
||||||
|
* 新增年级弹窗
|
||||||
|
* @author pangu
|
||||||
|
*/
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import { onMounted, ref } from 'vue'
|
||||||
|
import { addSchoolGrade, getGradeOptions } from '@/api/pangu/school'
|
||||||
|
|
||||||
|
const emit = defineEmits(['success'])
|
||||||
|
|
||||||
|
const dialogVisible = ref(false)
|
||||||
|
const submitLoading = ref(false)
|
||||||
|
const formRef = ref(null)
|
||||||
|
const currentSchool = ref(null)
|
||||||
|
const gradeOptions = ref([])
|
||||||
|
|
||||||
|
// 表单数据
|
||||||
|
const form = ref({
|
||||||
|
schoolId: null,
|
||||||
|
gradeIds: []
|
||||||
|
})
|
||||||
|
|
||||||
|
// 获取年级选项
|
||||||
|
const fetchGradeOptions = async () => {
|
||||||
|
try {
|
||||||
|
const res = await getGradeOptions()
|
||||||
|
if (res.code === 200) {
|
||||||
|
gradeOptions.value = res.data
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取年级选项失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 打开弹窗
|
||||||
|
const open = (school) => {
|
||||||
|
dialogVisible.value = true
|
||||||
|
currentSchool.value = school
|
||||||
|
form.value = {
|
||||||
|
schoolId: school.id,
|
||||||
|
gradeIds: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交表单
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
if (form.value.gradeIds.length === 0) {
|
||||||
|
ElMessage.warning('请至少选择一个年级')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
submitLoading.value = true
|
||||||
|
try {
|
||||||
|
const res = await addSchoolGrade(form.value)
|
||||||
|
if (res.code === 200) {
|
||||||
|
ElMessage.success('添加年级成功')
|
||||||
|
dialogVisible.value = false
|
||||||
|
emit('success')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('添加年级失败:', error)
|
||||||
|
} finally {
|
||||||
|
submitLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化
|
||||||
|
onMounted(() => {
|
||||||
|
fetchGradeOptions()
|
||||||
|
})
|
||||||
|
|
||||||
|
// 暴露方法给父组件
|
||||||
|
defineExpose({ open })
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,225 @@
|
||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
v-model="dialogVisible"
|
||||||
|
:title="dialogTitle"
|
||||||
|
width="600px"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
destroy-on-close
|
||||||
|
>
|
||||||
|
<el-form
|
||||||
|
ref="formRef"
|
||||||
|
:model="form"
|
||||||
|
:rules="rules"
|
||||||
|
label-width="100px"
|
||||||
|
>
|
||||||
|
<el-form-item label="学校名称" prop="schoolName">
|
||||||
|
<el-input v-model="form.schoolName" placeholder="请输入学校名称" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="学校类型" prop="schoolType">
|
||||||
|
<el-select v-model="form.schoolType" placeholder="请选择学校类型" style="width: 100%">
|
||||||
|
<el-option label="小学" value="小学" />
|
||||||
|
<el-option label="初中" value="初中" />
|
||||||
|
<el-option label="高中" value="高中" />
|
||||||
|
<el-option label="九年一贯制" value="九年一贯制" />
|
||||||
|
<el-option label="完全中学" value="完全中学" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="所属区域" prop="regionId">
|
||||||
|
<el-cascader
|
||||||
|
v-model="form.regionIds"
|
||||||
|
:options="regionTree"
|
||||||
|
:props="{
|
||||||
|
value: 'regionId',
|
||||||
|
label: 'regionName',
|
||||||
|
children: 'children',
|
||||||
|
checkStrictly: true
|
||||||
|
}"
|
||||||
|
placeholder="请选择所属区域"
|
||||||
|
clearable
|
||||||
|
style="width: 100%"
|
||||||
|
@change="handleRegionChange"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="状态" prop="status">
|
||||||
|
<el-switch
|
||||||
|
v-model="form.status"
|
||||||
|
active-value="0"
|
||||||
|
inactive-value="1"
|
||||||
|
active-text="正常"
|
||||||
|
inactive-text="停用"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="dialogVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="handleSubmit" :loading="submitLoading">确定</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
/**
|
||||||
|
* 学校新增/编辑弹窗
|
||||||
|
* @author pangu
|
||||||
|
*/
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import { computed, ref } from 'vue'
|
||||||
|
import { addSchool, updateSchool } from '@/api/pangu/school'
|
||||||
|
|
||||||
|
// 接收父组件传递的区域树
|
||||||
|
const props = defineProps({
|
||||||
|
regionTree: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['success'])
|
||||||
|
|
||||||
|
const dialogVisible = ref(false)
|
||||||
|
const submitLoading = ref(false)
|
||||||
|
const formRef = ref(null)
|
||||||
|
const isEdit = ref(false)
|
||||||
|
|
||||||
|
// 表单数据
|
||||||
|
const form = ref({
|
||||||
|
id: null,
|
||||||
|
schoolName: '',
|
||||||
|
schoolType: '',
|
||||||
|
regionId: null,
|
||||||
|
regionIds: [],
|
||||||
|
regionName: '',
|
||||||
|
status: '0'
|
||||||
|
})
|
||||||
|
|
||||||
|
// 弹窗标题
|
||||||
|
const dialogTitle = computed(() => isEdit.value ? '编辑学校' : '新增学校')
|
||||||
|
|
||||||
|
// 表单验证规则
|
||||||
|
const rules = {
|
||||||
|
schoolName: [
|
||||||
|
{ required: true, message: '请输入学校名称', trigger: 'blur' }
|
||||||
|
],
|
||||||
|
schoolType: [
|
||||||
|
{ required: true, message: '请选择学校类型', trigger: 'change' }
|
||||||
|
],
|
||||||
|
regionId: [
|
||||||
|
{ required: true, message: '请选择所属区域', trigger: 'change' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取区域完整路径名称
|
||||||
|
const getRegionPath = (ids, tree) => {
|
||||||
|
const names = []
|
||||||
|
const findPath = (nodes, targetId, path) => {
|
||||||
|
for (const node of nodes) {
|
||||||
|
if (ids.includes(node.regionId)) {
|
||||||
|
path.push(node.regionName)
|
||||||
|
}
|
||||||
|
if (node.children) {
|
||||||
|
findPath(node.children, targetId, path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
findPath(tree, ids[ids.length - 1], names)
|
||||||
|
return names.join('/')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 区域选择变化
|
||||||
|
const handleRegionChange = (value) => {
|
||||||
|
if (value && value.length > 0) {
|
||||||
|
form.value.regionId = value[value.length - 1]
|
||||||
|
form.value.regionName = getRegionPath(value, props.regionTree)
|
||||||
|
} else {
|
||||||
|
form.value.regionId = null
|
||||||
|
form.value.regionName = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 打开弹窗
|
||||||
|
const open = (row) => {
|
||||||
|
dialogVisible.value = true
|
||||||
|
isEdit.value = !!row
|
||||||
|
|
||||||
|
if (row) {
|
||||||
|
// 编辑模式:回显数据
|
||||||
|
form.value = {
|
||||||
|
id: row.id,
|
||||||
|
schoolName: row.schoolName,
|
||||||
|
schoolType: row.schoolType,
|
||||||
|
regionId: row.regionId,
|
||||||
|
regionIds: getRegionIdPath(row.regionId),
|
||||||
|
regionName: row.regionName,
|
||||||
|
status: row.status
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 新增模式:重置表单
|
||||||
|
form.value = {
|
||||||
|
id: null,
|
||||||
|
schoolName: '',
|
||||||
|
schoolType: '',
|
||||||
|
regionId: null,
|
||||||
|
regionIds: [],
|
||||||
|
regionName: '',
|
||||||
|
status: '0'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据区域ID获取完整的ID路径(用于级联选择器回显)
|
||||||
|
const getRegionIdPath = (regionId) => {
|
||||||
|
if (!regionId) return []
|
||||||
|
|
||||||
|
const id = regionId.toString()
|
||||||
|
const path = []
|
||||||
|
|
||||||
|
// 省级ID(1位)
|
||||||
|
if (id.length >= 1) {
|
||||||
|
path.push(parseInt(id.charAt(0)))
|
||||||
|
}
|
||||||
|
// 市级ID(2位)
|
||||||
|
if (id.length >= 2) {
|
||||||
|
path.push(parseInt(id.substring(0, 2)))
|
||||||
|
}
|
||||||
|
// 区级ID(3位)
|
||||||
|
if (id.length >= 3) {
|
||||||
|
path.push(parseInt(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交表单
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
if (!formRef.value) return
|
||||||
|
|
||||||
|
await formRef.value.validate(async (valid) => {
|
||||||
|
if (!valid) return
|
||||||
|
|
||||||
|
submitLoading.value = true
|
||||||
|
try {
|
||||||
|
const submitData = {
|
||||||
|
...form.value,
|
||||||
|
regionIds: undefined // 不提交级联选择器的数组
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = isEdit.value
|
||||||
|
? await updateSchool(submitData)
|
||||||
|
: await addSchool(submitData)
|
||||||
|
|
||||||
|
if (res.code === 200) {
|
||||||
|
ElMessage.success(isEdit.value ? '修改成功' : '新增成功')
|
||||||
|
dialogVisible.value = false
|
||||||
|
emit('success')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('提交失败:', error)
|
||||||
|
} finally {
|
||||||
|
submitLoading.value = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 暴露方法给父组件
|
||||||
|
defineExpose({ open })
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,250 @@
|
||||||
|
<template>
|
||||||
|
<div class="app-container">
|
||||||
|
<el-row :gutter="16">
|
||||||
|
<!-- 左侧区域树 -->
|
||||||
|
<el-col :span="5">
|
||||||
|
<el-card shadow="never">
|
||||||
|
<template #header>
|
||||||
|
<span>区域筛选</span>
|
||||||
|
</template>
|
||||||
|
<el-input v-model="treeFilterText" placeholder="输入关键字过滤" clearable style="margin-bottom: 12px" />
|
||||||
|
<el-scrollbar height="calc(100vh - 260px)">
|
||||||
|
<el-tree
|
||||||
|
ref="treeRef"
|
||||||
|
:data="regionTree"
|
||||||
|
:props="{ label: 'regionName', children: 'children' }"
|
||||||
|
node-key="regionId"
|
||||||
|
highlight-current
|
||||||
|
:filter-node-method="filterNode"
|
||||||
|
@node-click="handleNodeClick"
|
||||||
|
/>
|
||||||
|
</el-scrollbar>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
|
||||||
|
<!-- 右侧列表 -->
|
||||||
|
<el-col :span="19">
|
||||||
|
<!-- 搜索区域 -->
|
||||||
|
<el-card shadow="never" class="search-wrapper">
|
||||||
|
<el-form :model="queryParams" :inline="true">
|
||||||
|
<el-form-item label="学校名称">
|
||||||
|
<el-input v-model="queryParams.schoolName" placeholder="请输入学校名称" clearable style="width: 200px" @keyup.enter="handleQuery" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="状态">
|
||||||
|
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable style="width: 120px">
|
||||||
|
<el-option label="正常" value="0" />
|
||||||
|
<el-option label="停用" value="1" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" :icon="Search" @click="handleQuery">搜索</el-button>
|
||||||
|
<el-button :icon="Refresh" @click="resetQuery">重置</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<!-- 表格区域 -->
|
||||||
|
<el-card shadow="never" style="margin-top: 12px">
|
||||||
|
<el-row :gutter="10" style="margin-bottom: 12px">
|
||||||
|
<el-col :span="1.5">
|
||||||
|
<el-button type="primary" :icon="Plus" @click="handleAdd">新增</el-button>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<el-table v-loading="loading" :data="tableData" border stripe :header-cell-style="{ background: '#f5f7fa', color: '#606266' }" style="width: 100%">
|
||||||
|
<el-table-column prop="schoolName" label="学校名称" min-width="150" show-overflow-tooltip />
|
||||||
|
<el-table-column prop="schoolType" label="学校类型" width="100" align="center" />
|
||||||
|
<el-table-column prop="regionName" label="所属区域" min-width="180" show-overflow-tooltip />
|
||||||
|
<el-table-column prop="status" label="状态" width="80" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag :type="row.status === '0' ? 'success' : 'danger'">
|
||||||
|
{{ row.status === '0' ? '正常' : '停用' }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="createTime" label="创建时间" width="160" />
|
||||||
|
<el-table-column prop="createBy" label="创建人" width="100" />
|
||||||
|
<el-table-column label="操作" width="280" fixed="right" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-button type="primary" link :icon="Edit" @click="handleEdit(row)">编辑</el-button>
|
||||||
|
<el-button type="primary" link :icon="Collection" @click="handleAddGrade(row)">新增年级</el-button>
|
||||||
|
<el-button type="primary" link :icon="Files" @click="handleAddClass(row)">新增班级</el-button>
|
||||||
|
<el-button type="danger" link :icon="Delete" @click="handleDelete(row)">删除</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<!-- 分页 -->
|
||||||
|
<el-pagination
|
||||||
|
v-model:current-page="queryParams.pageNum"
|
||||||
|
v-model:page-size="queryParams.pageSize"
|
||||||
|
:page-sizes="[10, 20, 50, 100]"
|
||||||
|
:total="total"
|
||||||
|
layout="total, sizes, prev, pager, next, jumper"
|
||||||
|
style="margin-top: 16px; justify-content: flex-end"
|
||||||
|
@size-change="handleQuery"
|
||||||
|
@current-change="handleQuery"
|
||||||
|
/>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<!-- 新增/编辑弹窗 -->
|
||||||
|
<SchoolDialog ref="schoolDialogRef" :region-tree="regionTree" @success="handleQuery" />
|
||||||
|
|
||||||
|
<!-- 新增年级弹窗 -->
|
||||||
|
<GradeDialog ref="gradeDialogRef" @success="handleQuery" />
|
||||||
|
|
||||||
|
<!-- 新增班级弹窗 -->
|
||||||
|
<ClassDialog ref="classDialogRef" @success="handleQuery" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
/**
|
||||||
|
* 学校管理页面
|
||||||
|
* @author pangu
|
||||||
|
*/
|
||||||
|
import { Collection, Delete, Edit, Files, Plus, Refresh, Search } from '@element-plus/icons-vue'
|
||||||
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
|
import { onMounted, ref, watch } from 'vue'
|
||||||
|
import request from '@/utils/request'
|
||||||
|
import ClassDialog from './components/ClassDialog.vue'
|
||||||
|
import GradeDialog from './components/GradeDialog.vue'
|
||||||
|
import SchoolDialog from './components/SchoolDialog.vue'
|
||||||
|
|
||||||
|
// 区域树相关
|
||||||
|
const treeRef = ref()
|
||||||
|
const treeFilterText = ref('')
|
||||||
|
const regionTree = ref([])
|
||||||
|
const selectedRegionId = ref(null)
|
||||||
|
|
||||||
|
// 表格相关
|
||||||
|
const loading = ref(false)
|
||||||
|
const tableData = ref([])
|
||||||
|
const total = ref(0)
|
||||||
|
|
||||||
|
// 查询参数
|
||||||
|
const queryParams = ref({
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
schoolName: '',
|
||||||
|
status: '',
|
||||||
|
regionId: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
// 弹窗引用
|
||||||
|
const schoolDialogRef = ref()
|
||||||
|
const gradeDialogRef = ref()
|
||||||
|
const classDialogRef = ref()
|
||||||
|
|
||||||
|
// 监听树过滤
|
||||||
|
watch(treeFilterText, (val) => {
|
||||||
|
treeRef.value?.filter(val)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 树节点过滤
|
||||||
|
const filterNode = (value, data) => {
|
||||||
|
if (!value) return true
|
||||||
|
return data.regionName.includes(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取区域树
|
||||||
|
const getRegionTree = async () => {
|
||||||
|
const res = await request.get('/business/region/tree')
|
||||||
|
if (res.code === 200) {
|
||||||
|
regionTree.value = res.data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取学校列表
|
||||||
|
const getList = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const res = await request.get('/business/school/list', { params: queryParams.value })
|
||||||
|
if (res.code === 200) {
|
||||||
|
tableData.value = res.rows
|
||||||
|
total.value = res.total
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 树节点点击
|
||||||
|
const handleNodeClick = (data) => {
|
||||||
|
selectedRegionId.value = data.regionId
|
||||||
|
queryParams.value.regionId = data.regionId
|
||||||
|
queryParams.value.pageNum = 1
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 搜索
|
||||||
|
const handleQuery = () => {
|
||||||
|
queryParams.value.pageNum = 1
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置
|
||||||
|
const resetQuery = () => {
|
||||||
|
queryParams.value = {
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
schoolName: '',
|
||||||
|
status: '',
|
||||||
|
regionId: ''
|
||||||
|
}
|
||||||
|
selectedRegionId.value = null
|
||||||
|
treeRef.value?.setCurrentKey(null)
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增
|
||||||
|
const handleAdd = () => {
|
||||||
|
schoolDialogRef.value?.open()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 编辑
|
||||||
|
const handleEdit = (row) => {
|
||||||
|
schoolDialogRef.value?.open(row)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除
|
||||||
|
const handleDelete = (row) => {
|
||||||
|
ElMessageBox.confirm(`确定要删除学校"${row.schoolName}"吗?`, '提示', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
}).then(async () => {
|
||||||
|
const res = await request.delete(`/api/school/${row.id}`)
|
||||||
|
if (res.code === 200) {
|
||||||
|
ElMessage.success('删除成功')
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
}).catch(() => {})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增年级
|
||||||
|
const handleAddGrade = (row) => {
|
||||||
|
gradeDialogRef.value?.open(row)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增班级
|
||||||
|
const handleAddClass = (row) => {
|
||||||
|
classDialogRef.value?.open(row)
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getRegionTree()
|
||||||
|
getList()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.app-container {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
.search-wrapper {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,151 @@
|
||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
v-model="visible"
|
||||||
|
title="批量导入学生"
|
||||||
|
width="500px"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
destroy-on-close
|
||||||
|
>
|
||||||
|
<el-alert
|
||||||
|
title="导入说明"
|
||||||
|
type="info"
|
||||||
|
:closable="false"
|
||||||
|
style="margin-bottom: 16px"
|
||||||
|
>
|
||||||
|
<template #default>
|
||||||
|
<div style="line-height: 1.8">
|
||||||
|
1. 请先下载导入模板,按模板格式填写数据<br>
|
||||||
|
2. 支持 xlsx、xls 格式文件,单次最多导入500条<br>
|
||||||
|
3. 必填字段:姓名、学校、年级、班级
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-alert>
|
||||||
|
|
||||||
|
<div style="margin-bottom: 16px;">
|
||||||
|
<el-button type="primary" :icon="Download" @click="handleDownloadTemplate">下载模板</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-upload
|
||||||
|
ref="uploadRef"
|
||||||
|
:action="uploadUrl"
|
||||||
|
:headers="uploadHeaders"
|
||||||
|
:before-upload="beforeUpload"
|
||||||
|
:on-success="handleSuccess"
|
||||||
|
:on-error="handleError"
|
||||||
|
:show-file-list="true"
|
||||||
|
:limit="1"
|
||||||
|
accept=".xlsx,.xls"
|
||||||
|
drag
|
||||||
|
>
|
||||||
|
<el-icon class="el-icon--upload"><Upload /></el-icon>
|
||||||
|
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
|
||||||
|
<template #tip>
|
||||||
|
<div class="el-upload__tip">只能上传 xlsx/xls 文件</div>
|
||||||
|
</template>
|
||||||
|
</el-upload>
|
||||||
|
|
||||||
|
<!-- 导入结果 -->
|
||||||
|
<div v-if="importResult" style="margin-top: 16px;">
|
||||||
|
<el-alert
|
||||||
|
:title="`导入完成:成功 ${importResult.successCount} 条,失败 ${importResult.failCount} 条`"
|
||||||
|
:type="importResult.failCount > 0 ? 'warning' : 'success'"
|
||||||
|
:closable="false"
|
||||||
|
/>
|
||||||
|
<div v-if="importResult.failList && importResult.failList.length > 0" style="margin-top: 12px;">
|
||||||
|
<el-table :data="importResult.failList" border size="small" max-height="200">
|
||||||
|
<el-table-column prop="row" label="行号" width="80" />
|
||||||
|
<el-table-column prop="reason" label="失败原因" min-width="200" />
|
||||||
|
</el-table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="visible = false">关闭</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
/**
|
||||||
|
* 学生批量导入弹窗
|
||||||
|
* @author pangu
|
||||||
|
*/
|
||||||
|
import { Download, Upload } from '@element-plus/icons-vue'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import { computed, ref } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue', 'success'])
|
||||||
|
|
||||||
|
const visible = computed({
|
||||||
|
get: () => props.modelValue,
|
||||||
|
set: (val) => emit('update:modelValue', val)
|
||||||
|
})
|
||||||
|
|
||||||
|
const uploadRef = ref(null)
|
||||||
|
const importResult = ref(null)
|
||||||
|
|
||||||
|
// 上传地址
|
||||||
|
const uploadUrl = '/business/student/import'
|
||||||
|
|
||||||
|
// 上传请求头
|
||||||
|
const uploadHeaders = computed(() => {
|
||||||
|
const token = localStorage.getItem('token')
|
||||||
|
return token ? { Authorization: 'Bearer ' + token } : {}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 下载模板
|
||||||
|
const handleDownloadTemplate = () => {
|
||||||
|
// 实际应该调用后端接口下载模板
|
||||||
|
ElMessage.info('模板下载功能需要对接后端下载接口')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传前校验
|
||||||
|
const beforeUpload = (file) => {
|
||||||
|
const isExcel = file.name.endsWith('.xlsx') || file.name.endsWith('.xls')
|
||||||
|
if (!isExcel) {
|
||||||
|
ElMessage.error('只能上传 Excel 文件')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
const isLt10M = file.size / 1024 / 1024 < 10
|
||||||
|
if (!isLt10M) {
|
||||||
|
ElMessage.error('文件大小不能超过 10MB')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
importResult.value = null
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传成功
|
||||||
|
const handleSuccess = (response) => {
|
||||||
|
if (response.code === 200) {
|
||||||
|
importResult.value = response.data
|
||||||
|
if (response.data.failCount === 0) {
|
||||||
|
ElMessage.success('导入成功')
|
||||||
|
emit('success')
|
||||||
|
} else {
|
||||||
|
ElMessage.warning('部分数据导入失败,请查看失败原因')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ElMessage.error(response.msg || '导入失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传失败
|
||||||
|
const handleError = () => {
|
||||||
|
ElMessage.error('文件上传失败,请重试')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.el-icon--upload {
|
||||||
|
font-size: 48px;
|
||||||
|
color: #c0c4cc;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,203 @@
|
||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
v-model="visible"
|
||||||
|
:title="isEdit ? '编辑学生' : '新增学生'"
|
||||||
|
width="600px"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
destroy-on-close
|
||||||
|
@open="handleOpen"
|
||||||
|
>
|
||||||
|
<el-form
|
||||||
|
ref="formRef"
|
||||||
|
:model="form"
|
||||||
|
:rules="rules"
|
||||||
|
label-width="100px"
|
||||||
|
>
|
||||||
|
<el-form-item label="姓名" prop="name">
|
||||||
|
<el-input v-model="form.name" placeholder="请输入学生姓名" maxlength="20" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="学号" prop="studentNo">
|
||||||
|
<el-input v-model="form.studentNo" placeholder="请输入学号" maxlength="30" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="性别" prop="gender">
|
||||||
|
<el-radio-group v-model="form.gender">
|
||||||
|
<el-radio value="1">男</el-radio>
|
||||||
|
<el-radio value="2">女</el-radio>
|
||||||
|
<el-radio value="0">未知</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="出生日期" prop="birthday">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="form.birthday"
|
||||||
|
type="month"
|
||||||
|
placeholder="请选择出生年月"
|
||||||
|
format="YYYY-MM"
|
||||||
|
value-format="YYYY-MM"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="学校信息" prop="schoolPath" required>
|
||||||
|
<el-cascader
|
||||||
|
v-model="form.schoolPath"
|
||||||
|
:options="schoolTree"
|
||||||
|
:props="{
|
||||||
|
value: 'id',
|
||||||
|
label: 'label',
|
||||||
|
children: 'children',
|
||||||
|
checkStrictly: false
|
||||||
|
}"
|
||||||
|
placeholder="请选择学校/年级/班级"
|
||||||
|
clearable
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="学科" prop="subject">
|
||||||
|
<el-select v-model="form.subject" placeholder="请选择学科" clearable style="width: 100%">
|
||||||
|
<el-option v-for="item in subjectList" :key="item.id" :label="item.name" :value="item.name" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="归属用户" prop="userId">
|
||||||
|
<el-input v-model="form.userNickname" placeholder="请输入用户昵称搜索" readonly>
|
||||||
|
<template #append>
|
||||||
|
<el-button @click="handleSelectUser">选择</el-button>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="visible = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="handleSubmit" :loading="submitLoading">确定</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
/**
|
||||||
|
* 学生新增/编辑弹窗
|
||||||
|
* @author pangu
|
||||||
|
*/
|
||||||
|
import { addStudent, getStudent, updateStudent } from '@/api/pangu/student'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import { computed, reactive, ref } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
studentId: {
|
||||||
|
type: [Number, null],
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
schoolTree: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
subjectList: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue', 'success'])
|
||||||
|
|
||||||
|
const visible = computed({
|
||||||
|
get: () => props.modelValue,
|
||||||
|
set: (val) => emit('update:modelValue', val)
|
||||||
|
})
|
||||||
|
|
||||||
|
const isEdit = computed(() => !!props.studentId)
|
||||||
|
|
||||||
|
const formRef = ref(null)
|
||||||
|
const submitLoading = ref(false)
|
||||||
|
|
||||||
|
const initialForm = {
|
||||||
|
id: null,
|
||||||
|
name: '',
|
||||||
|
studentNo: '',
|
||||||
|
gender: '1',
|
||||||
|
birthday: '',
|
||||||
|
schoolPath: [],
|
||||||
|
subject: '',
|
||||||
|
userId: null,
|
||||||
|
userNickname: ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const form = reactive({ ...initialForm })
|
||||||
|
|
||||||
|
const rules = {
|
||||||
|
name: [
|
||||||
|
{ required: true, message: '请输入学生姓名', trigger: 'blur' }
|
||||||
|
],
|
||||||
|
schoolPath: [
|
||||||
|
{ required: true, message: '请选择学校/年级/班级', trigger: 'change' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 弹窗打开时
|
||||||
|
const handleOpen = async () => {
|
||||||
|
Object.assign(form, initialForm)
|
||||||
|
formRef.value?.clearValidate()
|
||||||
|
|
||||||
|
if (props.studentId) {
|
||||||
|
try {
|
||||||
|
const res = await getStudent(props.studentId)
|
||||||
|
if (res.data) {
|
||||||
|
const data = res.data
|
||||||
|
form.id = data.id
|
||||||
|
form.name = data.name
|
||||||
|
form.studentNo = data.studentNo
|
||||||
|
form.gender = data.gender
|
||||||
|
form.birthday = data.birthday
|
||||||
|
form.subject = data.subject
|
||||||
|
form.userId = data.userId
|
||||||
|
form.userNickname = data.userNickname
|
||||||
|
// 需要根据实际数据结构构建 schoolPath
|
||||||
|
form.schoolPath = [data.schoolId]
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('获取学生详情失败:', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 选择用户
|
||||||
|
const handleSelectUser = () => {
|
||||||
|
// 简化处理,实际应该弹出用户选择器
|
||||||
|
ElMessage.info('用户选择功能需要对接会员管理模块')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
try {
|
||||||
|
await formRef.value?.validate()
|
||||||
|
} catch (e) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
submitLoading.value = true
|
||||||
|
try {
|
||||||
|
const submitData = {
|
||||||
|
...form,
|
||||||
|
schoolId: form.schoolPath[0],
|
||||||
|
gradeId: form.schoolPath[1],
|
||||||
|
classId: form.schoolPath[2]
|
||||||
|
}
|
||||||
|
delete submitData.schoolPath
|
||||||
|
|
||||||
|
if (isEdit.value) {
|
||||||
|
await updateStudent(submitData)
|
||||||
|
ElMessage.success('修改成功')
|
||||||
|
} else {
|
||||||
|
await addStudent(submitData)
|
||||||
|
ElMessage.success('新增成功')
|
||||||
|
}
|
||||||
|
visible.value = false
|
||||||
|
emit('success')
|
||||||
|
} catch (e) {
|
||||||
|
// 错误在拦截器中已处理
|
||||||
|
} finally {
|
||||||
|
submitLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,259 @@
|
||||||
|
<template>
|
||||||
|
<div class="app-container">
|
||||||
|
<el-row :gutter="16">
|
||||||
|
<!-- 左侧学校树 -->
|
||||||
|
<el-col :span="5">
|
||||||
|
<el-card shadow="never">
|
||||||
|
<template #header>
|
||||||
|
<span>学校筛选</span>
|
||||||
|
</template>
|
||||||
|
<el-input v-model="treeFilterText" placeholder="输入关键字过滤" clearable style="margin-bottom: 12px" />
|
||||||
|
<el-scrollbar height="calc(100vh - 260px)">
|
||||||
|
<el-tree
|
||||||
|
ref="treeRef"
|
||||||
|
:data="schoolTree"
|
||||||
|
:props="{ label: 'label', children: 'children' }"
|
||||||
|
node-key="id"
|
||||||
|
highlight-current
|
||||||
|
:filter-node-method="filterNode"
|
||||||
|
@node-click="handleNodeClick"
|
||||||
|
/>
|
||||||
|
</el-scrollbar>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
|
||||||
|
<!-- 右侧列表 -->
|
||||||
|
<el-col :span="19">
|
||||||
|
<!-- 搜索区域 -->
|
||||||
|
<el-card shadow="never" class="search-wrapper">
|
||||||
|
<el-form :model="queryParams" :inline="true">
|
||||||
|
<el-form-item label="学生姓名">
|
||||||
|
<el-input v-model="queryParams.name" placeholder="请输入学生姓名" clearable style="width: 150px" @keyup.enter="handleQuery" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="学号">
|
||||||
|
<el-input v-model="queryParams.studentNo" placeholder="请输入学号" clearable style="width: 150px" @keyup.enter="handleQuery" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="性别">
|
||||||
|
<el-select v-model="queryParams.gender" placeholder="全部" clearable style="width: 100px">
|
||||||
|
<el-option label="男" value="1" />
|
||||||
|
<el-option label="女" value="2" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" :icon="Search" @click="handleQuery">搜索</el-button>
|
||||||
|
<el-button :icon="Refresh" @click="resetQuery">重置</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<!-- 表格区域 -->
|
||||||
|
<el-card shadow="never" style="margin-top: 12px">
|
||||||
|
<el-row :gutter="10" style="margin-bottom: 12px">
|
||||||
|
<el-col :span="1.5">
|
||||||
|
<el-button type="primary" :icon="Plus" @click="handleAdd">新增</el-button>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="1.5">
|
||||||
|
<el-button type="success" :icon="Upload" @click="handleImport">导入</el-button>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<el-table v-loading="loading" :data="tableData" border stripe :header-cell-style="{ background: '#f5f7fa', color: '#606266' }" style="width: 100%">
|
||||||
|
<el-table-column prop="studentNo" label="学号" width="130" />
|
||||||
|
<el-table-column prop="name" label="姓名" width="100" />
|
||||||
|
<el-table-column prop="gender" label="性别" width="60" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ row.gender === '1' ? '男' : row.gender === '2' ? '女' : '未知' }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="birthday" label="出生年月" width="100" />
|
||||||
|
<el-table-column prop="schoolName" label="学校" min-width="150" show-overflow-tooltip />
|
||||||
|
<el-table-column prop="gradeName" label="年级" width="80" />
|
||||||
|
<el-table-column prop="className" label="班级" width="80" />
|
||||||
|
<el-table-column prop="subject" label="学科" width="80" />
|
||||||
|
<el-table-column prop="userNickname" label="归属用户" width="100" show-overflow-tooltip />
|
||||||
|
<el-table-column prop="createTime" label="创建时间" width="160" />
|
||||||
|
<el-table-column label="操作" width="150" fixed="right" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-button type="primary" link :icon="Edit" @click="handleEdit(row)">编辑</el-button>
|
||||||
|
<el-button type="danger" link :icon="Delete" @click="handleDelete(row)">删除</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<!-- 分页 -->
|
||||||
|
<el-pagination
|
||||||
|
v-model:current-page="queryParams.pageNum"
|
||||||
|
v-model:page-size="queryParams.pageSize"
|
||||||
|
:page-sizes="[10, 20, 50, 100]"
|
||||||
|
:total="total"
|
||||||
|
layout="total, sizes, prev, pager, next, jumper"
|
||||||
|
style="margin-top: 16px; justify-content: flex-end"
|
||||||
|
@size-change="handleQuery"
|
||||||
|
@current-change="handleQuery"
|
||||||
|
/>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<!-- 新增/编辑弹窗 -->
|
||||||
|
<StudentDialog ref="studentDialogRef" @success="handleQuery" />
|
||||||
|
|
||||||
|
<!-- 导入弹窗 -->
|
||||||
|
<ImportDialog ref="importDialogRef" @success="handleQuery" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
/**
|
||||||
|
* 学生管理页面
|
||||||
|
* @author pangu
|
||||||
|
*/
|
||||||
|
import { Delete, Edit, Plus, Refresh, Search, Upload } from '@element-plus/icons-vue'
|
||||||
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
|
import { onMounted, ref, watch } from 'vue'
|
||||||
|
import request from '@/utils/request'
|
||||||
|
import ImportDialog from './components/ImportDialog.vue'
|
||||||
|
import StudentDialog from './components/StudentDialog.vue'
|
||||||
|
|
||||||
|
// 学校树相关
|
||||||
|
const treeRef = ref()
|
||||||
|
const treeFilterText = ref('')
|
||||||
|
const schoolTree = ref([])
|
||||||
|
|
||||||
|
// 表格相关
|
||||||
|
const loading = ref(false)
|
||||||
|
const tableData = ref([])
|
||||||
|
const total = ref(0)
|
||||||
|
|
||||||
|
// 查询参数
|
||||||
|
const queryParams = ref({
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
name: '',
|
||||||
|
studentNo: '',
|
||||||
|
gender: '',
|
||||||
|
schoolId: '',
|
||||||
|
gradeId: '',
|
||||||
|
classId: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
// 弹窗引用
|
||||||
|
const studentDialogRef = ref()
|
||||||
|
const importDialogRef = ref()
|
||||||
|
|
||||||
|
// 监听树过滤
|
||||||
|
watch(treeFilterText, (val) => {
|
||||||
|
treeRef.value?.filter(val)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 树节点过滤
|
||||||
|
const filterNode = (value, data) => {
|
||||||
|
if (!value) return true
|
||||||
|
return data.label.includes(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取学校树
|
||||||
|
const getSchoolTree = async () => {
|
||||||
|
const res = await request.get('/business/student/schoolTree')
|
||||||
|
if (res.code === 200) {
|
||||||
|
schoolTree.value = res.data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取学生列表
|
||||||
|
const getList = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const res = await request.get('/business/student/list', { params: queryParams.value })
|
||||||
|
if (res.code === 200) {
|
||||||
|
tableData.value = res.rows
|
||||||
|
total.value = res.total
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 树节点点击
|
||||||
|
const handleNodeClick = (data) => {
|
||||||
|
// 根据节点层级设置筛选条件
|
||||||
|
if (data.type === 'school') {
|
||||||
|
queryParams.value.schoolId = data.id
|
||||||
|
queryParams.value.gradeId = ''
|
||||||
|
queryParams.value.classId = ''
|
||||||
|
} else if (data.type === 'grade') {
|
||||||
|
queryParams.value.gradeId = data.id
|
||||||
|
queryParams.value.classId = ''
|
||||||
|
} else if (data.type === 'class') {
|
||||||
|
queryParams.value.classId = data.id
|
||||||
|
}
|
||||||
|
queryParams.value.pageNum = 1
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 搜索
|
||||||
|
const handleQuery = () => {
|
||||||
|
queryParams.value.pageNum = 1
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置
|
||||||
|
const resetQuery = () => {
|
||||||
|
queryParams.value = {
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
name: '',
|
||||||
|
studentNo: '',
|
||||||
|
gender: '',
|
||||||
|
schoolId: '',
|
||||||
|
gradeId: '',
|
||||||
|
classId: ''
|
||||||
|
}
|
||||||
|
treeRef.value?.setCurrentKey(null)
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增
|
||||||
|
const handleAdd = () => {
|
||||||
|
studentDialogRef.value?.open()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 编辑
|
||||||
|
const handleEdit = (row) => {
|
||||||
|
studentDialogRef.value?.open(row)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导入
|
||||||
|
const handleImport = () => {
|
||||||
|
importDialogRef.value?.open()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除
|
||||||
|
const handleDelete = (row) => {
|
||||||
|
ElMessageBox.confirm(`确定要删除学生"${row.name}"吗?`, '提示', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
}).then(async () => {
|
||||||
|
const res = await request.delete(`/api/student/${row.id}`)
|
||||||
|
if (res.code === 200) {
|
||||||
|
ElMessage.success('删除成功')
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
}).catch(() => {})
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getSchoolTree()
|
||||||
|
getList()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.app-container {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
.search-wrapper {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Loading…
Reference in New Issue