|
|
|
|
@ -0,0 +1,385 @@
|
|
|
|
|
<template>
|
|
|
|
|
<div class="session-manage">
|
|
|
|
|
<!-- 工具栏 -->
|
|
|
|
|
<div class="session-toolbar">
|
|
|
|
|
<h3>期次管理</h3>
|
|
|
|
|
<el-button type="primary" icon="Plus" @click="showAddDialog">新增期次</el-button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 搜索筛选 -->
|
|
|
|
|
<div class="search-bar">
|
|
|
|
|
<el-input
|
|
|
|
|
v-model="searchKeyword"
|
|
|
|
|
placeholder="搜索期次名称、期次描述"
|
|
|
|
|
prefix-icon="Search"
|
|
|
|
|
clearable
|
|
|
|
|
style="max-width: 300px;"
|
|
|
|
|
@clear="handleSearch"
|
|
|
|
|
@keyup.enter="handleSearch"
|
|
|
|
|
/>
|
|
|
|
|
<el-select v-model="filterStatus" placeholder="筛选状态" clearable style="width: 150px;">
|
|
|
|
|
<el-option label="全部" value="" />
|
|
|
|
|
<el-option label="启用" value="active" />
|
|
|
|
|
<el-option label="停用" value="inactive" />
|
|
|
|
|
</el-select>
|
|
|
|
|
<el-button type="primary" icon="Search" @click="handleSearch">搜索</el-button>
|
|
|
|
|
<el-button icon="Refresh" @click="handleRefresh">重置</el-button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 数据表格 -->
|
|
|
|
|
<div class="session-table-card">
|
|
|
|
|
<el-table
|
|
|
|
|
:data="sessionList"
|
|
|
|
|
stripe
|
|
|
|
|
border
|
|
|
|
|
v-loading="loading"
|
|
|
|
|
:header-cell-style="{ background: '#f5f7fa', color: '#303133', fontWeight: '600' }"
|
|
|
|
|
style="width: 100%"
|
|
|
|
|
>
|
|
|
|
|
<el-table-column type="index" label="序号" width="60" align="center" />
|
|
|
|
|
<el-table-column prop="periodName" label="期次名称" width="140" align="center" />
|
|
|
|
|
<el-table-column prop="description" label="期次描述" min-width="200" show-overflow-tooltip />
|
|
|
|
|
<el-table-column label="培训日期" width="200" align="center">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
{{ formatDateRange(row.startDate, row.endDate) }}
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column prop="status" label="状态" width="100" align="center">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
<el-tag :type="row.status === 'active' ? 'success' : 'info'">
|
|
|
|
|
{{ row.status === 'active' ? '启用' : '停用' }}
|
|
|
|
|
</el-tag>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column prop="createTime" label="创建时间" width="160" align="center">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
{{ formatTime(row.createTime) }}
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="操作" width="180" align="center" fixed="right">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
<el-button type="primary" link icon="Edit" @click="showEditDialog(row)">编辑</el-button>
|
|
|
|
|
<el-button type="danger" link icon="Delete" @click="handleDelete(row)">删除</el-button>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
</el-table>
|
|
|
|
|
|
|
|
|
|
<!-- 分页 -->
|
|
|
|
|
<div class="pagination-wrapper">
|
|
|
|
|
<el-pagination
|
|
|
|
|
:current-page="currentPage"
|
|
|
|
|
:page-size="pageSize"
|
|
|
|
|
:page-sizes="[10, 20, 50, 100]"
|
|
|
|
|
:total="total"
|
|
|
|
|
layout="total, sizes, prev, pager, next, jumper"
|
|
|
|
|
@size-change="handleSizeChange"
|
|
|
|
|
@current-change="handleCurrentChange"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 新增/编辑弹窗 -->
|
|
|
|
|
<el-dialog
|
|
|
|
|
v-model="dialogVisible"
|
|
|
|
|
:title="isEdit ? '编辑期次' : '新增期次'"
|
|
|
|
|
width="520px"
|
|
|
|
|
destroy-on-close
|
|
|
|
|
:close-on-click-modal="false"
|
|
|
|
|
>
|
|
|
|
|
<el-form
|
|
|
|
|
ref="formRef"
|
|
|
|
|
:model="form"
|
|
|
|
|
:rules="rules"
|
|
|
|
|
label-width="100px"
|
|
|
|
|
size="default"
|
|
|
|
|
>
|
|
|
|
|
<el-form-item label="期次名称" prop="periodName">
|
|
|
|
|
<el-input v-model="form.periodName" placeholder="如:第一期" maxlength="50" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="期次描述" prop="description">
|
|
|
|
|
<el-input
|
|
|
|
|
v-model="form.description"
|
|
|
|
|
type="textarea"
|
|
|
|
|
:rows="3"
|
|
|
|
|
placeholder="请输入期次描述"
|
|
|
|
|
maxlength="500"
|
|
|
|
|
show-word-limit
|
|
|
|
|
/>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="培训日期" prop="dateRange">
|
|
|
|
|
<el-date-picker
|
|
|
|
|
v-model="form.dateRange"
|
|
|
|
|
type="daterange"
|
|
|
|
|
range-separator="至"
|
|
|
|
|
start-placeholder="开始日期"
|
|
|
|
|
end-placeholder="结束日期"
|
|
|
|
|
value-format="YYYY-MM-DD"
|
|
|
|
|
style="width: 100%"
|
|
|
|
|
/>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="状态" prop="status">
|
|
|
|
|
<el-switch
|
|
|
|
|
v-model="form.status"
|
|
|
|
|
active-value="active"
|
|
|
|
|
inactive-value="inactive"
|
|
|
|
|
active-text="启用"
|
|
|
|
|
inactive-text="停用"
|
|
|
|
|
/>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
</el-form>
|
|
|
|
|
<template #footer>
|
|
|
|
|
<el-button @click="dialogVisible = false">取消</el-button>
|
|
|
|
|
<el-button type="primary" @click="submitForm" :loading="submitLoading">确定</el-button>
|
|
|
|
|
</template>
|
|
|
|
|
</el-dialog>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup>
|
|
|
|
|
import { ref, reactive, onMounted } from 'vue'
|
|
|
|
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
|
|
|
|
import { getSessionList, addSession, updateSession, deleteSession } from '../../api/registration'
|
|
|
|
|
|
|
|
|
|
// 数据
|
|
|
|
|
const loading = ref(false)
|
|
|
|
|
const sessionList = ref([])
|
|
|
|
|
const dialogVisible = ref(false)
|
|
|
|
|
const isEdit = ref(false)
|
|
|
|
|
const editId = ref(null)
|
|
|
|
|
const submitLoading = ref(false)
|
|
|
|
|
const formRef = ref(null)
|
|
|
|
|
|
|
|
|
|
// 搜索与分页
|
|
|
|
|
const searchKeyword = ref('')
|
|
|
|
|
const filterStatus = ref('')
|
|
|
|
|
const currentPage = ref(1)
|
|
|
|
|
const pageSize = ref(10)
|
|
|
|
|
const total = ref(0)
|
|
|
|
|
|
|
|
|
|
// 表单数据
|
|
|
|
|
const form = reactive({
|
|
|
|
|
periodName: '',
|
|
|
|
|
description: '',
|
|
|
|
|
dateRange: [],
|
|
|
|
|
status: 'active'
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// 表单校验规则 — 全部必填
|
|
|
|
|
const rules = {
|
|
|
|
|
periodName: [
|
|
|
|
|
{ required: true, message: '请输入期次名称', trigger: 'blur' }
|
|
|
|
|
],
|
|
|
|
|
description: [
|
|
|
|
|
{ max: 500, message: '期次描述不能超过500个字符', trigger: 'blur' }
|
|
|
|
|
],
|
|
|
|
|
dateRange: [
|
|
|
|
|
{ required: true, message: '请选择培训日期', trigger: 'change' }
|
|
|
|
|
],
|
|
|
|
|
status: [
|
|
|
|
|
{ required: true, message: '请选择状态', trigger: 'change' }
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 格式化时间
|
|
|
|
|
const formatTime = (time) => {
|
|
|
|
|
if (!time) return '-'
|
|
|
|
|
const date = new Date(time)
|
|
|
|
|
return date.toLocaleString('zh-CN', {
|
|
|
|
|
year: 'numeric',
|
|
|
|
|
month: '2-digit',
|
|
|
|
|
day: '2-digit',
|
|
|
|
|
hour: '2-digit',
|
|
|
|
|
minute: '2-digit'
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 格式化培训日期:5月22-24日(同月)、5月22日 - 6月1日(跨月)
|
|
|
|
|
const formatDateRange = (startDate, endDate) => {
|
|
|
|
|
if (!startDate || !endDate) return '-'
|
|
|
|
|
const sMM = parseInt(startDate.slice(5, 7), 10)
|
|
|
|
|
const sDD = parseInt(startDate.slice(8, 10), 10)
|
|
|
|
|
const eMM = parseInt(endDate.slice(5, 7), 10)
|
|
|
|
|
const eDD = parseInt(endDate.slice(8, 10), 10)
|
|
|
|
|
if (eMM !== sMM) {
|
|
|
|
|
return `${sMM}月${sDD}日 - ${eMM}月${eDD}日`
|
|
|
|
|
}
|
|
|
|
|
return `${sMM}月${sDD}-${eDD}日`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 加载期次列表(分页)
|
|
|
|
|
const loadSessionList = async () => {
|
|
|
|
|
loading.value = true
|
|
|
|
|
try {
|
|
|
|
|
const params = {
|
|
|
|
|
pageNum: currentPage.value,
|
|
|
|
|
pageSize: pageSize.value,
|
|
|
|
|
status: filterStatus.value || undefined
|
|
|
|
|
}
|
|
|
|
|
if (searchKeyword.value) {
|
|
|
|
|
params.keyword = searchKeyword.value
|
|
|
|
|
}
|
|
|
|
|
const res = await getSessionList(params)
|
|
|
|
|
sessionList.value = res.data.records || []
|
|
|
|
|
total.value = res.data.total || 0
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.error('加载期次列表失败', e)
|
|
|
|
|
sessionList.value = []
|
|
|
|
|
total.value = 0
|
|
|
|
|
}
|
|
|
|
|
loading.value = false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 搜索
|
|
|
|
|
const handleSearch = () => {
|
|
|
|
|
currentPage.value = 1
|
|
|
|
|
loadSessionList()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 刷新
|
|
|
|
|
const handleRefresh = () => {
|
|
|
|
|
searchKeyword.value = ''
|
|
|
|
|
filterStatus.value = ''
|
|
|
|
|
currentPage.value = 1
|
|
|
|
|
loadSessionList()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 分页
|
|
|
|
|
const handleSizeChange = (val) => {
|
|
|
|
|
pageSize.value = val
|
|
|
|
|
currentPage.value = 1
|
|
|
|
|
loadSessionList()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handleCurrentChange = (val) => {
|
|
|
|
|
currentPage.value = val
|
|
|
|
|
loadSessionList()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 重置表单
|
|
|
|
|
const resetForm = () => {
|
|
|
|
|
form.periodName = ''
|
|
|
|
|
form.description = ''
|
|
|
|
|
form.dateRange = []
|
|
|
|
|
form.status = 'active'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 显示新增弹窗
|
|
|
|
|
const showAddDialog = () => {
|
|
|
|
|
isEdit.value = false
|
|
|
|
|
editId.value = null
|
|
|
|
|
resetForm()
|
|
|
|
|
dialogVisible.value = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 显示编辑弹窗
|
|
|
|
|
const showEditDialog = (row) => {
|
|
|
|
|
isEdit.value = true
|
|
|
|
|
editId.value = row.id
|
|
|
|
|
form.periodName = row.periodName || ''
|
|
|
|
|
form.description = row.description || ''
|
|
|
|
|
form.dateRange = row.startDate && row.endDate ? [row.startDate, row.endDate] : []
|
|
|
|
|
form.status = row.status || 'active'
|
|
|
|
|
dialogVisible.value = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 提交表单
|
|
|
|
|
const submitForm = async () => {
|
|
|
|
|
const valid = await formRef.value.validate().catch(() => false)
|
|
|
|
|
if (!valid) return
|
|
|
|
|
|
|
|
|
|
submitLoading.value = true
|
|
|
|
|
try {
|
|
|
|
|
const [startDate, endDate] = form.dateRange || []
|
|
|
|
|
const data = {
|
|
|
|
|
periodName: form.periodName,
|
|
|
|
|
description: form.description,
|
|
|
|
|
startDate,
|
|
|
|
|
endDate,
|
|
|
|
|
status: form.status
|
|
|
|
|
}
|
|
|
|
|
if (isEdit.value) {
|
|
|
|
|
await updateSession(editId.value, data)
|
|
|
|
|
ElMessage.success('期次更新成功!')
|
|
|
|
|
} else {
|
|
|
|
|
await addSession(data)
|
|
|
|
|
ElMessage.success('期次新增成功!')
|
|
|
|
|
}
|
|
|
|
|
dialogVisible.value = false
|
|
|
|
|
loadSessionList()
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.error('保存期次失败', e)
|
|
|
|
|
}
|
|
|
|
|
submitLoading.value = false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 删除期次
|
|
|
|
|
const handleDelete = (row) => {
|
|
|
|
|
ElMessageBox.confirm(
|
|
|
|
|
`确定要删除期次"${row.periodName}"吗?删除后不可恢复。`,
|
|
|
|
|
'删除确认',
|
|
|
|
|
{
|
|
|
|
|
confirmButtonText: '确定删除',
|
|
|
|
|
cancelButtonText: '取消',
|
|
|
|
|
type: 'warning'
|
|
|
|
|
}
|
|
|
|
|
).then(async () => {
|
|
|
|
|
try {
|
|
|
|
|
await deleteSession(row.id)
|
|
|
|
|
ElMessage.success('期次已删除!')
|
|
|
|
|
loadSessionList()
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.error('删除期次失败', e)
|
|
|
|
|
}
|
|
|
|
|
}).catch(() => {})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 初始化
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
loadSessionList()
|
|
|
|
|
})
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
.session-manage {
|
|
|
|
|
padding: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.session-toolbar {
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
align-items: center;
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.session-toolbar h3 {
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
color: var(--text-primary);
|
|
|
|
|
margin: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.search-bar {
|
|
|
|
|
display: flex;
|
|
|
|
|
gap: 12px;
|
|
|
|
|
align-items: center;
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
background: #fff;
|
|
|
|
|
padding: 16px;
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.session-table-card {
|
|
|
|
|
background: #fff;
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
padding: 24px;
|
|
|
|
|
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.pagination-wrapper {
|
|
|
|
|
margin-top: 20px;
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: flex-end;
|
|
|
|
|
}
|
|
|
|
|
</style>
|