forked from liweijie/education
恢复最新前端代码
This commit is contained in:
@@ -0,0 +1,531 @@
|
||||
<template>
|
||||
<div class="app-container joint-training-page">
|
||||
<div class="list-title">联教联训管理</div>
|
||||
|
||||
<!-- ==================== 1. 查询条件区域 ==================== -->
|
||||
<el-card shadow="never" class="search-card">
|
||||
<el-form :model="searchForm" label-width="40px" class="search-form">
|
||||
<el-row :gutter="6">
|
||||
<el-col :span="4">
|
||||
<el-form-item label="名称">
|
||||
<el-input v-model="searchForm.mc" placeholder="请输入名称" clearable @keyup.enter.native="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="2">
|
||||
<div class="search-actions">
|
||||
<el-button type="primary" @click="handleQuery">查询</el-button>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<!-- ==================== 2. 操作区域 ==================== -->
|
||||
<el-card shadow="never" class="upload-card">
|
||||
<div class="upload-row">
|
||||
<div class="upload-left">
|
||||
<el-button type="primary" @click="handleAdd">新增联教联训</el-button>
|
||||
<el-button icon="el-icon-download" @click="handleExport">导出 Excel</el-button>
|
||||
<el-button icon="el-icon-download" @click="handleDownloadTemplate">【联教联训数据文件模板】下载</el-button>
|
||||
<el-button icon="el-icon-folder-opened" @click="handleChooseFile">选择文件</el-button>
|
||||
<span class="file-name" :class="{ 'has-file': selectedFile }">{{ fileName }}</span>
|
||||
<input ref="fileInputRef" type="file" accept=".xls,.xlsx" style="display: none" @change="handleFileChange" />
|
||||
<el-button type="primary" icon="el-icon-upload2" :loading="uploading" @click="handleUpload">上传数据</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- ==================== 3. 数据表格 ==================== -->
|
||||
<el-card shadow="never" class="table-card">
|
||||
<div class="table-title">联教联训记录列表</div>
|
||||
<el-table v-loading="tableLoading" :data="tableData" border stripe style="width: 100%">
|
||||
<el-table-column type="index" label="序号" width="55" align="center" />
|
||||
<el-table-column prop="bh" label="编号" width="150" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="mc" label="名称" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column prop="xz" label="性质" width="80" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="yj" label="依据" width="110" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="ksrq" label="开始日期" width="110" align="center" :formatter="fmtDateTime" />
|
||||
<el-table-column prop="jsrq" label="结束日期" width="110" align="center" :formatter="fmtDateTime" />
|
||||
<el-table-column prop="ts" label="天数" width="70" align="center" />
|
||||
<el-table-column prop="jbksl" label="基本课时量" width="90" align="center" />
|
||||
<el-table-column prop="kslx" label="课时类型" width="80" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="xmlx" label="项目类型" width="80" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="sm" label="说明" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column prop="bz" label="备注" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="160" align="center" fixed="right">
|
||||
<template slot-scope="{ row }">
|
||||
<el-button type="text" size="small" @click="handleDetail(row)">详情</el-button>
|
||||
<el-button type="text" size="small" @click="handleEdit(row)">修改</el-button>
|
||||
<el-button type="text" size="small" class="text-danger" @click="handleDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-pagination
|
||||
:current-page="pageNum"
|
||||
:page-size="pageSize"
|
||||
:total="total"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
class="pagination"
|
||||
@current-change="handlePageChange"
|
||||
@size-change="handleSizeChange"
|
||||
/>
|
||||
</el-card>
|
||||
|
||||
<!-- ==================== 4. 新增/修改对话框 ==================== -->
|
||||
<el-dialog
|
||||
:visible="dialogVisible"
|
||||
:title="dialogTitle"
|
||||
width="720px"
|
||||
:close-on-click-modal="false"
|
||||
@update:visible="val => dialogVisible = val"
|
||||
>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="110px" class="add-form">
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="编号">
|
||||
<el-input v-model="form.bh" placeholder="留空则自动生成" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="名称" prop="mc">
|
||||
<el-input v-model="form.mc" placeholder="请输入名称" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="性质">
|
||||
<el-input v-model="form.xz" placeholder="请输入性质" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="依据">
|
||||
<el-input v-model="form.yj" placeholder="请输入依据" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="开始日期">
|
||||
<el-date-picker v-model="form.ksrq" type="datetime" value-format="yyyy-MM-dd HH:mm:ss" placeholder="请选择开始日期" clearable class="w-full" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="结束日期">
|
||||
<el-date-picker v-model="form.jsrq" type="datetime" value-format="yyyy-MM-dd HH:mm:ss" placeholder="请选择结束日期" clearable class="w-full" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="天数">
|
||||
<el-input-number v-model="form.ts" :min="0" :precision="1" :step="1" class="w-full" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="基本课时量">
|
||||
<el-input-number v-model="form.jbksl" :min="0" :precision="1" :step="0.5" class="w-full" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="课时类型">
|
||||
<el-input v-model="form.kslx" placeholder="请输入课时类型" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="项目类型">
|
||||
<el-input v-model="form.xmlx" placeholder="请输入项目类型" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="说明">
|
||||
<el-input v-model="form.sm" type="textarea" :rows="2" placeholder="请输入说明" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="form.bz" type="textarea" :rows="2" placeholder="请输入备注" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer">
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="saving" @click="handleSubmit">确定</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- ==================== 5. 详情对话框 ==================== -->
|
||||
<el-dialog :visible="detailVisible" title="联教联训详情" width="640px" @update:visible="val => detailVisible = val">
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="编号">{{ detailData.bh }}</el-descriptions-item>
|
||||
<el-descriptions-item label="名称">{{ detailData.mc }}</el-descriptions-item>
|
||||
<el-descriptions-item label="性质">{{ fmtEmpty(detailData.xz) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="依据">{{ fmtEmpty(detailData.yj) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="开始日期">{{ fmtDateTime(null, null, detailData.ksrq) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="结束日期">{{ fmtDateTime(null, null, detailData.jsrq) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="天数">{{ fmtValue(detailData.ts) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="基本课时量">{{ fmtValue(detailData.jbksl) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="课时类型">{{ fmtEmpty(detailData.kslx) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="项目类型">{{ fmtEmpty(detailData.xmlx) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="说明" :span="2">{{ fmtEmpty(detailData.sm) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="备注" :span="2">{{ fmtEmpty(detailData.bz) }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<div slot="footer">
|
||||
<el-button @click="detailVisible = false">关闭</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { saveAs } from 'file-saver'
|
||||
import {
|
||||
listJointTraining,
|
||||
addJointTraining,
|
||||
updateJointTraining,
|
||||
deleteJointTraining,
|
||||
getJointTraining,
|
||||
importJointTraining,
|
||||
exportJointTraining
|
||||
} from '@/api/classHour/jointTraining'
|
||||
|
||||
export default {
|
||||
name: 'JointTrainingIndex',
|
||||
data() {
|
||||
return {
|
||||
// ==================== 1. 查询条件 ====================
|
||||
searchForm: {
|
||||
mc: ''
|
||||
},
|
||||
|
||||
// ==================== 2. 文件操作 ====================
|
||||
selectedFile: null,
|
||||
uploading: false,
|
||||
|
||||
// ==================== 3. 表格数据 ====================
|
||||
tableLoading: false,
|
||||
tableData: [],
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
|
||||
// ==================== 4. 新增/修改表单 ====================
|
||||
dialogVisible: false,
|
||||
dialogTitle: '新增联教联训',
|
||||
saving: false,
|
||||
form: this.createEmptyForm(),
|
||||
rules: {
|
||||
mc: [{ required: true, message: '请输入名称', trigger: 'blur' }]
|
||||
},
|
||||
|
||||
// ==================== 5. 详情 ====================
|
||||
detailVisible: false,
|
||||
detailData: {}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
fileName() {
|
||||
return (this.selectedFile && this.selectedFile.name) || '未选择任何文件'
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.fetchList()
|
||||
},
|
||||
methods: {
|
||||
createEmptyForm() {
|
||||
return {
|
||||
bh: '',
|
||||
mc: '',
|
||||
xz: '',
|
||||
yj: '',
|
||||
ksrq: '',
|
||||
jsrq: '',
|
||||
ts: null,
|
||||
jbksl: null,
|
||||
kslx: '',
|
||||
xmlx: '',
|
||||
sm: '',
|
||||
bz: ''
|
||||
}
|
||||
},
|
||||
|
||||
/** 把后端 LocalDateTime(含 T/毫秒)规范为表单可回显的格式 */
|
||||
normalizeDateTime(val) {
|
||||
if (!val) return ''
|
||||
return String(val).replace('T', ' ').replace(/\.\d+$/, '').slice(0, 19)
|
||||
},
|
||||
|
||||
// ==================== 查询 ====================
|
||||
fetchList() {
|
||||
this.tableLoading = true
|
||||
const params = { pageNum: this.pageNum, pageSize: this.pageSize }
|
||||
if (this.searchForm.mc && this.searchForm.mc.trim()) params.mc = this.searchForm.mc.trim()
|
||||
listJointTraining(params).then(res => {
|
||||
const data = (res && res.data) || {}
|
||||
this.tableData = data.records || []
|
||||
this.total = data.total || 0
|
||||
this.tableLoading = false
|
||||
}).catch(() => {
|
||||
this.tableData = []
|
||||
this.total = 0
|
||||
this.tableLoading = false
|
||||
})
|
||||
},
|
||||
|
||||
handleQuery() {
|
||||
this.pageNum = 1
|
||||
this.fetchList()
|
||||
},
|
||||
|
||||
handlePageChange(page) {
|
||||
this.pageNum = page
|
||||
this.fetchList()
|
||||
},
|
||||
|
||||
handleSizeChange(size) {
|
||||
this.pageSize = size
|
||||
this.pageNum = 1
|
||||
this.fetchList()
|
||||
},
|
||||
|
||||
/** 日期时间显示(去掉 T、截断到分钟) */
|
||||
fmtDateTime(row, column, cellValue) {
|
||||
if (cellValue === null || cellValue === undefined || cellValue === '') return '-'
|
||||
return String(cellValue).replace('T', ' ').slice(0, 16)
|
||||
},
|
||||
|
||||
/** 空值显示为 '-' */
|
||||
fmtEmpty(val) {
|
||||
return val === null || val === undefined || val === '' ? '-' : val
|
||||
},
|
||||
|
||||
/** 数值空值显示为 '-'(保留 0) */
|
||||
fmtValue(val) {
|
||||
return val === null || val === undefined ? '-' : val
|
||||
},
|
||||
|
||||
// ==================== 新增 / 修改 ====================
|
||||
handleAdd() {
|
||||
this.form = this.createEmptyForm()
|
||||
this.dialogTitle = '新增联教联训'
|
||||
this.dialogVisible = true
|
||||
},
|
||||
|
||||
handleEdit(row) {
|
||||
getJointTraining(row.bh).then(res => {
|
||||
const d = (res && res.data) || {}
|
||||
this.form = {
|
||||
bh: d.bh || '',
|
||||
mc: d.mc || '',
|
||||
xz: d.xz || '',
|
||||
yj: d.yj || '',
|
||||
ksrq: this.normalizeDateTime(d.ksrq),
|
||||
jsrq: this.normalizeDateTime(d.jsrq),
|
||||
ts: d.ts === null || d.ts === undefined ? null : d.ts,
|
||||
jbksl: d.jbksl === null || d.jbksl === undefined ? null : d.jbksl,
|
||||
kslx: d.kslx || '',
|
||||
xmlx: d.xmlx || '',
|
||||
sm: d.sm || '',
|
||||
bz: d.bz || ''
|
||||
}
|
||||
this.dialogTitle = '修改联教联训'
|
||||
this.dialogVisible = true
|
||||
}).catch(() => {})
|
||||
},
|
||||
|
||||
handleSubmit() {
|
||||
this.$refs.formRef.validate(valid => {
|
||||
if (!valid) {
|
||||
this.$message.warning('请完善必填项后再提交')
|
||||
return
|
||||
}
|
||||
const payload = this.buildPayload()
|
||||
this.saving = true
|
||||
const req = payload.bh ? updateJointTraining(payload) : addJointTraining(payload)
|
||||
req.then(() => {
|
||||
this.$message.success(this.dialogTitle === '修改联教联训' ? '修改成功' : '新增成功')
|
||||
this.dialogVisible = false
|
||||
this.fetchList()
|
||||
}).catch(() => {}).finally(() => {
|
||||
this.saving = false
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
/** 构建请求体,仅含实体非空字段(创建/修改时间为后端自动维护,不传) */
|
||||
buildPayload() {
|
||||
const f = this.form
|
||||
const payload = {}
|
||||
if (f.bh && f.bh.trim()) payload.bh = f.bh.trim()
|
||||
if (f.mc && f.mc.trim()) payload.mc = f.mc.trim()
|
||||
if (f.xz && f.xz.trim()) payload.xz = f.xz.trim()
|
||||
if (f.yj && f.yj.trim()) payload.yj = f.yj.trim()
|
||||
if (f.ksrq) payload.ksrq = f.ksrq
|
||||
if (f.jsrq) payload.jsrq = f.jsrq
|
||||
if (f.ts !== null && f.ts !== undefined) payload.ts = f.ts
|
||||
if (f.jbksl !== null && f.jbksl !== undefined) payload.jbksl = f.jbksl
|
||||
if (f.kslx && f.kslx.trim()) payload.kslx = f.kslx.trim()
|
||||
if (f.xmlx && f.xmlx.trim()) payload.xmlx = f.xmlx.trim()
|
||||
if (f.sm && f.sm.trim()) payload.sm = f.sm.trim()
|
||||
if (f.bz && f.bz.trim()) payload.bz = f.bz.trim()
|
||||
return payload
|
||||
},
|
||||
|
||||
// ==================== 删除 ====================
|
||||
handleDelete(row) {
|
||||
this.$confirm(`确定要删除「${row.mc || row.bh || '该记录'}」吗?`, '系统提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => deleteJointTraining(row.bh)).then(() => {
|
||||
this.$message.success('删除成功')
|
||||
this.fetchList()
|
||||
}).catch(() => {})
|
||||
},
|
||||
|
||||
// ==================== 详情 ====================
|
||||
handleDetail(row) {
|
||||
getJointTraining(row.bh).then(res => {
|
||||
this.detailData = (res && res.data) || {}
|
||||
this.detailVisible = true
|
||||
}).catch(() => {})
|
||||
},
|
||||
|
||||
// ==================== 导出 ====================
|
||||
handleExport() {
|
||||
exportJointTraining().then(blob => {
|
||||
saveAs(blob, '联教联训管理.xlsx')
|
||||
this.$message.success('导出成功')
|
||||
}).catch(() => {})
|
||||
},
|
||||
|
||||
// ==================== 文件选择与导入 ====================
|
||||
handleChooseFile() {
|
||||
this.$refs.fileInputRef && this.$refs.fileInputRef.click()
|
||||
},
|
||||
|
||||
handleFileChange(e) {
|
||||
const input = e.target
|
||||
this.selectedFile = (input.files && input.files[0]) || null
|
||||
},
|
||||
|
||||
handleUpload() {
|
||||
if (!this.selectedFile) {
|
||||
this.$message.warning('请先选择文件')
|
||||
return
|
||||
}
|
||||
this.uploading = true
|
||||
importJointTraining(this.selectedFile).then(res => {
|
||||
const count = res && res.data
|
||||
if (count !== null && count !== undefined) {
|
||||
this.$message.success(`导入成功,共 ${count} 条记录`)
|
||||
} else {
|
||||
this.$message.success((res && res.msg) || '导入成功')
|
||||
}
|
||||
this.selectedFile = null
|
||||
this.$refs.fileInputRef && (this.$refs.fileInputRef.value = '')
|
||||
this.pageNum = 1
|
||||
this.fetchList()
|
||||
}).catch(() => {}).finally(() => {
|
||||
this.uploading = false
|
||||
})
|
||||
},
|
||||
|
||||
handleDownloadTemplate() {
|
||||
// 后端暂未提供模板下载接口,仅作提示
|
||||
this.$message.info('后端暂未提供该接口')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.app-container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.joint-training-page {
|
||||
.list-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
// ==================== 1. 查询条件区域 ====================
|
||||
.search-card {
|
||||
margin-bottom: 16px;
|
||||
|
||||
.search-form {
|
||||
.w-full {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.search-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
height: 40px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 2. 操作区域 ====================
|
||||
.upload-card {
|
||||
margin-bottom: 16px;
|
||||
|
||||
.upload-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
|
||||
.upload-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.file-name {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
|
||||
&.has-file {
|
||||
color: #303133;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 3. 数据表格 ====================
|
||||
.table-card {
|
||||
margin-bottom: 16px;
|
||||
|
||||
.table-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.text-danger {
|
||||
color: #f56c6c;
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 4. 新增/修改表单 ====================
|
||||
.add-form {
|
||||
max-height: 62vh;
|
||||
overflow-y: auto;
|
||||
padding-right: 4px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,679 @@
|
||||
<template>
|
||||
<div class="app-container plan-page">
|
||||
|
||||
<!-- ==================== Tab 切换 ==================== -->
|
||||
<el-tabs v-model="activeTab" @tab-click="handleTabClick">
|
||||
<el-tab-pane label="课时费方案" name="fee" />
|
||||
<el-tab-pane label="课时方案" name="coeff" />
|
||||
</el-tabs>
|
||||
|
||||
<!-- ==================== 1. 查询与新增 ==================== -->
|
||||
<el-card shadow="never" class="search-card">
|
||||
<el-form :inline="true" class="search-form" @submit.native.prevent>
|
||||
<el-form-item label="名称">
|
||||
<el-input v-model="currentForm.mc" placeholder="请输入名称" clearable style="width: 220px" @keyup.enter.native="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleQuery">查询</el-button>
|
||||
<el-button type="primary" plain @click="handleAdd">新增{{ currentTitle }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<!-- ==================== 2. 数据表格 ==================== -->
|
||||
<el-card shadow="never" class="table-card">
|
||||
<!-- 课时费方案 -->
|
||||
<el-table v-if="activeTab === 'fee'" v-loading="feeState.loading" :data="feeState.list" border stripe style="width: 100%">
|
||||
<el-table-column type="index" label="序号" width="55" align="center" />
|
||||
<el-table-column prop="bh" label="编号" width="150" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="mc" label="名称" min-width="130" show-overflow-tooltip />
|
||||
<el-table-column prop="mrbzksl" label="默认标准课时量" width="120" align="right">
|
||||
<template slot-scope="{ row }">{{ fmtValue(row.mrbzksl) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="mrksfbz" label="默认课时费标准" width="120" align="right">
|
||||
<template slot-scope="{ row }">{{ fmtValue(row.mrksfbz) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="mrclksfbz" label="默认超量课时费标准" width="150" align="right">
|
||||
<template slot-scope="{ row }">{{ fmtValue(row.mrclksfbz) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="xgjxbzksfbz" label="相关教学补助课时费标准" width="180" align="right">
|
||||
<template slot-scope="{ row }">{{ fmtValue(row.xgjxbzksfbz) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="jxyxbz" label="绩效优秀补助" width="110" align="right">
|
||||
<template slot-scope="{ row }">{{ fmtValue(row.jxyxbz) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="jxlhbz" label="绩效良好补助" width="110" align="right">
|
||||
<template slot-scope="{ row }">{{ fmtValue(row.jxlhbz) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="jxzdbz" label="绩效中等补助" width="110" align="right">
|
||||
<template slot-scope="{ row }">{{ fmtValue(row.jxzdbz) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="jxjgbz" label="绩效及格补助" width="110" align="right">
|
||||
<template slot-scope="{ row }">{{ fmtValue(row.jxjgbz) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="jxbjgbz" label="绩效不及格补助" width="120" align="right">
|
||||
<template slot-scope="{ row }">{{ fmtValue(row.jxbjgbz) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="sm" label="说明" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="160" align="center" fixed="right">
|
||||
<template slot-scope="{ row }">
|
||||
<el-button type="text" size="small" @click="handleDetail(row)">详情</el-button>
|
||||
<el-button type="text" size="small" @click="handleEdit(row)">修改</el-button>
|
||||
<el-button type="text" size="small" class="text-danger" @click="handleDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 课时方案 -->
|
||||
<el-table v-else v-loading="coeffState.loading" :data="coeffState.list" border stripe style="width: 100%">
|
||||
<el-table-column type="index" label="序号" width="55" align="center" />
|
||||
<el-table-column prop="bh" label="编号" width="150" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="mc" label="名称" min-width="130" show-overflow-tooltip />
|
||||
<el-table-column prop="zjksxs" label="主讲课时系数" width="110" align="right">
|
||||
<template slot-scope="{ row }">{{ fmtValue(row.zjksxs) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="fjksxs" label="辅讲课时系数" width="110" align="right">
|
||||
<template slot-scope="{ row }">{{ fmtValue(row.fjksxs) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="mbxyrs" label="满班学员人数" width="110" align="right">
|
||||
<template slot-scope="{ row }">{{ fmtValue(row.mbxyrs) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="dz10xybjxs" label="递增10学员班级系数" width="150" align="right">
|
||||
<template slot-scope="{ row }">{{ fmtValue(row.dz10xybjxs) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="bjxssx" label="班级系数上限" width="110" align="right">
|
||||
<template slot-scope="{ row }">{{ fmtValue(row.bjxssx) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="d78jksl" label="第78节课时量" width="110" align="right">
|
||||
<template slot-scope="{ row }">{{ fmtValue(row.d78jksl) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="yjksl" label="夜间课时量" width="100" align="right">
|
||||
<template slot-scope="{ row }">{{ fmtValue(row.yjksl) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="mrktjxffxs" label="默认课堂教法系数" width="130" align="right">
|
||||
<template slot-scope="{ row }">{{ fmtValue(row.mrktjxffxs) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="hbxszl" label="合班系数增量" width="110" align="right">
|
||||
<template slot-scope="{ row }">{{ fmtValue(row.hbxszl) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="hbxssx" label="合班系数上限" width="110" align="right">
|
||||
<template slot-scope="{ row }">{{ fmtValue(row.hbxssx) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="sm" label="说明" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="120" align="center" fixed="right">
|
||||
<template slot-scope="{ row }">
|
||||
<el-button type="text" size="small" @click="handleDetail(row)">详情</el-button>
|
||||
<el-button type="text" size="small" @click="handleEdit(row)">修改</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-pagination
|
||||
:current-page="currentState.pageNum"
|
||||
:page-size="currentState.pageSize"
|
||||
:total="currentState.total"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
class="pagination"
|
||||
@current-change="handlePageChange"
|
||||
@size-change="handleSizeChange"
|
||||
/>
|
||||
</el-card>
|
||||
|
||||
<!-- ==================== 3. 新增/修改对话框 ==================== -->
|
||||
<el-dialog
|
||||
:visible="dialogVisible"
|
||||
:title="dialogTitle"
|
||||
width="760px"
|
||||
:close-on-click-modal="false"
|
||||
@update:visible="val => dialogVisible = val"
|
||||
>
|
||||
<!-- 课时费方案表单 -->
|
||||
<el-form v-if="activeTab === 'fee'" ref="feeFormRef" :model="feeForm" :rules="feeRules" label-width="160px" class="add-form">
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="编号">
|
||||
<el-input v-model="feeForm.bh" placeholder="留空则自动生成" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="名称" prop="mc">
|
||||
<el-input v-model="feeForm.mc" placeholder="请输入名称" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="默认标准课时量" prop="mrbzksl">
|
||||
<el-input-number v-model="feeForm.mrbzksl" :min="0" :precision="2" :step="1" class="w-full" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="默认课时费标准" prop="mrksfbz">
|
||||
<el-input-number v-model="feeForm.mrksfbz" :min="0" :precision="2" :step="1" class="w-full" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="默认超量课时费标准" prop="mrclksfbz">
|
||||
<el-input-number v-model="feeForm.mrclksfbz" :min="0" :precision="2" :step="1" class="w-full" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="相关教学补助课时费标准" prop="xgjxbzksfbz">
|
||||
<el-input-number v-model="feeForm.xgjxbzksfbz" :min="0" :precision="2" :step="1" class="w-full" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="绩效优秀补助" prop="jxyxbz">
|
||||
<el-input-number v-model="feeForm.jxyxbz" :min="0" :precision="2" :step="1" class="w-full" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="绩效良好补助" prop="jxlhbz">
|
||||
<el-input-number v-model="feeForm.jxlhbz" :min="0" :precision="2" :step="1" class="w-full" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="绩效中等补助" prop="jxzdbz">
|
||||
<el-input-number v-model="feeForm.jxzdbz" :min="0" :precision="2" :step="1" class="w-full" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="绩效及格补助" prop="jxjgbz">
|
||||
<el-input-number v-model="feeForm.jxjgbz" :min="0" :precision="2" :step="1" class="w-full" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="绩效不及格补助" prop="jxbjgbz">
|
||||
<el-input-number v-model="feeForm.jxbjgbz" :min="0" :precision="2" :step="1" class="w-full" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="说明">
|
||||
<el-input v-model="feeForm.sm" type="textarea" :rows="2" placeholder="请输入说明" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="feeForm.bz" type="textarea" :rows="2" placeholder="请输入备注" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- 课时方案表单 -->
|
||||
<el-form v-else ref="coeffFormRef" :model="coeffForm" :rules="coeffRules" label-width="160px" class="add-form">
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="编号">
|
||||
<el-input v-model="coeffForm.bh" placeholder="留空则自动生成" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="名称" prop="mc">
|
||||
<el-input v-model="coeffForm.mc" placeholder="请输入名称" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="主讲课时系数">
|
||||
<el-input-number v-model="coeffForm.zjksxs" :min="0" :precision="2" :step="0.1" class="w-full" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="辅讲课时系数">
|
||||
<el-input-number v-model="coeffForm.fjksxs" :min="0" :precision="2" :step="0.1" class="w-full" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="满班学员人数">
|
||||
<el-input-number v-model="coeffForm.mbxyrs" :min="0" :precision="0" :step="1" class="w-full" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="递增10学员班级系数">
|
||||
<el-input-number v-model="coeffForm.dz10xybjxs" :min="0" :precision="2" :step="0.1" class="w-full" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="班级系数上限">
|
||||
<el-input-number v-model="coeffForm.bjxssx" :min="0" :precision="2" :step="0.1" class="w-full" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="第78节课时量">
|
||||
<el-input-number v-model="coeffForm.d78jksl" :min="0" :precision="2" :step="1" class="w-full" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="夜间课时量">
|
||||
<el-input-number v-model="coeffForm.yjksl" :min="0" :precision="2" :step="1" class="w-full" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="默认课堂教学方法系数">
|
||||
<el-input-number v-model="coeffForm.mrktjxffxs" :min="0" :precision="2" :step="0.1" class="w-full" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="合班系数增量">
|
||||
<el-input-number v-model="coeffForm.hbxszl" :min="0" :precision="2" :step="0.1" class="w-full" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="合班系数上限">
|
||||
<el-input-number v-model="coeffForm.hbxssx" :min="0" :precision="2" :step="0.1" class="w-full" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="说明">
|
||||
<el-input v-model="coeffForm.sm" type="textarea" :rows="2" placeholder="请输入说明" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="coeffForm.bz" type="textarea" :rows="2" placeholder="请输入备注" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<div slot="footer">
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="saving" @click="handleSubmit">确定</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- ==================== 4. 详情对话框 ==================== -->
|
||||
<el-dialog :visible="detailVisible" :title="`${currentTitle}详情`" width="680px" @update:visible="val => detailVisible = val">
|
||||
<el-descriptions v-if="activeTab === 'fee'" :column="2" border>
|
||||
<el-descriptions-item label="编号">{{ detailData.bh }}</el-descriptions-item>
|
||||
<el-descriptions-item label="名称">{{ detailData.mc }}</el-descriptions-item>
|
||||
<el-descriptions-item label="默认标准课时量">{{ fmtValue(detailData.mrbzksl) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="默认课时费标准">{{ fmtValue(detailData.mrksfbz) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="默认超量课时费标准">{{ fmtValue(detailData.mrclksfbz) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="相关教学补助课时费标准">{{ fmtValue(detailData.xgjxbzksfbz) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="绩效优秀补助">{{ fmtValue(detailData.jxyxbz) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="绩效良好补助">{{ fmtValue(detailData.jxlhbz) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="绩效中等补助">{{ fmtValue(detailData.jxzdbz) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="绩效及格补助">{{ fmtValue(detailData.jxjgbz) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="绩效不及格补助">{{ fmtValue(detailData.jxbjgbz) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">{{ fmtEmpty(detailData.cjsj) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="说明" :span="2">{{ fmtEmpty(detailData.sm) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="备注" :span="2">{{ fmtEmpty(detailData.bz) }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<el-descriptions v-else :column="2" border>
|
||||
<el-descriptions-item label="编号">{{ detailData.bh }}</el-descriptions-item>
|
||||
<el-descriptions-item label="名称">{{ detailData.mc }}</el-descriptions-item>
|
||||
<el-descriptions-item label="主讲课时系数">{{ fmtValue(detailData.zjksxs) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="辅讲课时系数">{{ fmtValue(detailData.fjksxs) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="满班学员人数">{{ fmtValue(detailData.mbxyrs) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="递增10学员班级系数">{{ fmtValue(detailData.dz10xybjxs) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="班级系数上限">{{ fmtValue(detailData.bjxssx) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="第78节课时量">{{ fmtValue(detailData.d78jksl) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="夜间课时量">{{ fmtValue(detailData.yjksl) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="默认课堂教学方法系数">{{ fmtValue(detailData.mrktjxffxs) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="合班系数增量">{{ fmtValue(detailData.hbxszl) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="合班系数上限">{{ fmtValue(detailData.hbxssx) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">{{ fmtEmpty(detailData.cjsj) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="说明" :span="2">{{ fmtEmpty(detailData.sm) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="备注" :span="2">{{ fmtEmpty(detailData.bz) }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<div slot="footer">
|
||||
<el-button @click="detailVisible = false">关闭</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
listFeeStandard,
|
||||
getFeeStandard,
|
||||
addFeeStandard,
|
||||
updateFeeStandard,
|
||||
deleteFeeStandard,
|
||||
listCoefficientPlan,
|
||||
getCoefficientPlan,
|
||||
addCoefficientPlan,
|
||||
updateCoefficientPlan
|
||||
} from '@/api/classHour/plan'
|
||||
|
||||
export default {
|
||||
name: 'ClassHourPlan',
|
||||
data() {
|
||||
return {
|
||||
// ==================== Tab ====================
|
||||
activeTab: 'fee',
|
||||
|
||||
// ==================== 查询条件 ====================
|
||||
feeSearch: { mc: '' },
|
||||
coeffSearch: { mc: '' },
|
||||
|
||||
// ==================== 列表状态 ====================
|
||||
feeState: this.createEmptyState(),
|
||||
coeffState: this.createEmptyState(),
|
||||
|
||||
// ==================== 新增/修改 ====================
|
||||
dialogVisible: false,
|
||||
dialogTitle: '',
|
||||
saving: false,
|
||||
feeForm: this.createEmptyFeeForm(),
|
||||
coeffForm: this.createEmptyCoeffForm(),
|
||||
feeRules: {
|
||||
mc: [{ required: true, message: '请输入名称', trigger: 'blur' }],
|
||||
mrbzksl: [{ required: true, message: '请输入默认标准课时量', trigger: 'blur' }],
|
||||
mrksfbz: [{ required: true, message: '请输入默认课时费标准', trigger: 'blur' }],
|
||||
mrclksfbz: [{ required: true, message: '请输入默认超量课时费标准', trigger: 'blur' }],
|
||||
xgjxbzksfbz: [{ required: true, message: '请输入相关教学补助课时费标准', trigger: 'blur' }],
|
||||
jxyxbz: [{ required: true, message: '请输入绩效优秀补助', trigger: 'blur' }],
|
||||
jxlhbz: [{ required: true, message: '请输入绩效良好补助', trigger: 'blur' }],
|
||||
jxzdbz: [{ required: true, message: '请输入绩效中等补助', trigger: 'blur' }],
|
||||
jxjgbz: [{ required: true, message: '请输入绩效及格补助', trigger: 'blur' }],
|
||||
jxbjgbz: [{ required: true, message: '请输入绩效不及格补助', trigger: 'blur' }]
|
||||
},
|
||||
coeffRules: {
|
||||
mc: [{ required: true, message: '请输入名称', trigger: 'blur' }]
|
||||
},
|
||||
|
||||
// ==================== 详情 ====================
|
||||
detailVisible: false,
|
||||
detailData: {}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
currentForm() {
|
||||
return this.activeTab === 'fee' ? this.feeSearch : this.coeffSearch
|
||||
},
|
||||
currentState() {
|
||||
return this.activeTab === 'fee' ? this.feeState : this.coeffState
|
||||
},
|
||||
currentTitle() {
|
||||
return this.activeTab === 'fee' ? '课时费方案' : '课时方案'
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.fetchList()
|
||||
},
|
||||
methods: {
|
||||
createEmptyState() {
|
||||
return { list: [], loading: false, pageNum: 1, pageSize: 10, total: 0 }
|
||||
},
|
||||
|
||||
createEmptyFeeForm() {
|
||||
return {
|
||||
bh: '',
|
||||
mc: '',
|
||||
mrbzksl: null,
|
||||
mrksfbz: null,
|
||||
mrclksfbz: null,
|
||||
xgjxbzksfbz: null,
|
||||
jxyxbz: null,
|
||||
jxlhbz: null,
|
||||
jxzdbz: null,
|
||||
jxjgbz: null,
|
||||
jxbjgbz: null,
|
||||
sm: '',
|
||||
bz: ''
|
||||
}
|
||||
},
|
||||
|
||||
createEmptyCoeffForm() {
|
||||
return {
|
||||
bh: '',
|
||||
mc: '',
|
||||
zjksxs: null,
|
||||
fjksxs: null,
|
||||
mbxyrs: null,
|
||||
dz10xybjxs: null,
|
||||
bjxssx: null,
|
||||
d78jksl: null,
|
||||
yjksl: null,
|
||||
mrktjxffxs: null,
|
||||
hbxszl: null,
|
||||
hbxssx: null,
|
||||
sm: '',
|
||||
bz: ''
|
||||
}
|
||||
},
|
||||
|
||||
// ==================== 查询 ====================
|
||||
fetchList() {
|
||||
const state = this.currentState
|
||||
const params = { pageNum: state.pageNum, pageSize: state.pageSize }
|
||||
if (this.currentForm.mc && this.currentForm.mc.trim()) params.mc = this.currentForm.mc.trim()
|
||||
state.loading = true
|
||||
const req = this.activeTab === 'fee' ? listFeeStandard(params) : listCoefficientPlan(params)
|
||||
req.then(res => {
|
||||
const data = (res && res.data) || {}
|
||||
state.list = data.records || []
|
||||
state.total = data.total || 0
|
||||
}).catch(() => {
|
||||
state.list = []
|
||||
state.total = 0
|
||||
}).finally(() => {
|
||||
state.loading = false
|
||||
})
|
||||
},
|
||||
|
||||
handleTabClick() {
|
||||
this.currentState.pageNum = 1
|
||||
this.fetchList()
|
||||
},
|
||||
|
||||
handleQuery() {
|
||||
this.currentState.pageNum = 1
|
||||
this.fetchList()
|
||||
},
|
||||
|
||||
handlePageChange(page) {
|
||||
this.currentState.pageNum = page
|
||||
this.fetchList()
|
||||
},
|
||||
|
||||
handleSizeChange(size) {
|
||||
this.currentState.pageSize = size
|
||||
this.currentState.pageNum = 1
|
||||
this.fetchList()
|
||||
},
|
||||
|
||||
// ==================== 新增 / 修改 ====================
|
||||
handleAdd() {
|
||||
this.feeForm = this.createEmptyFeeForm()
|
||||
this.coeffForm = this.createEmptyCoeffForm()
|
||||
this.dialogTitle = `新增${this.currentTitle}`
|
||||
this.dialogVisible = true
|
||||
},
|
||||
|
||||
handleEdit(row) {
|
||||
const req = this.activeTab === 'fee' ? getFeeStandard(row.bh) : getCoefficientPlan(row.bh)
|
||||
req.then(res => {
|
||||
const d = (res && res.data) || {}
|
||||
if (this.activeTab === 'fee') {
|
||||
this.feeForm = {
|
||||
bh: d.bh || '',
|
||||
mc: d.mc || '',
|
||||
mrbzksl: this.toNull(d.mrbzksl),
|
||||
mrksfbz: this.toNull(d.mrksfbz),
|
||||
mrclksfbz: this.toNull(d.mrclksfbz),
|
||||
xgjxbzksfbz: this.toNull(d.xgjxbzksfbz),
|
||||
jxyxbz: this.toNull(d.jxyxbz),
|
||||
jxlhbz: this.toNull(d.jxlhbz),
|
||||
jxzdbz: this.toNull(d.jxzdbz),
|
||||
jxjgbz: this.toNull(d.jxjgbz),
|
||||
jxbjgbz: this.toNull(d.jxbjgbz),
|
||||
sm: d.sm || '',
|
||||
bz: d.bz || ''
|
||||
}
|
||||
} else {
|
||||
this.coeffForm = {
|
||||
bh: d.bh || '',
|
||||
mc: d.mc || '',
|
||||
zjksxs: this.toNull(d.zjksxs),
|
||||
fjksxs: this.toNull(d.fjksxs),
|
||||
mbxyrs: this.toNull(d.mbxyrs),
|
||||
dz10xybjxs: this.toNull(d.dz10xybjxs),
|
||||
bjxssx: this.toNull(d.bjxssx),
|
||||
d78jksl: this.toNull(d.d78jksl),
|
||||
yjksl: this.toNull(d.yjksl),
|
||||
mrktjxffxs: this.toNull(d.mrktjxffxs),
|
||||
hbxszl: this.toNull(d.hbxszl),
|
||||
hbxssx: this.toNull(d.hbxssx),
|
||||
sm: d.sm || '',
|
||||
bz: d.bz || ''
|
||||
}
|
||||
}
|
||||
this.dialogTitle = `修改${this.currentTitle}`
|
||||
this.dialogVisible = true
|
||||
}).catch(() => {})
|
||||
},
|
||||
|
||||
handleSubmit() {
|
||||
const isFee = this.activeTab === 'fee'
|
||||
const formRef = isFee ? this.$refs.feeFormRef : this.$refs.coeffFormRef
|
||||
formRef.validate(valid => {
|
||||
if (!valid) {
|
||||
this.$message.warning('请完善必填项后再提交')
|
||||
return
|
||||
}
|
||||
const payload = isFee ? this.buildFeePayload() : this.buildCoeffPayload()
|
||||
this.saving = true
|
||||
const req = payload.bh
|
||||
? (isFee ? updateFeeStandard(payload) : updateCoefficientPlan(payload))
|
||||
: (isFee ? addFeeStandard(payload) : addCoefficientPlan(payload))
|
||||
req.then(() => {
|
||||
this.$message.success(this.dialogTitle.indexOf('修改') === 0 ? '修改成功' : '新增成功')
|
||||
this.dialogVisible = false
|
||||
this.fetchList()
|
||||
}).catch(() => {}).finally(() => {
|
||||
this.saving = false
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
toNull(val) {
|
||||
return val === null || val === undefined ? null : val
|
||||
},
|
||||
|
||||
/** 课时费方案请求体:仅含实体非空字段(创建/修改时间后端自动维护) */
|
||||
buildFeePayload() {
|
||||
const f = this.feeForm
|
||||
const p = {}
|
||||
if (f.bh && f.bh.trim()) p.bh = f.bh.trim()
|
||||
if (f.mc && f.mc.trim()) p.mc = f.mc.trim()
|
||||
;['mrbzksl', 'mrksfbz', 'mrclksfbz', 'xgjxbzksfbz', 'jxyxbz', 'jxlhbz', 'jxzdbz', 'jxjgbz', 'jxbjgbz'].forEach(k => {
|
||||
if (f[k] !== null && f[k] !== undefined) p[k] = f[k]
|
||||
})
|
||||
if (f.sm && f.sm.trim()) p.sm = f.sm.trim()
|
||||
if (f.bz && f.bz.trim()) p.bz = f.bz.trim()
|
||||
return p
|
||||
},
|
||||
|
||||
/** 课时方案请求体:仅含实体非空字段 */
|
||||
buildCoeffPayload() {
|
||||
const f = this.coeffForm
|
||||
const p = {}
|
||||
if (f.bh && f.bh.trim()) p.bh = f.bh.trim()
|
||||
if (f.mc && f.mc.trim()) p.mc = f.mc.trim()
|
||||
;['zjksxs', 'fjksxs', 'mbxyrs', 'dz10xybjxs', 'bjxssx', 'd78jksl', 'yjksl', 'mrktjxffxs', 'hbxszl', 'hbxssx'].forEach(k => {
|
||||
if (f[k] !== null && f[k] !== undefined) p[k] = f[k]
|
||||
})
|
||||
if (f.sm && f.sm.trim()) p.sm = f.sm.trim()
|
||||
if (f.bz && f.bz.trim()) p.bz = f.bz.trim()
|
||||
return p
|
||||
},
|
||||
|
||||
// ==================== 删除(仅课时费方案有删除接口) ====================
|
||||
handleDelete(row) {
|
||||
this.$confirm(`确定要删除「${row.mc || row.bh || '该方案'}」吗?`, '系统提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => deleteFeeStandard(row.bh)).then(() => {
|
||||
this.$message.success('删除成功')
|
||||
this.fetchList()
|
||||
}).catch(() => {})
|
||||
},
|
||||
|
||||
// ==================== 详情 ====================
|
||||
handleDetail(row) {
|
||||
const req = this.activeTab === 'fee' ? getFeeStandard(row.bh) : getCoefficientPlan(row.bh)
|
||||
req.then(res => {
|
||||
this.detailData = (res && res.data) || {}
|
||||
this.detailVisible = true
|
||||
}).catch(() => {})
|
||||
},
|
||||
|
||||
/** 数值空值显示为 '-'(保留 0) */
|
||||
fmtValue(val) {
|
||||
return val === null || val === undefined ? '-' : val
|
||||
},
|
||||
|
||||
/** 空值显示为 '-' */
|
||||
fmtEmpty(val) {
|
||||
return val === null || val === undefined || val === '' ? '-' : val
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.app-container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.plan-page {
|
||||
.list-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
// ==================== 1. 查询区域 ====================
|
||||
.search-card {
|
||||
margin-bottom: 16px;
|
||||
|
||||
.search-form {
|
||||
.el-form-item {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 2. 数据表格 ====================
|
||||
.table-card {
|
||||
.pagination {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.text-danger {
|
||||
color: #f56c6c;
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 3. 新增/修改表单 ====================
|
||||
.add-form {
|
||||
max-height: 62vh;
|
||||
overflow-y: auto;
|
||||
padding-right: 4px;
|
||||
|
||||
.w-full {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,299 @@
|
||||
<template>
|
||||
<div class="app-container subsidy-page">
|
||||
<div class="list-title">课时补助核算</div>
|
||||
|
||||
<!-- ==================== Tab 切换 ==================== -->
|
||||
<el-tabs v-model="activeTab" @tab-click="handleTabClick">
|
||||
<el-tab-pane label="课时统计" name="hour" />
|
||||
<el-tab-pane label="课时费统计" name="fee" />
|
||||
</el-tabs>
|
||||
|
||||
<!-- ==================== 1. 查询条件 ==================== -->
|
||||
<!-- 课时统计 -->
|
||||
<el-card v-if="activeTab === 'hour'" shadow="never" class="search-card">
|
||||
<el-form :model="hourForm" label-width="100px" class="search-form" @submit.native.prevent>
|
||||
<el-row :gutter="24">
|
||||
<el-col :xs="24" :sm="12" :md="6">
|
||||
<el-form-item label="学期年度">
|
||||
<el-input v-model="hourForm.nd" placeholder="如 2026" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="6">
|
||||
<el-form-item label="学期序号">
|
||||
<el-input v-model="hourForm.xq" placeholder="如 1" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="6">
|
||||
<el-form-item label="教员姓名">
|
||||
<el-input v-model="hourForm.jyxm" placeholder="请输入教员姓名" clearable @keyup.enter.native="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="6" class="search-actions-col">
|
||||
<div class="search-actions">
|
||||
<el-button type="primary" @click="handleQuery">查询</el-button>
|
||||
<el-button type="primary" plain @click="handleExport">导出 Excel</el-button>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<!-- 课时费统计 -->
|
||||
<el-card v-else shadow="never" class="search-card">
|
||||
<el-form :model="feeForm" label-width="100px" class="search-form" @submit.native.prevent>
|
||||
<el-row :gutter="24">
|
||||
<el-col :xs="24" :sm="12" :md="6">
|
||||
<el-form-item label="学期年度">
|
||||
<el-input v-model="feeForm.nd" placeholder="如 2026" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="6">
|
||||
<el-form-item label="学期序号">
|
||||
<el-input v-model="feeForm.xq" placeholder="如 1" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="6">
|
||||
<el-form-item label="教员姓名">
|
||||
<el-input v-model="feeForm.jyxm" placeholder="请输入教员姓名" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="6">
|
||||
<el-form-item label="标准编号">
|
||||
<el-input v-model="feeForm.ksfbzbh" placeholder="课时费标准编号" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" class="search-actions-col">
|
||||
<div class="search-actions">
|
||||
<el-button type="primary" @click="handleQuery">查询</el-button>
|
||||
<el-button type="primary" plain @click="handleExport">导出 Excel</el-button>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<!-- ==================== 2. 数据表格 ==================== -->
|
||||
<el-card shadow="never" class="table-card">
|
||||
<div class="table-title">{{ activeTab === 'hour' ? '课时统计列表' : '课时费统计列表' }}</div>
|
||||
<el-table v-loading="currentState.loading" :data="currentState.list" border stripe style="width: 100%">
|
||||
<el-table-column type="index" label="序号" width="55" align="center" />
|
||||
<el-table-column prop="jysdh" label="教研室代号" width="100" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="jysmc" label="教研室名称" width="120" show-overflow-tooltip />
|
||||
<el-table-column prop="jybh" label="教员编号" width="110" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="jyxm" label="教员姓名" width="100" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="zc" label="职称" width="90" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="nf" label="年份" width="70" align="center" />
|
||||
<el-table-column prop="sbn" label="上半年" width="90" align="right" header-align="center">
|
||||
<template slot-scope="{ row }">{{ fmtValue(row.sbn) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="xbn" label="下半年" width="90" align="right" header-align="center">
|
||||
<template slot-scope="{ row }">{{ fmtValue(row.xbn) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="zj" label="总计" width="90" align="right" header-align="center">
|
||||
<template slot-scope="{ row }">{{ fmtValue(row.zj) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="bzksl" label="标准课时量" width="100" align="right" header-align="center">
|
||||
<template slot-scope="{ row }">{{ fmtValue(row.bzksl) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="cks" label="超课时" width="90" align="right" header-align="center">
|
||||
<template slot-scope="{ row }">{{ fmtValue(row.cks) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="jsgs" label="计算过程" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column prop="jg" label="结果" width="90" align="right" header-align="center">
|
||||
<template slot-scope="{ row }">{{ fmtValue(row.jg) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="ksfbzmc" label="课时费标准" width="110" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="ksfbz" label="课时费单价" width="100" align="right" header-align="center">
|
||||
<template slot-scope="{ row }">{{ fmtValue(row.ksfbz) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="clksfbz" label="超量课时费单价" width="110" align="right" header-align="center">
|
||||
<template slot-scope="{ row }">{{ fmtValue(row.clksfbz) }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-pagination
|
||||
:current-page="currentState.pageNum"
|
||||
:page-size="currentState.pageSize"
|
||||
:total="currentState.total"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
class="pagination"
|
||||
@current-change="handlePageChange"
|
||||
@size-change="handleSizeChange"
|
||||
/>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { saveAs } from 'file-saver'
|
||||
import {
|
||||
listHourStatistics,
|
||||
exportHourStatistics,
|
||||
listFeeStatistics,
|
||||
exportFeeStatistics
|
||||
} from '@/api/classHour/hourStat'
|
||||
|
||||
export default {
|
||||
name: 'SubsidyIndex',
|
||||
data() {
|
||||
return {
|
||||
// ==================== Tab ====================
|
||||
activeTab: 'hour',
|
||||
|
||||
// ==================== 课时统计查询条件 ====================
|
||||
hourForm: {
|
||||
nd: '',
|
||||
xq: '',
|
||||
jyxm: ''
|
||||
},
|
||||
|
||||
// ==================== 课时费统计查询条件 ====================
|
||||
feeForm: {
|
||||
nd: '',
|
||||
xq: '',
|
||||
jyxm: '',
|
||||
ksfbzbh: ''
|
||||
},
|
||||
|
||||
// ==================== 各 Tab 列表状态 ====================
|
||||
hourState: this.createEmptyState(),
|
||||
feeState: this.createEmptyState()
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
currentForm() {
|
||||
return this.activeTab === 'hour' ? this.hourForm : this.feeForm
|
||||
},
|
||||
currentState() {
|
||||
return this.activeTab === 'hour' ? this.hourState : this.feeState
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.fetchList()
|
||||
},
|
||||
methods: {
|
||||
createEmptyState() {
|
||||
return { list: [], loading: false, pageNum: 1, pageSize: 20, total: 0 }
|
||||
},
|
||||
|
||||
/** 构建查询参数,仅传非空字段(与后端 Query 字段一致) */
|
||||
buildParams(form) {
|
||||
const params = {}
|
||||
if (form.nd && form.nd.trim()) params.nd = form.nd.trim()
|
||||
if (form.xq && form.xq.trim()) params.xq = form.xq.trim()
|
||||
if (form.jyxm && form.jyxm.trim()) params.jyxm = form.jyxm.trim()
|
||||
if (form.ksfbzbh && form.ksfbzbh.trim()) params.ksfbzbh = form.ksfbzbh.trim()
|
||||
return params
|
||||
},
|
||||
|
||||
// ==================== 查询 ====================
|
||||
fetchList() {
|
||||
const state = this.currentState
|
||||
const form = this.currentForm
|
||||
const params = Object.assign(
|
||||
{ pageNum: state.pageNum, pageSize: state.pageSize },
|
||||
this.buildParams(form)
|
||||
)
|
||||
state.loading = true
|
||||
const req = this.activeTab === 'hour' ? listHourStatistics(params) : listFeeStatistics(params)
|
||||
req.then(res => {
|
||||
const data = (res && res.data) || {}
|
||||
state.list = data.records || []
|
||||
state.total = data.total || 0
|
||||
}).catch(() => {
|
||||
state.list = []
|
||||
state.total = 0
|
||||
}).finally(() => {
|
||||
state.loading = false
|
||||
})
|
||||
},
|
||||
|
||||
handleTabClick() {
|
||||
this.currentState.pageNum = 1
|
||||
this.fetchList()
|
||||
},
|
||||
|
||||
handleQuery() {
|
||||
this.currentState.pageNum = 1
|
||||
this.fetchList()
|
||||
},
|
||||
|
||||
handlePageChange(page) {
|
||||
this.currentState.pageNum = page
|
||||
this.fetchList()
|
||||
},
|
||||
|
||||
handleSizeChange(size) {
|
||||
this.currentState.pageSize = size
|
||||
this.currentState.pageNum = 1
|
||||
this.fetchList()
|
||||
},
|
||||
|
||||
// ==================== 导出 ====================
|
||||
handleExport() {
|
||||
const clean = this.buildParams(this.currentForm)
|
||||
const isHour = this.activeTab === 'hour'
|
||||
const req = isHour ? exportHourStatistics(clean) : exportFeeStatistics(clean)
|
||||
const fileName = isHour ? '课时统计.xlsx' : '课时费统计.xlsx'
|
||||
req.then(blob => {
|
||||
saveAs(blob, fileName)
|
||||
this.$message.success('导出成功')
|
||||
}).catch(() => {})
|
||||
},
|
||||
|
||||
/** 数值空值显示为 '-'(保留 0) */
|
||||
fmtValue(val) {
|
||||
return val === null || val === undefined ? '-' : val
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.app-container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.subsidy-page {
|
||||
.list-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
// ==================== 1. 查询条件区域 ====================
|
||||
.search-card {
|
||||
margin-bottom: 16px;
|
||||
|
||||
.search-form {
|
||||
.search-actions-col {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.search-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 2. 数据表格 ====================
|
||||
.table-card {
|
||||
.table-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,102 @@
|
||||
<template>
|
||||
<div :class="className" :style="{height:height,width:width}" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as echarts from 'echarts'
|
||||
require('echarts/theme/macarons') // echarts theme
|
||||
import resize from './mixins/resize'
|
||||
|
||||
const animationDuration = 6000
|
||||
|
||||
export default {
|
||||
mixins: [resize],
|
||||
props: {
|
||||
className: {
|
||||
type: String,
|
||||
default: 'chart'
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '100%'
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: '300px'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chart: null
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.initChart()
|
||||
})
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (!this.chart) {
|
||||
return
|
||||
}
|
||||
this.chart.dispose()
|
||||
this.chart = null
|
||||
},
|
||||
methods: {
|
||||
initChart() {
|
||||
this.chart = echarts.init(this.$el, 'macarons')
|
||||
|
||||
this.chart.setOption({
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: { // 坐标轴指示器,坐标轴触发有效
|
||||
type: 'shadow' // 默认为直线,可选为:'line' | 'shadow'
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
top: 10,
|
||||
left: '2%',
|
||||
right: '2%',
|
||||
bottom: '3%',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: [{
|
||||
type: 'category',
|
||||
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
|
||||
axisTick: {
|
||||
alignWithLabel: true
|
||||
}
|
||||
}],
|
||||
yAxis: [{
|
||||
type: 'value',
|
||||
axisTick: {
|
||||
show: false
|
||||
}
|
||||
}],
|
||||
series: [{
|
||||
name: 'pageA',
|
||||
type: 'bar',
|
||||
stack: 'vistors',
|
||||
barWidth: '60%',
|
||||
data: [79, 52, 200, 334, 390, 330, 220],
|
||||
animationDuration
|
||||
}, {
|
||||
name: 'pageB',
|
||||
type: 'bar',
|
||||
stack: 'vistors',
|
||||
barWidth: '60%',
|
||||
data: [80, 52, 200, 334, 390, 330, 220],
|
||||
animationDuration
|
||||
}, {
|
||||
name: 'pageC',
|
||||
type: 'bar',
|
||||
stack: 'vistors',
|
||||
barWidth: '60%',
|
||||
data: [30, 52, 200, 334, 390, 330, 220],
|
||||
animationDuration
|
||||
}]
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,135 @@
|
||||
<template>
|
||||
<div :class="className" :style="{height:height,width:width}" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as echarts from 'echarts'
|
||||
require('echarts/theme/macarons') // echarts theme
|
||||
import resize from './mixins/resize'
|
||||
|
||||
export default {
|
||||
mixins: [resize],
|
||||
props: {
|
||||
className: {
|
||||
type: String,
|
||||
default: 'chart'
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '100%'
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: '350px'
|
||||
},
|
||||
autoResize: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
chartData: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chart: null
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
chartData: {
|
||||
deep: true,
|
||||
handler(val) {
|
||||
this.setOptions(val)
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.initChart()
|
||||
})
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (!this.chart) {
|
||||
return
|
||||
}
|
||||
this.chart.dispose()
|
||||
this.chart = null
|
||||
},
|
||||
methods: {
|
||||
initChart() {
|
||||
this.chart = echarts.init(this.$el, 'macarons')
|
||||
this.setOptions(this.chartData)
|
||||
},
|
||||
setOptions({ expectedData, actualData } = {}) {
|
||||
this.chart.setOption({
|
||||
xAxis: {
|
||||
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
|
||||
boundaryGap: false,
|
||||
axisTick: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
left: 10,
|
||||
right: 10,
|
||||
bottom: 20,
|
||||
top: 30,
|
||||
containLabel: true
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'cross'
|
||||
},
|
||||
padding: [5, 10]
|
||||
},
|
||||
yAxis: {
|
||||
axisTick: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
data: ['expected', 'actual']
|
||||
},
|
||||
series: [{
|
||||
name: 'expected', itemStyle: {
|
||||
normal: {
|
||||
color: '#FF005A',
|
||||
lineStyle: {
|
||||
color: '#FF005A',
|
||||
width: 2
|
||||
}
|
||||
}
|
||||
},
|
||||
smooth: true,
|
||||
type: 'line',
|
||||
data: expectedData,
|
||||
animationDuration: 2800,
|
||||
animationEasing: 'cubicInOut'
|
||||
},
|
||||
{
|
||||
name: 'actual',
|
||||
smooth: true,
|
||||
type: 'line',
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: '#3888fa',
|
||||
lineStyle: {
|
||||
color: '#3888fa',
|
||||
width: 2
|
||||
},
|
||||
areaStyle: {
|
||||
color: '#f3f8ff'
|
||||
}
|
||||
}
|
||||
},
|
||||
data: actualData,
|
||||
animationDuration: 2800,
|
||||
animationEasing: 'quadraticOut'
|
||||
}]
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,181 @@
|
||||
<template>
|
||||
<el-row :gutter="40" class="panel-group">
|
||||
<el-col :xs="12" :sm="12" :lg="6" class="card-panel-col">
|
||||
<div class="card-panel" @click="handleSetLineChartData('newVisitis')">
|
||||
<div class="card-panel-icon-wrapper icon-people">
|
||||
<svg-icon icon-class="peoples" class-name="card-panel-icon" />
|
||||
</div>
|
||||
<div class="card-panel-description">
|
||||
<div class="card-panel-text">
|
||||
访客
|
||||
</div>
|
||||
<count-to :start-val="0" :end-val="102400" :duration="2600" class="card-panel-num" />
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :xs="12" :sm="12" :lg="6" class="card-panel-col">
|
||||
<div class="card-panel" @click="handleSetLineChartData('messages')">
|
||||
<div class="card-panel-icon-wrapper icon-message">
|
||||
<svg-icon icon-class="message" class-name="card-panel-icon" />
|
||||
</div>
|
||||
<div class="card-panel-description">
|
||||
<div class="card-panel-text">
|
||||
消息
|
||||
</div>
|
||||
<count-to :start-val="0" :end-val="81212" :duration="3000" class="card-panel-num" />
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :xs="12" :sm="12" :lg="6" class="card-panel-col">
|
||||
<div class="card-panel" @click="handleSetLineChartData('purchases')">
|
||||
<div class="card-panel-icon-wrapper icon-money">
|
||||
<svg-icon icon-class="money" class-name="card-panel-icon" />
|
||||
</div>
|
||||
<div class="card-panel-description">
|
||||
<div class="card-panel-text">
|
||||
金额
|
||||
</div>
|
||||
<count-to :start-val="0" :end-val="9280" :duration="3200" class="card-panel-num" />
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :xs="12" :sm="12" :lg="6" class="card-panel-col">
|
||||
<div class="card-panel" @click="handleSetLineChartData('shoppings')">
|
||||
<div class="card-panel-icon-wrapper icon-shopping">
|
||||
<svg-icon icon-class="shopping" class-name="card-panel-icon" />
|
||||
</div>
|
||||
<div class="card-panel-description">
|
||||
<div class="card-panel-text">
|
||||
订单
|
||||
</div>
|
||||
<count-to :start-val="0" :end-val="13600" :duration="3600" class="card-panel-num" />
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CountTo from 'vue-count-to'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CountTo
|
||||
},
|
||||
methods: {
|
||||
handleSetLineChartData(type) {
|
||||
this.$emit('handleSetLineChartData', type)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.panel-group {
|
||||
margin-top: 18px;
|
||||
|
||||
.card-panel-col {
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.card-panel {
|
||||
height: 108px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
color: #666;
|
||||
background: #fff;
|
||||
box-shadow: 4px 4px 40px rgba(0, 0, 0, .05);
|
||||
border-color: rgba(0, 0, 0, .05);
|
||||
|
||||
&:hover {
|
||||
.card-panel-icon-wrapper {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.icon-people {
|
||||
background: #40c9c6;
|
||||
}
|
||||
|
||||
.icon-message {
|
||||
background: #36a3f7;
|
||||
}
|
||||
|
||||
.icon-money {
|
||||
background: #f4516c;
|
||||
}
|
||||
|
||||
.icon-shopping {
|
||||
background: #34bfa3
|
||||
}
|
||||
}
|
||||
|
||||
.icon-people {
|
||||
color: #40c9c6;
|
||||
}
|
||||
|
||||
.icon-message {
|
||||
color: #36a3f7;
|
||||
}
|
||||
|
||||
.icon-money {
|
||||
color: #f4516c;
|
||||
}
|
||||
|
||||
.icon-shopping {
|
||||
color: #34bfa3
|
||||
}
|
||||
|
||||
.card-panel-icon-wrapper {
|
||||
float: left;
|
||||
margin: 14px 0 0 14px;
|
||||
padding: 16px;
|
||||
transition: all 0.38s ease-out;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.card-panel-icon {
|
||||
float: left;
|
||||
font-size: 48px;
|
||||
}
|
||||
|
||||
.card-panel-description {
|
||||
float: right;
|
||||
font-weight: bold;
|
||||
margin: 26px;
|
||||
margin-left: 0px;
|
||||
|
||||
.card-panel-text {
|
||||
line-height: 18px;
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
font-size: 16px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.card-panel-num {
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width:550px) {
|
||||
.card-panel-description {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.card-panel-icon-wrapper {
|
||||
float: none !important;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0 !important;
|
||||
|
||||
.svg-icon {
|
||||
display: block;
|
||||
margin: 14px auto !important;
|
||||
float: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,79 @@
|
||||
<template>
|
||||
<div :class="className" :style="{height:height,width:width}" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as echarts from 'echarts'
|
||||
require('echarts/theme/macarons') // echarts theme
|
||||
import resize from './mixins/resize'
|
||||
|
||||
export default {
|
||||
mixins: [resize],
|
||||
props: {
|
||||
className: {
|
||||
type: String,
|
||||
default: 'chart'
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '100%'
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: '300px'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chart: null
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.initChart()
|
||||
})
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (!this.chart) {
|
||||
return
|
||||
}
|
||||
this.chart.dispose()
|
||||
this.chart = null
|
||||
},
|
||||
methods: {
|
||||
initChart() {
|
||||
this.chart = echarts.init(this.$el, 'macarons')
|
||||
|
||||
this.chart.setOption({
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
formatter: '{a} <br/>{b} : {c} ({d}%)'
|
||||
},
|
||||
legend: {
|
||||
left: 'center',
|
||||
bottom: '10',
|
||||
data: ['Industries', 'Technology', 'Forex', 'Gold', 'Forecasts']
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: 'WEEKLY WRITE ARTICLES',
|
||||
type: 'pie',
|
||||
roseType: 'radius',
|
||||
radius: [15, 95],
|
||||
center: ['50%', '38%'],
|
||||
data: [
|
||||
{ value: 320, name: 'Industries' },
|
||||
{ value: 240, name: 'Technology' },
|
||||
{ value: 149, name: 'Forex' },
|
||||
{ value: 100, name: 'Gold' },
|
||||
{ value: 59, name: 'Forecasts' }
|
||||
],
|
||||
animationEasing: 'cubicInOut',
|
||||
animationDuration: 2600
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,116 @@
|
||||
<template>
|
||||
<div :class="className" :style="{height:height,width:width}" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as echarts from 'echarts'
|
||||
require('echarts/theme/macarons') // echarts theme
|
||||
import resize from './mixins/resize'
|
||||
|
||||
const animationDuration = 3000
|
||||
|
||||
export default {
|
||||
mixins: [resize],
|
||||
props: {
|
||||
className: {
|
||||
type: String,
|
||||
default: 'chart'
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '100%'
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: '300px'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chart: null
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.initChart()
|
||||
})
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (!this.chart) {
|
||||
return
|
||||
}
|
||||
this.chart.dispose()
|
||||
this.chart = null
|
||||
},
|
||||
methods: {
|
||||
initChart() {
|
||||
this.chart = echarts.init(this.$el, 'macarons')
|
||||
|
||||
this.chart.setOption({
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: { // 坐标轴指示器,坐标轴触发有效
|
||||
type: 'shadow' // 默认为直线,可选为:'line' | 'shadow'
|
||||
}
|
||||
},
|
||||
radar: {
|
||||
radius: '66%',
|
||||
center: ['50%', '42%'],
|
||||
splitNumber: 8,
|
||||
splitArea: {
|
||||
areaStyle: {
|
||||
color: 'rgba(127,95,132,.3)',
|
||||
opacity: 1,
|
||||
shadowBlur: 45,
|
||||
shadowColor: 'rgba(0,0,0,.5)',
|
||||
shadowOffsetX: 0,
|
||||
shadowOffsetY: 15
|
||||
}
|
||||
},
|
||||
indicator: [
|
||||
{ name: 'Sales', max: 10000 },
|
||||
{ name: 'Administration', max: 20000 },
|
||||
{ name: 'Information Techology', max: 20000 },
|
||||
{ name: 'Customer Support', max: 20000 },
|
||||
{ name: 'Development', max: 20000 },
|
||||
{ name: 'Marketing', max: 20000 }
|
||||
]
|
||||
},
|
||||
legend: {
|
||||
left: 'center',
|
||||
bottom: '10',
|
||||
data: ['Allocated Budget', 'Expected Spending', 'Actual Spending']
|
||||
},
|
||||
series: [{
|
||||
type: 'radar',
|
||||
symbolSize: 0,
|
||||
areaStyle: {
|
||||
normal: {
|
||||
shadowBlur: 13,
|
||||
shadowColor: 'rgba(0,0,0,.2)',
|
||||
shadowOffsetX: 0,
|
||||
shadowOffsetY: 10,
|
||||
opacity: 1
|
||||
}
|
||||
},
|
||||
data: [
|
||||
{
|
||||
value: [5000, 7000, 12000, 11000, 15000, 14000],
|
||||
name: 'Allocated Budget'
|
||||
},
|
||||
{
|
||||
value: [4000, 9000, 15000, 15000, 13000, 11000],
|
||||
name: 'Expected Spending'
|
||||
},
|
||||
{
|
||||
value: [5500, 11000, 12000, 15000, 12000, 12000],
|
||||
name: 'Actual Spending'
|
||||
}
|
||||
],
|
||||
animationDuration: animationDuration
|
||||
}]
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,56 @@
|
||||
import { debounce } from '@/utils'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
$_sidebarElm: null,
|
||||
$_resizeHandler: null
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.initListener()
|
||||
},
|
||||
activated() {
|
||||
if (!this.$_resizeHandler) {
|
||||
// avoid duplication init
|
||||
this.initListener()
|
||||
}
|
||||
|
||||
// when keep-alive chart activated, auto resize
|
||||
this.resize()
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.destroyListener()
|
||||
},
|
||||
deactivated() {
|
||||
this.destroyListener()
|
||||
},
|
||||
methods: {
|
||||
// use $_ for mixins properties
|
||||
// https://vuejs.org/v2/style-guide/index.html#Private-property-names-essential
|
||||
$_sidebarResizeHandler(e) {
|
||||
if (e.propertyName === 'width') {
|
||||
this.$_resizeHandler()
|
||||
}
|
||||
},
|
||||
initListener() {
|
||||
this.$_resizeHandler = debounce(() => {
|
||||
this.resize()
|
||||
}, 100)
|
||||
window.addEventListener('resize', this.$_resizeHandler)
|
||||
|
||||
this.$_sidebarElm = document.getElementsByClassName('sidebar-container')[0]
|
||||
this.$_sidebarElm && this.$_sidebarElm.addEventListener('transitionend', this.$_sidebarResizeHandler)
|
||||
},
|
||||
destroyListener() {
|
||||
window.removeEventListener('resize', this.$_resizeHandler)
|
||||
this.$_resizeHandler = null
|
||||
|
||||
this.$_sidebarElm && this.$_sidebarElm.removeEventListener('transitionend', this.$_sidebarResizeHandler)
|
||||
},
|
||||
resize() {
|
||||
const { chart } = this
|
||||
chart && chart.resize()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<template>
|
||||
<div class="errPage-container">
|
||||
<el-button icon="arrow-left" class="pan-back-btn" @click="back">
|
||||
返回
|
||||
</el-button>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<h1 class="text-jumbo text-ginormous">
|
||||
401错误!
|
||||
</h1>
|
||||
<h2>您没有访问权限!</h2>
|
||||
<h6>对不起,您没有访问权限,请不要进行非法操作!您可以返回主页面</h6>
|
||||
<ul class="list-unstyled">
|
||||
<li class="link-type">
|
||||
<router-link to="/">
|
||||
回首页
|
||||
</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<img :src="errGif" width="313" height="428" alt="Girl has dropped her ice cream.">
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import errGif from '@/assets/401_images/401.gif'
|
||||
|
||||
export default {
|
||||
name: 'Page401',
|
||||
data() {
|
||||
return {
|
||||
errGif: errGif + '?' + +new Date()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
back() {
|
||||
if (this.$route.query.noGoBack) {
|
||||
this.$router.push({ path: '/' })
|
||||
} else {
|
||||
this.$router.go(-1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.errPage-container {
|
||||
width: 800px;
|
||||
max-width: 100%;
|
||||
margin: 100px auto;
|
||||
.pan-back-btn {
|
||||
background: #008489;
|
||||
color: #fff;
|
||||
border: none!important;
|
||||
}
|
||||
.pan-gif {
|
||||
margin: 0 auto;
|
||||
display: block;
|
||||
}
|
||||
.pan-img {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
}
|
||||
.text-jumbo {
|
||||
font-size: 60px;
|
||||
font-weight: 700;
|
||||
color: #484848;
|
||||
}
|
||||
.list-unstyled {
|
||||
font-size: 14px;
|
||||
li {
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
a {
|
||||
color: #008489;
|
||||
text-decoration: none;
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,233 @@
|
||||
<template>
|
||||
<div class="wscn-http404-container">
|
||||
<div class="wscn-http404">
|
||||
<div class="pic-404">
|
||||
<img class="pic-404__parent" src="@/assets/404_images/404.png" alt="404">
|
||||
<img class="pic-404__child left" src="@/assets/404_images/404_cloud.png" alt="404">
|
||||
<img class="pic-404__child mid" src="@/assets/404_images/404_cloud.png" alt="404">
|
||||
<img class="pic-404__child right" src="@/assets/404_images/404_cloud.png" alt="404">
|
||||
</div>
|
||||
<div class="bullshit">
|
||||
<div class="bullshit__oops">
|
||||
404错误!
|
||||
</div>
|
||||
<div class="bullshit__headline">
|
||||
{{ message }}
|
||||
</div>
|
||||
<div class="bullshit__info">
|
||||
对不起,您正在寻找的页面不存在。尝试检查URL的错误,然后按浏览器上的刷新按钮或尝试在我们的应用程序中找到其他内容。
|
||||
</div>
|
||||
<router-link to="/" class="bullshit__return-home">
|
||||
返回首页
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'Page404',
|
||||
computed: {
|
||||
message() {
|
||||
return '找不到网页!'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.wscn-http404-container{
|
||||
transform: translate(-50%,-50%);
|
||||
position: absolute;
|
||||
top: 40%;
|
||||
left: 50%;
|
||||
}
|
||||
.wscn-http404 {
|
||||
position: relative;
|
||||
width: 1200px;
|
||||
padding: 0 50px;
|
||||
overflow: hidden;
|
||||
.pic-404 {
|
||||
position: relative;
|
||||
float: left;
|
||||
width: 600px;
|
||||
overflow: hidden;
|
||||
&__parent {
|
||||
width: 100%;
|
||||
}
|
||||
&__child {
|
||||
position: absolute;
|
||||
&.left {
|
||||
width: 80px;
|
||||
top: 17px;
|
||||
left: 220px;
|
||||
opacity: 0;
|
||||
animation-name: cloudLeft;
|
||||
animation-duration: 2s;
|
||||
animation-timing-function: linear;
|
||||
animation-fill-mode: forwards;
|
||||
animation-delay: 1s;
|
||||
}
|
||||
&.mid {
|
||||
width: 46px;
|
||||
top: 10px;
|
||||
left: 420px;
|
||||
opacity: 0;
|
||||
animation-name: cloudMid;
|
||||
animation-duration: 2s;
|
||||
animation-timing-function: linear;
|
||||
animation-fill-mode: forwards;
|
||||
animation-delay: 1.2s;
|
||||
}
|
||||
&.right {
|
||||
width: 62px;
|
||||
top: 100px;
|
||||
left: 500px;
|
||||
opacity: 0;
|
||||
animation-name: cloudRight;
|
||||
animation-duration: 2s;
|
||||
animation-timing-function: linear;
|
||||
animation-fill-mode: forwards;
|
||||
animation-delay: 1s;
|
||||
}
|
||||
@keyframes cloudLeft {
|
||||
0% {
|
||||
top: 17px;
|
||||
left: 220px;
|
||||
opacity: 0;
|
||||
}
|
||||
20% {
|
||||
top: 33px;
|
||||
left: 188px;
|
||||
opacity: 1;
|
||||
}
|
||||
80% {
|
||||
top: 81px;
|
||||
left: 92px;
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
top: 97px;
|
||||
left: 60px;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
@keyframes cloudMid {
|
||||
0% {
|
||||
top: 10px;
|
||||
left: 420px;
|
||||
opacity: 0;
|
||||
}
|
||||
20% {
|
||||
top: 40px;
|
||||
left: 360px;
|
||||
opacity: 1;
|
||||
}
|
||||
70% {
|
||||
top: 130px;
|
||||
left: 180px;
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
top: 160px;
|
||||
left: 120px;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
@keyframes cloudRight {
|
||||
0% {
|
||||
top: 100px;
|
||||
left: 500px;
|
||||
opacity: 0;
|
||||
}
|
||||
20% {
|
||||
top: 120px;
|
||||
left: 460px;
|
||||
opacity: 1;
|
||||
}
|
||||
80% {
|
||||
top: 180px;
|
||||
left: 340px;
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
top: 200px;
|
||||
left: 300px;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.bullshit {
|
||||
position: relative;
|
||||
float: left;
|
||||
width: 300px;
|
||||
padding: 30px 0;
|
||||
overflow: hidden;
|
||||
&__oops {
|
||||
font-size: 32px;
|
||||
font-weight: bold;
|
||||
line-height: 40px;
|
||||
color: #1482f0;
|
||||
opacity: 0;
|
||||
margin-bottom: 20px;
|
||||
animation-name: slideUp;
|
||||
animation-duration: 0.5s;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
&__headline {
|
||||
font-size: 20px;
|
||||
line-height: 24px;
|
||||
color: #222;
|
||||
font-weight: bold;
|
||||
opacity: 0;
|
||||
margin-bottom: 10px;
|
||||
animation-name: slideUp;
|
||||
animation-duration: 0.5s;
|
||||
animation-delay: 0.1s;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
&__info {
|
||||
font-size: 13px;
|
||||
line-height: 21px;
|
||||
color: grey;
|
||||
opacity: 0;
|
||||
margin-bottom: 30px;
|
||||
animation-name: slideUp;
|
||||
animation-duration: 0.5s;
|
||||
animation-delay: 0.2s;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
&__return-home {
|
||||
display: block;
|
||||
float: left;
|
||||
width: 110px;
|
||||
height: 36px;
|
||||
background: #1482f0;
|
||||
border-radius: 100px;
|
||||
text-align: center;
|
||||
color: #ffffff;
|
||||
opacity: 0;
|
||||
font-size: 14px;
|
||||
line-height: 36px;
|
||||
cursor: pointer;
|
||||
animation-name: slideUp;
|
||||
animation-duration: 0.5s;
|
||||
animation-delay: 0.3s;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
@keyframes slideUp {
|
||||
0% {
|
||||
transform: translateY(60px);
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,326 @@
|
||||
<template>
|
||||
<div class="app-container home">
|
||||
<!-- 通知公告 -->
|
||||
<el-row :gutter="20" class="bottom-row">
|
||||
<el-col :xs="24" :sm="24" :md="24" :lg="24">
|
||||
<el-card class="module-card notice-card" shadow="hover">
|
||||
<div slot="header" class="card-header">
|
||||
<span><i class="el-icon-bell card-icon"></i> 通知公告</span>
|
||||
<el-button type="text" class="more-btn" @click="handleMore('notice')">更多 <i class="el-icon-arrow-right"></i></el-button>
|
||||
</div>
|
||||
<div class="card-body notice-body" v-loading="noticeLoading">
|
||||
<div v-if="noticeList.length === 0" class="empty-state mini-empty">
|
||||
<i class="el-icon-inbox empty-icon"></i>
|
||||
<p>暂无公告</p>
|
||||
</div>
|
||||
<div v-else class="notice-list">
|
||||
<div v-for="item in noticeList" :key="item.noticeId" class="notice-item" @click="handleViewNotice(item)">
|
||||
<div class="notice-main">
|
||||
<el-tag size="mini" :type="item.noticeType === '1' ? 'warning' : 'success'" class="notice-tag">
|
||||
{{ item.noticeType === '1' ? '通知' : '公告' }}
|
||||
</el-tag>
|
||||
<span class="notice-title">{{ item.noticeTitle }}</span>
|
||||
</div>
|
||||
<div class="notice-meta">
|
||||
<span>{{ item.createBy || '系统' }}</span>
|
||||
<span>{{ item.createTime }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<el-pagination
|
||||
@size-change="handleNoticeSizeChange"
|
||||
@current-change="handleNoticeCurrentChange"
|
||||
:current-page="noticePage.currentPage"
|
||||
:page-sizes="[5, 10, 20, 50]"
|
||||
:page-size="noticePage.pageSize"
|
||||
:total="noticePage.total"
|
||||
layout="total, prev, pager, next"
|
||||
small
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<notice-detail-view ref="noticeViewRef" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listNotice } from "@/api/system/notice"
|
||||
import NoticeDetailView from "@/layout/components/HeaderNotice/DetailView"
|
||||
|
||||
export default {
|
||||
name: "Index",
|
||||
components: { NoticeDetailView },
|
||||
data() {
|
||||
return {
|
||||
version: "3.9.2",
|
||||
noticeList: [],
|
||||
noticeLoading: false,
|
||||
noticePage: {
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadNoticeList()
|
||||
},
|
||||
methods: {
|
||||
goTarget(href) {
|
||||
window.open(href, "_blank")
|
||||
},
|
||||
handleMore(type) {
|
||||
const titleMap = {
|
||||
notice: '通知公告'
|
||||
}
|
||||
const targetTitle = titleMap[type]
|
||||
if (targetTitle) {
|
||||
const routes = this.$store.getters.sidebarRouters || []
|
||||
const findRouteByTitle = (routeList, parentPath = '') => {
|
||||
for (const route of routeList) {
|
||||
const fullPath = parentPath ? parentPath + '/' + route.path : route.path
|
||||
if (route.meta && route.meta.title === targetTitle) {
|
||||
return fullPath
|
||||
}
|
||||
if (route.children && route.children.length) {
|
||||
const found = findRouteByTitle(route.children, fullPath)
|
||||
if (found) return found
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
const targetPath = findRouteByTitle(routes)
|
||||
if (targetPath) {
|
||||
this.$router.push(targetPath)
|
||||
return
|
||||
}
|
||||
}
|
||||
this.$message.info("更多功能暂未开放")
|
||||
},
|
||||
loadNoticeList() {
|
||||
this.noticeLoading = true
|
||||
listNotice({
|
||||
pageNum: this.noticePage.currentPage,
|
||||
pageSize: this.noticePage.pageSize
|
||||
}).then(response => {
|
||||
this.noticeList = response.rows || []
|
||||
this.noticePage.total = response.total || 0
|
||||
this.noticeLoading = false
|
||||
}).catch(() => {
|
||||
this.noticeLoading = false
|
||||
})
|
||||
},
|
||||
handleViewNotice(item) {
|
||||
this.$refs.noticeViewRef.open(item)
|
||||
},
|
||||
handleNoticeSizeChange(val) {
|
||||
this.noticePage.pageSize = val
|
||||
this.noticePage.currentPage = 1
|
||||
this.loadNoticeList()
|
||||
},
|
||||
handleNoticeCurrentChange(val) {
|
||||
this.noticePage.currentPage = val
|
||||
this.loadNoticeList()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.home {
|
||||
background: linear-gradient(135deg, #f5f7fa 0%, #eef0f8 50%, #f5f7fa 100%);
|
||||
min-height: calc(100vh - 84px);
|
||||
padding: 20px;
|
||||
position: relative;
|
||||
|
||||
.bottom-row {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.module-card {
|
||||
height: 420px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-radius: 10px;
|
||||
border: none;
|
||||
transition: all 0.3s ease;
|
||||
background: linear-gradient(180deg, #ffffff 0%, #f8f9fd 100%);
|
||||
box-shadow: 0 4px 20px rgba(0, 135, 90, 0.06);
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 8px 30px rgba(0, 135, 90, 0.12);
|
||||
}
|
||||
|
||||
::v-deep .el-card__header {
|
||||
padding: 14px 20px;
|
||||
border-bottom: 1px solid rgba(0, 135, 90, 0.08);
|
||||
background: linear-gradient(135deg, var(--edu-green-dark) 0%, var(--edu-green-primary) 100%);
|
||||
border-radius: 10px 10px 0 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
::v-deep .el-card__body {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-weight: 600;
|
||||
font-size: 15px;
|
||||
color: #ffffff;
|
||||
|
||||
.card-icon {
|
||||
margin-right: 6px;
|
||||
color: var(--edu-green-light);
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.more-btn {
|
||||
padding: 0;
|
||||
font-size: 13px;
|
||||
color: rgba(255, 255, 255, 0.75);
|
||||
|
||||
&:hover {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
i {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card-body {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
padding: 15px 20px;
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.card-footer {
|
||||
flex-shrink: 0;
|
||||
padding: 10px 20px 15px;
|
||||
text-align: right;
|
||||
border-top: 1px solid rgba(0, 135, 90, 0.06);
|
||||
background: linear-gradient(180deg, #ffffff 0%, #f4f6fb 100%);
|
||||
overflow-x: auto;
|
||||
|
||||
::v-deep .el-pagination__total,
|
||||
::v-deep .el-pagination__jump,
|
||||
::v-deep .el-pager li {
|
||||
color: #5a5f7a;
|
||||
}
|
||||
|
||||
::v-deep .el-pager li.active,
|
||||
::v-deep .el-pager li:hover {
|
||||
color: var(--edu-green-dark);
|
||||
}
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 160px;
|
||||
color: #8a90ad;
|
||||
|
||||
.empty-icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: 12px;
|
||||
color: #b8bed8;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.notice-list {
|
||||
.notice-item {
|
||||
padding: 14px 0;
|
||||
border-bottom: 1px solid #f5f5f5;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: rgba(0, 135, 90, 0.03);
|
||||
margin: 0 -20px;
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
.notice-main {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 6px;
|
||||
|
||||
.notice-tag {
|
||||
flex-shrink: 0;
|
||||
|
||||
&.el-tag--warning {
|
||||
background-color: rgba(0, 135, 90, 0.08);
|
||||
border-color: rgba(0, 135, 90, 0.15);
|
||||
color: var(--edu-green-dark);
|
||||
}
|
||||
|
||||
&.el-tag--success {
|
||||
background-color: rgba(0, 135, 90, 0.06);
|
||||
border-color: rgba(0, 135, 90, 0.12);
|
||||
color: var(--edu-green-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.notice-title {
|
||||
flex: 1;
|
||||
font-size: 13px;
|
||||
color: #303133;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
&:hover {
|
||||
color: var(--edu-green-dark);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.notice-meta {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
padding-left: 42px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,375 @@
|
||||
<template>
|
||||
<div class="lock-container">
|
||||
<!-- 动态粒子背景 -->
|
||||
<canvas ref="particleCanvas" class="particle-bg"></canvas>
|
||||
|
||||
<!-- 时钟 -->
|
||||
<div class="lock-time">{{ currentTime }}</div>
|
||||
<div class="lock-date">{{ currentDate }}</div>
|
||||
|
||||
<!-- 锁屏卡片 -->
|
||||
<div class="lock-card">
|
||||
<div class="avatar-wrap">
|
||||
<img :src="avatar" class="lock-avatar" @error="onAvatarError" />
|
||||
<div class="lock-icon">🔒</div>
|
||||
</div>
|
||||
<div class="lock-username">{{ nickName }}</div>
|
||||
<div class="lock-hint">系统已锁定,请输入密码解锁</div>
|
||||
|
||||
<div class="input-wrap" :class="{ shake: isShaking }">
|
||||
<input ref="passwordInput" v-model="password" type="password" placeholder="请输入登录密码" class="lock-input" @keydown.enter="handleUnlock" autocomplete="off" />
|
||||
<button class="unlock-btn" @click="handleUnlock" :disabled="loading">
|
||||
<span v-if="!loading">→</span>
|
||||
<span v-else class="loading-dot">···</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div v-if="errorMsg" class="error-msg">{{ errorMsg }}</div>
|
||||
|
||||
<div class="lock-footer">
|
||||
<a href="/login" @click.prevent="goLogin">退出重新登录</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
import { unlockScreen } from '@/api/login'
|
||||
import defAva from '@/assets/images/profile.jpg'
|
||||
|
||||
export default {
|
||||
name: 'LockScreen',
|
||||
data() {
|
||||
return {
|
||||
password: '',
|
||||
loading: false,
|
||||
errorMsg: '',
|
||||
isShaking: false,
|
||||
currentTime: '',
|
||||
currentDate: '',
|
||||
timer: null,
|
||||
animationId: null,
|
||||
particles: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['avatar', 'nickName'])
|
||||
},
|
||||
mounted() {
|
||||
this.startClock()
|
||||
this.initParticles()
|
||||
this.$nextTick(() => {
|
||||
this.$refs.passwordInput && this.$refs.passwordInput.focus()
|
||||
})
|
||||
},
|
||||
beforeDestroy() {
|
||||
clearInterval(this.timer)
|
||||
cancelAnimationFrame(this.animationId)
|
||||
},
|
||||
methods: {
|
||||
onAvatarError(e) {
|
||||
e.target.src = defAva
|
||||
},
|
||||
startClock() {
|
||||
const update = () => {
|
||||
const now = new Date()
|
||||
const h = String(now.getHours()).padStart(2, '0')
|
||||
const m = String(now.getMinutes()).padStart(2, '0')
|
||||
const s = String(now.getSeconds()).padStart(2, '0')
|
||||
this.currentTime = `${h}:${m}:${s}`
|
||||
const days = ['星期日','星期一','星期二','星期三','星期四','星期五','星期六']
|
||||
this.currentDate = `${now.getFullYear()}年${now.getMonth()+1}月${now.getDate()}日 ${days[now.getDay()]}`
|
||||
}
|
||||
update()
|
||||
this.timer = setInterval(update, 1000)
|
||||
},
|
||||
async handleUnlock() {
|
||||
if (!this.password) {
|
||||
this.showError('请输入密码')
|
||||
return
|
||||
}
|
||||
this.loading = true
|
||||
this.errorMsg = ''
|
||||
try {
|
||||
await unlockScreen(this.password)
|
||||
const lockPath = this.$store.getters.lockPath // 取锁屏前的路径
|
||||
await this.$store.dispatch('lock/unlockScreen')
|
||||
this.$router.replace(lockPath)
|
||||
} catch (err) {
|
||||
const msg = err.message || err.toString()
|
||||
this.showError(msg)
|
||||
this.password = ''
|
||||
this.$refs.passwordInput && this.$refs.passwordInput.focus()
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
showError(msg) {
|
||||
this.errorMsg = msg
|
||||
this.isShaking = true
|
||||
setTimeout(() => { this.isShaking = false }, 600)
|
||||
},
|
||||
goLogin() {
|
||||
this.$store.dispatch('lock/unlockScreen')
|
||||
this.$store.dispatch('LogOut').then(() => {
|
||||
this.$router.push('/login')
|
||||
})
|
||||
},
|
||||
// 粒子背景
|
||||
initParticles() {
|
||||
const canvas = this.$refs.particleCanvas
|
||||
if (!canvas) return
|
||||
const ctx = canvas.getContext('2d')
|
||||
const resize = () => {
|
||||
canvas.width = window.innerWidth
|
||||
canvas.height = window.innerHeight
|
||||
}
|
||||
resize()
|
||||
window.addEventListener('resize', resize)
|
||||
const count = 80
|
||||
for (let i = 0; i < count; i++) {
|
||||
this.particles.push({
|
||||
x: Math.random() * canvas.width,
|
||||
y: Math.random() * canvas.height,
|
||||
r: Math.random() * 2 + 1,
|
||||
dx: (Math.random() - 0.5) * 0.6,
|
||||
dy: (Math.random() - 0.5) * 0.6,
|
||||
alpha: Math.random() * 0.5 + 0.2
|
||||
})
|
||||
}
|
||||
const draw = () => {
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height)
|
||||
this.particles.forEach(p => {
|
||||
ctx.beginPath()
|
||||
ctx.arc(p.x, p.y, p.r, 0, Math.PI * 2)
|
||||
ctx.fillStyle = `rgba(255,255,255,${p.alpha})`
|
||||
ctx.fill()
|
||||
p.x += p.dx
|
||||
p.y += p.dy
|
||||
if (p.x < 0 || p.x > canvas.width) p.dx *= -1
|
||||
if (p.y < 0 || p.y > canvas.height) p.dy *= -1
|
||||
})
|
||||
// 连线
|
||||
for (let i = 0; i < this.particles.length; i++) {
|
||||
for (let j = i + 1; j < this.particles.length; j++) {
|
||||
const a = this.particles[i], b = this.particles[j]
|
||||
const dist = Math.hypot(a.x - b.x, a.y - b.y)
|
||||
if (dist < 120) {
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(a.x, a.y)
|
||||
ctx.lineTo(b.x, b.y)
|
||||
ctx.strokeStyle = `rgba(255,255,255,${0.15 * (1 - dist / 120)})`
|
||||
ctx.lineWidth = 0.5
|
||||
ctx.stroke()
|
||||
}
|
||||
}
|
||||
}
|
||||
this.animationId = requestAnimationFrame(draw)
|
||||
}
|
||||
draw()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.lock-container {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: linear-gradient(135deg, #0f0c29, #302b63, #24243e);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 9999;
|
||||
font-family: 'PingFang SC', 'Microsoft YaHei', sans-serif;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.particle-bg {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.lock-time {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
font-size: 72px;
|
||||
font-weight: 200;
|
||||
color: #fff;
|
||||
letter-spacing: 4px;
|
||||
text-shadow: 0 0 40px rgba(255,255,255,0.3);
|
||||
margin-bottom: 8px;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.lock-date {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
font-size: 15px;
|
||||
color: rgba(255,255,255,0.6);
|
||||
margin-bottom: 48px;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
.lock-card {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.15);
|
||||
border-radius: 24px;
|
||||
padding: 40px 48px;
|
||||
width: 360px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
box-shadow: 0 25px 60px rgba(0,0,0,0.4);
|
||||
}
|
||||
|
||||
.avatar-wrap {
|
||||
position: relative;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.lock-avatar {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 50%;
|
||||
border: 3px solid rgba(255,255,255,0.3);
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.lock-icon {
|
||||
position: absolute;
|
||||
bottom: -4px;
|
||||
right: -4px;
|
||||
background: rgba(255,255,255,0.15);
|
||||
border-radius: 50%;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 13px;
|
||||
backdrop-filter: blur(8px);
|
||||
}
|
||||
|
||||
.lock-username {
|
||||
color: #fff;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 6px;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.lock-hint {
|
||||
color: rgba(255,255,255,0.5);
|
||||
font-size: 13px;
|
||||
margin-bottom: 28px;
|
||||
}
|
||||
|
||||
.input-wrap {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: rgba(255,255,255,0.1);
|
||||
border: 1px solid rgba(255,255,255,0.2);
|
||||
border-radius: 50px;
|
||||
padding: 4px 4px 4px 20px;
|
||||
transition: border-color 0.3s;
|
||||
}
|
||||
|
||||
.input-wrap:focus-within {
|
||||
border-color: rgba(255,255,255,0.6);
|
||||
background: rgba(255,255,255,0.13);
|
||||
}
|
||||
|
||||
.input-wrap.shake {
|
||||
animation: shake 0.5s ease;
|
||||
}
|
||||
|
||||
@keyframes shake {
|
||||
0%, 100% { transform: translateX(0); }
|
||||
20% { transform: translateX(-8px); }
|
||||
40% { transform: translateX(8px); }
|
||||
60% { transform: translateX(-6px); }
|
||||
80% { transform: translateX(6px); }
|
||||
}
|
||||
|
||||
.lock-input {
|
||||
flex: 1;
|
||||
background: transparent;
|
||||
border: none;
|
||||
outline: none;
|
||||
color: #fff;
|
||||
font-size: 15px;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.lock-input::placeholder {
|
||||
color: rgba(255,255,255,0.35);
|
||||
}
|
||||
|
||||
.unlock-btn {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, #667eea, #764ba2);
|
||||
border: none;
|
||||
color: #fff;
|
||||
font-size: 18px;
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s, opacity 0.2s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.unlock-btn:hover:not(:disabled) {
|
||||
transform: scale(1.08);
|
||||
}
|
||||
|
||||
.unlock-btn:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.loading-dot {
|
||||
font-size: 13px;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.error-msg {
|
||||
margin-top: 14px;
|
||||
color: #ff7675;
|
||||
font-size: 13px;
|
||||
text-align: center;
|
||||
animation: fadeIn 0.3s ease;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(-4px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
.lock-footer {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.lock-footer a {
|
||||
color: rgba(255,255,255,0.4);
|
||||
font-size: 13px;
|
||||
text-decoration: none;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
.lock-footer a:hover {
|
||||
color: rgba(255,255,255,0.8);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,180 @@
|
||||
<template>
|
||||
<div class="login">
|
||||
<el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form">
|
||||
<h3 class="title">{{title}}</h3>
|
||||
<el-form-item prop="username">
|
||||
<el-input
|
||||
v-model="loginForm.username"
|
||||
type="text"
|
||||
auto-complete="off"
|
||||
placeholder="账号"
|
||||
>
|
||||
<svg-icon slot="prefix" icon-class="user" class="el-input__icon input-icon" />
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="password">
|
||||
<el-input
|
||||
v-model="loginForm.password"
|
||||
type="password"
|
||||
auto-complete="off"
|
||||
placeholder="密码"
|
||||
@keyup.enter.native="handleLogin"
|
||||
>
|
||||
<svg-icon slot="prefix" icon-class="password" class="el-input__icon input-icon" />
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-checkbox v-model="loginForm.rememberMe" style="margin:0px 0px 25px 0px;">记住密码</el-checkbox>
|
||||
<el-form-item style="width:100%;">
|
||||
<el-button
|
||||
:loading="loading"
|
||||
size="medium"
|
||||
type="primary"
|
||||
style="width:100%;"
|
||||
@click.native.prevent="handleLogin"
|
||||
>
|
||||
<span v-if="!loading">登 录</span>
|
||||
<span v-else>登 录 中...</span>
|
||||
</el-button>
|
||||
<div style="float: right;" v-if="register">
|
||||
<router-link class="link-type" :to="'/register'">立即注册</router-link>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<!-- 底部 -->
|
||||
<div class="el-login-footer">
|
||||
<span>{{ footerContent }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Cookies from "js-cookie"
|
||||
import { encrypt, decrypt } from '@/utils/jsencrypt'
|
||||
|
||||
export default {
|
||||
name: "Login",
|
||||
data() {
|
||||
return {
|
||||
title: process.env.VUE_APP_TITLE,
|
||||
footerContent: "Copyright © 2018-2026 roomroot. All Rights Reserved.",
|
||||
loginForm: {
|
||||
username: "admin",
|
||||
password: "admin123",
|
||||
rememberMe: false
|
||||
},
|
||||
loginRules: {
|
||||
username: [
|
||||
{ required: true, trigger: "blur", message: "请输入您的账号" }
|
||||
],
|
||||
password: [
|
||||
{ required: true, trigger: "blur", message: "请输入您的密码" }
|
||||
]
|
||||
},
|
||||
loading: false,
|
||||
// 注册开关
|
||||
register: false,
|
||||
redirect: undefined
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
$route: {
|
||||
handler: function(route) {
|
||||
this.redirect = route.query && route.query.redirect
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getCookie()
|
||||
},
|
||||
methods: {
|
||||
getCookie() {
|
||||
const username = Cookies.get("username")
|
||||
const password = Cookies.get("password")
|
||||
const rememberMe = Cookies.get('rememberMe')
|
||||
this.loginForm = {
|
||||
username: username === undefined ? this.loginForm.username : username,
|
||||
password: password === undefined ? this.loginForm.password : decrypt(password),
|
||||
rememberMe: rememberMe === undefined ? false : Boolean(rememberMe)
|
||||
}
|
||||
},
|
||||
handleLogin() {
|
||||
this.$refs.loginForm.validate(valid => {
|
||||
if (valid) {
|
||||
this.loading = true
|
||||
if (this.loginForm.rememberMe) {
|
||||
Cookies.set("username", this.loginForm.username, { expires: 30 })
|
||||
Cookies.set("password", encrypt(this.loginForm.password), { expires: 30 })
|
||||
Cookies.set('rememberMe', this.loginForm.rememberMe, { expires: 30 })
|
||||
} else {
|
||||
Cookies.remove("username")
|
||||
Cookies.remove("password")
|
||||
Cookies.remove('rememberMe')
|
||||
}
|
||||
const loginData = {
|
||||
username: this.loginForm.username,
|
||||
password: this.loginForm.password
|
||||
}
|
||||
this.$store.dispatch("Login", loginData).then(() => {
|
||||
this.$router.push({ path: this.redirect || "/" }).catch(()=>{})
|
||||
}).catch(() => {
|
||||
this.loading = false
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style rel="stylesheet/scss" lang="scss" scoped>
|
||||
.login {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
background-image: url("../assets/images/login-background.jpg");
|
||||
background-size: cover;
|
||||
}
|
||||
.title {
|
||||
margin: 0px auto 30px auto;
|
||||
text-align: center;
|
||||
color: #707070;
|
||||
}
|
||||
|
||||
.login-form {
|
||||
border-radius: 6px;
|
||||
background: #ffffff;
|
||||
width: 400px;
|
||||
padding: 25px 25px 5px 25px;
|
||||
z-index: 1;
|
||||
.el-input {
|
||||
height: 38px;
|
||||
input {
|
||||
height: 38px;
|
||||
}
|
||||
}
|
||||
.input-icon {
|
||||
height: 39px;
|
||||
width: 14px;
|
||||
margin-left: 2px;
|
||||
}
|
||||
}
|
||||
.login-tip {
|
||||
font-size: 13px;
|
||||
text-align: center;
|
||||
color: #bfbfbf;
|
||||
}
|
||||
.el-login-footer {
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
font-family: Arial;
|
||||
font-size: 12px;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
</style>
|
||||
+148
@@ -0,0 +1,148 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="24" class="card-box">
|
||||
<el-card>
|
||||
<div slot="header"><span><i class="el-icon-monitor"></i> 基本信息</span></div>
|
||||
<div class="el-table el-table--enable-row-hover el-table--medium">
|
||||
<table cellspacing="0" style="width: 100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">Redis版本</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ cache.info.redis_version }}</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">运行模式</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ cache.info.redis_mode == "standalone" ? "单机" : "集群" }}</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">端口</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ cache.info.tcp_port }}</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">客户端数</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ cache.info.connected_clients }}</div></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">运行时间(天)</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ cache.info.uptime_in_days }}</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">使用内存</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ cache.info.used_memory_human }}</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">使用CPU</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ parseFloat(cache.info.used_cpu_user_children).toFixed(2) }}</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">内存配置</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ cache.info.maxmemory_human }}</div></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">AOF是否开启</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ cache.info.aof_enabled == "0" ? "否" : "是" }}</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">RDB是否成功</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ cache.info.rdb_last_bgsave_status }}</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">Key数量</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="cache.dbSize">{{ cache.dbSize }} </div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">网络入口/出口</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ cache.info.instantaneous_input_kbps }}kps/{{cache.info.instantaneous_output_kbps}}kps</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12" class="card-box">
|
||||
<el-card>
|
||||
<div slot="header"><span><i class="el-icon-pie-chart"></i> 命令统计</span></div>
|
||||
<div class="el-table el-table--enable-row-hover el-table--medium">
|
||||
<div ref="commandstats" style="height: 420px" />
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12" class="card-box">
|
||||
<el-card>
|
||||
<div slot="header"><span><i class="el-icon-odometer"></i> 内存信息</span></div>
|
||||
<div class="el-table el-table--enable-row-hover el-table--medium">
|
||||
<div ref="usedmemory" style="height: 420px" />
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getCache } from "@/api/monitor/cache"
|
||||
import * as echarts from "echarts"
|
||||
|
||||
export default {
|
||||
name: "Cache",
|
||||
data() {
|
||||
return {
|
||||
// 统计命令信息
|
||||
commandstats: null,
|
||||
// 使用内存
|
||||
usedmemory: null,
|
||||
// cache信息
|
||||
cache: []
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getList()
|
||||
this.openLoading()
|
||||
},
|
||||
methods: {
|
||||
/** 查缓存询信息 */
|
||||
getList() {
|
||||
getCache().then((response) => {
|
||||
this.cache = response.data
|
||||
this.$modal.closeLoading()
|
||||
|
||||
this.commandstats = echarts.init(this.$refs.commandstats, "macarons")
|
||||
this.commandstats.setOption({
|
||||
tooltip: {
|
||||
trigger: "item",
|
||||
formatter: "{a} <br/>{b} : {c} ({d}%)",
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: "命令",
|
||||
type: "pie",
|
||||
roseType: "radius",
|
||||
radius: [15, 95],
|
||||
center: ["50%", "38%"],
|
||||
data: response.data.commandStats,
|
||||
animationEasing: "cubicInOut",
|
||||
animationDuration: 1000,
|
||||
}
|
||||
]
|
||||
})
|
||||
this.usedmemory = echarts.init(this.$refs.usedmemory, "macarons")
|
||||
this.usedmemory.setOption({
|
||||
tooltip: {
|
||||
formatter: "{b} <br/>{a} : " + this.cache.info.used_memory_human,
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: "峰值",
|
||||
type: "gauge",
|
||||
min: 0,
|
||||
max: 1000,
|
||||
detail: {
|
||||
formatter: this.cache.info.used_memory_human,
|
||||
},
|
||||
data: [
|
||||
{
|
||||
value: parseFloat(this.cache.info.used_memory_human),
|
||||
name: "内存消耗",
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
window.addEventListener("resize", () => {
|
||||
this.commandstats.resize()
|
||||
this.usedmemory.resize()
|
||||
})
|
||||
})
|
||||
},
|
||||
// 打开加载层
|
||||
openLoading() {
|
||||
this.$modal.loading("正在加载缓存监控数据,请稍候!")
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
+241
@@ -0,0 +1,241 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="8">
|
||||
<el-card style="height: calc(100vh - 125px)">
|
||||
<div slot="header">
|
||||
<span><i class="el-icon-collection"></i> 缓存列表</span>
|
||||
<el-button
|
||||
style="float: right; padding: 3px 0"
|
||||
type="text"
|
||||
icon="el-icon-refresh-right"
|
||||
@click="refreshCacheNames()"
|
||||
></el-button>
|
||||
</div>
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="cacheNames"
|
||||
:height="tableHeight"
|
||||
highlight-current-row
|
||||
@row-click="getCacheKeys"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-table-column
|
||||
label="序号"
|
||||
width="60"
|
||||
type="index"
|
||||
></el-table-column>
|
||||
|
||||
<el-table-column
|
||||
label="缓存名称"
|
||||
align="center"
|
||||
prop="cacheName"
|
||||
:show-overflow-tooltip="true"
|
||||
:formatter="nameFormatter"
|
||||
></el-table-column>
|
||||
|
||||
<el-table-column
|
||||
label="备注"
|
||||
align="center"
|
||||
prop="remark"
|
||||
:show-overflow-tooltip="true"
|
||||
/>
|
||||
<el-table-column
|
||||
label="操作"
|
||||
width="60"
|
||||
align="center"
|
||||
class-name="small-padding fixed-width"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<el-button
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-delete"
|
||||
@click="handleClearCacheName(scope.row)"
|
||||
></el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="8">
|
||||
<el-card style="height: calc(100vh - 125px)">
|
||||
<div slot="header">
|
||||
<span><i class="el-icon-key"></i> 键名列表</span>
|
||||
<el-button
|
||||
style="float: right; padding: 3px 0"
|
||||
type="text"
|
||||
icon="el-icon-refresh-right"
|
||||
@click="refreshCacheKeys()"
|
||||
></el-button>
|
||||
</div>
|
||||
<el-table
|
||||
v-loading="subLoading"
|
||||
:data="cacheKeys"
|
||||
:height="tableHeight"
|
||||
highlight-current-row
|
||||
@row-click="handleCacheValue"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-table-column
|
||||
label="序号"
|
||||
width="60"
|
||||
type="index"
|
||||
></el-table-column>
|
||||
<el-table-column
|
||||
label="缓存键名"
|
||||
align="center"
|
||||
:show-overflow-tooltip="true"
|
||||
:formatter="keyFormatter"
|
||||
>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="操作"
|
||||
width="60"
|
||||
align="center"
|
||||
class-name="small-padding fixed-width"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<el-button
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-delete"
|
||||
@click="handleClearCacheKey(scope.row)"
|
||||
></el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="8">
|
||||
<el-card :bordered="false" style="height: calc(100vh - 125px)">
|
||||
<div slot="header">
|
||||
<span><i class="el-icon-document"></i> 缓存内容</span>
|
||||
<el-button
|
||||
style="float: right; padding: 3px 0"
|
||||
type="text"
|
||||
icon="el-icon-refresh-right"
|
||||
@click="handleClearCacheAll()"
|
||||
>清理全部</el-button
|
||||
>
|
||||
</div>
|
||||
<el-form :model="cacheForm">
|
||||
<el-row :gutter="32">
|
||||
<el-col :offset="1" :span="22">
|
||||
<el-form-item label="缓存名称:" prop="cacheName">
|
||||
<el-input v-model="cacheForm.cacheName" :readOnly="true" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :offset="1" :span="22">
|
||||
<el-form-item label="缓存键名:" prop="cacheKey">
|
||||
<el-input v-model="cacheForm.cacheKey" :readOnly="true" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :offset="1" :span="22">
|
||||
<el-form-item label="缓存内容:" prop="cacheValue">
|
||||
<el-input
|
||||
v-model="cacheForm.cacheValue"
|
||||
type="textarea"
|
||||
:rows="8"
|
||||
:readOnly="true"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listCacheName, listCacheKey, getCacheValue, clearCacheName, clearCacheKey, clearCacheAll } from "@/api/monitor/cache"
|
||||
|
||||
export default {
|
||||
name: "CacheList",
|
||||
data() {
|
||||
return {
|
||||
cacheNames: [],
|
||||
cacheKeys: [],
|
||||
cacheForm: {},
|
||||
loading: true,
|
||||
subLoading: false,
|
||||
nowCacheName: "",
|
||||
tableHeight: window.innerHeight - 200
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getCacheNames()
|
||||
},
|
||||
methods: {
|
||||
/** 查询缓存名称列表 */
|
||||
getCacheNames() {
|
||||
this.loading = true
|
||||
listCacheName().then(response => {
|
||||
this.cacheNames = response.data
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
/** 刷新缓存名称列表 */
|
||||
refreshCacheNames() {
|
||||
this.getCacheNames()
|
||||
this.$modal.msgSuccess("刷新缓存列表成功")
|
||||
},
|
||||
/** 清理指定名称缓存 */
|
||||
handleClearCacheName(row) {
|
||||
clearCacheName(row.cacheName).then(response => {
|
||||
this.$modal.msgSuccess("清理缓存名称[" + row.cacheName + "]成功")
|
||||
this.getCacheKeys()
|
||||
})
|
||||
},
|
||||
/** 查询缓存键名列表 */
|
||||
getCacheKeys(row) {
|
||||
const cacheName = row !== undefined ? row.cacheName : this.nowCacheName
|
||||
if (cacheName === "") {
|
||||
return
|
||||
}
|
||||
this.subLoading = true
|
||||
listCacheKey(cacheName).then(response => {
|
||||
this.cacheKeys = response.data
|
||||
this.subLoading = false
|
||||
this.nowCacheName = cacheName
|
||||
})
|
||||
},
|
||||
/** 刷新缓存键名列表 */
|
||||
refreshCacheKeys() {
|
||||
this.getCacheKeys()
|
||||
this.$modal.msgSuccess("刷新键名列表成功")
|
||||
},
|
||||
/** 清理指定键名缓存 */
|
||||
handleClearCacheKey(cacheKey) {
|
||||
clearCacheKey(cacheKey).then(response => {
|
||||
this.$modal.msgSuccess("清理缓存键名[" + cacheKey + "]成功")
|
||||
this.getCacheKeys()
|
||||
})
|
||||
},
|
||||
/** 列表前缀去除 */
|
||||
nameFormatter(row) {
|
||||
return row.cacheName.replace(":", "")
|
||||
},
|
||||
/** 键名前缀去除 */
|
||||
keyFormatter(cacheKey) {
|
||||
return cacheKey.replace(this.nowCacheName, "")
|
||||
},
|
||||
/** 查询缓存内容详细 */
|
||||
handleCacheValue(cacheKey) {
|
||||
getCacheValue(this.nowCacheName, cacheKey).then(response => {
|
||||
this.cacheForm = response.data
|
||||
})
|
||||
},
|
||||
/** 清理全部缓存 */
|
||||
handleClearCacheAll() {
|
||||
clearCacheAll().then(response => {
|
||||
this.$modal.msgSuccess("清理全部缓存成功")
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,15 @@
|
||||
<template>
|
||||
<i-frame :src="url" />
|
||||
</template>
|
||||
<script>
|
||||
import iFrame from "@/components/iFrame/index"
|
||||
export default {
|
||||
name: "Druid",
|
||||
components: { iFrame },
|
||||
data() {
|
||||
return {
|
||||
url: process.env.VUE_APP_BASE_API + "/druid/login.html"
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,197 @@
|
||||
<template>
|
||||
<el-dialog :title="type === 'log' ? '调度日志详细' : '任务详细'" :visible.sync="visible" width="780px" append-to-body @close="$emit('update:visible', false)">
|
||||
<div class="detail-wrap">
|
||||
|
||||
<template v-if="type === 'log'">
|
||||
<!-- 基本信息 -->
|
||||
<div class="detail-card">
|
||||
<div class="detail-card-title"><i class="el-icon-info"></i> 基本信息</div>
|
||||
<el-row class="detail-row">
|
||||
<el-col :span="12">
|
||||
<div class="detail-item"><span class="detail-label">日志编号</span><span class="detail-value">{{ form.jobLogId }}</span></div>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">执行状态</span>
|
||||
<el-tag v-if="form.status == 0" type="success" size="small">正常</el-tag>
|
||||
<el-tag v-else type="danger" size="small">失败</el-tag>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row class="detail-row">
|
||||
<el-col :span="12">
|
||||
<div class="detail-item"><span class="detail-label">开始时间</span><span class="detail-value">{{ form.startTime }}</span></div>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<div class="detail-item"><span class="detail-label">结束时间</span><span class="detail-value">{{ form.endTime }}</span></div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row class="detail-row">
|
||||
<el-col :span="12">
|
||||
<div class="detail-item"><span class="detail-label">记录时间</span><span class="detail-value">{{ form.createTime }}</span></div>
|
||||
</el-col>
|
||||
<el-col :span="12" v-if="form.status == 0 && form.startTime && form.endTime">
|
||||
<div class="detail-item"><span class="detail-label">执行耗时</span><span class="detail-value">{{ costTime }} 毫秒</span></div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
<!-- 任务信息 -->
|
||||
<div class="detail-card">
|
||||
<div class="detail-card-title"><i class="el-icon-time"></i> 任务信息</div>
|
||||
<el-row class="detail-row">
|
||||
<el-col :span="12">
|
||||
<div class="detail-item"><span class="detail-label">任务名称</span><span class="detail-value">{{ form.jobName }}</span></div>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">任务分组</span>
|
||||
<dict-tag :options="dict.type.sys_job_group" :value="form.jobGroup" />
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row class="detail-row">
|
||||
<el-col :span="24">
|
||||
<div class="detail-item"><span class="detail-label">日志信息</span><span class="detail-value">{{ form.jobMessage }}</span></div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
<!-- 调用目标 -->
|
||||
<div class="detail-card">
|
||||
<div class="detail-card-title"><i class="el-icon-s-operation"></i> 调用目标</div>
|
||||
<div class="code-body">
|
||||
<div class="code-wrap"><pre class="code-pre">{{ form.invokeTarget || '(无)' }}</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 异常信息 -->
|
||||
<div class="detail-card" v-if="form.status == 1">
|
||||
<div class="detail-card-title error-title"><i class="el-icon-warning"></i> 异常信息</div>
|
||||
<div class="error-body"><div class="error-msg">{{ form.exceptionInfo }}</div></div>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<!-- 任务配置 -->
|
||||
<div class="detail-card">
|
||||
<div class="detail-card-title"><i class="el-icon-setting"></i> 任务配置</div>
|
||||
<el-row class="detail-row">
|
||||
<el-col :span="12">
|
||||
<div class="detail-item"><span class="detail-label">任务编号</span><span class="detail-value">{{ form.jobId }}</span></div>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<div class="detail-item"><span class="detail-label">任务名称</span><span class="detail-value">{{ form.jobName }}</span></div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row class="detail-row">
|
||||
<el-col :span="12">
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">任务分组</span>
|
||||
<dict-tag :options="dict.type.sys_job_group" :value="form.jobGroup" />
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">执行状态</span>
|
||||
<el-tag v-if="form.status == 0" type="success" size="small">正常</el-tag>
|
||||
<el-tag v-else type="info" size="small">暂停</el-tag>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
<!-- 调度信息 -->
|
||||
<div class="detail-card">
|
||||
<div class="detail-card-title"><i class="el-icon-date"></i> 调度信息</div>
|
||||
<el-row class="detail-row">
|
||||
<el-col :span="12">
|
||||
<div class="detail-item"><span class="detail-label">cron 表达式</span><span class="detail-value mono">{{ form.cronExpression }}</span></div>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<div class="detail-item"><span class="detail-label">下次执行时间</span><span class="detail-value">{{ parseTime(form.nextValidTime) }}</span></div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row class="detail-row">
|
||||
<el-col :span="12">
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">执行策略</span>
|
||||
<el-tag v-if="form.misfirePolicy == 0" type="info" size="small">默认策略</el-tag>
|
||||
<el-tag v-else-if="form.misfirePolicy == 1" type="warning" size="small">立即执行</el-tag>
|
||||
<el-tag v-else-if="form.misfirePolicy == 2" type="primary" size="small">执行一次</el-tag>
|
||||
<el-tag v-else-if="form.misfirePolicy == 3" type="danger" size="small">放弃执行</el-tag>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">并发执行</span>
|
||||
<el-tag v-if="form.concurrent == 0" type="success" size="small">允许</el-tag>
|
||||
<el-tag v-else type="danger" size="small">禁止</el-tag>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
<!-- 执行方法 -->
|
||||
<div class="detail-card">
|
||||
<div class="detail-card-title"><i class="el-icon-s-operation"></i> 执行方法</div>
|
||||
<div class="code-body">
|
||||
<div class="code-wrap"><pre class="code-pre">{{ form.invokeTarget || '(无)' }}</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 元信息 -->
|
||||
<div class="detail-card">
|
||||
<div class="detail-card-title"><i class="el-icon-document"></i> 元信息</div>
|
||||
<el-row class="detail-row">
|
||||
<el-col :span="12">
|
||||
<div class="detail-item"><span class="detail-label">创建人</span><span class="detail-value">{{ form.createBy || '-' }}</span></div>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<div class="detail-item"><span class="detail-label">创建时间</span><span class="detail-value">{{ form.createTime }}</span></div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row class="detail-row">
|
||||
<el-col :span="12">
|
||||
<div class="detail-item"><span class="detail-label">更新人</span><span class="detail-value">{{ form.updateBy || '-' }}</span></div>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<div class="detail-item"><span class="detail-label">更新时间</span><span class="detail-value">{{ form.updateTime || '-' }}</span></div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row class="detail-row" v-if="form.remark">
|
||||
<el-col :span="24">
|
||||
<div class="detail-item"><span class="detail-label">备注</span><span class="detail-value">{{ form.remark }}</span></div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
dicts: ['sys_job_group'],
|
||||
props: {
|
||||
visible: { type: Boolean, default: false },
|
||||
row: { type: Object, default: () => ({}) },
|
||||
// 'job' 任务详细 | 'log' 调度日志详细
|
||||
type: { type: String, default: 'job' }
|
||||
},
|
||||
computed: {
|
||||
form() { return this.row || {} },
|
||||
costTime() {
|
||||
if (!this.form.startTime || !this.form.endTime) return 0
|
||||
return new Date(this.form.endTime).getTime() - new Date(this.form.startTime).getTime()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.detail-label {
|
||||
width: 80px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,508 @@
|
||||
<template>
|
||||
<div class="app-container monitor-job">
|
||||
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
|
||||
<el-form-item label="任务名称" prop="jobName">
|
||||
<el-input
|
||||
v-model="queryParams.jobName"
|
||||
placeholder="请输入任务名称"
|
||||
clearable
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="任务组名" prop="jobGroup">
|
||||
<el-select v-model="queryParams.jobGroup" placeholder="请选择任务组名" clearable>
|
||||
<el-option
|
||||
v-for="dict in dict.type.sys_job_group"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="任务状态" prop="status">
|
||||
<el-select v-model="queryParams.status" placeholder="请选择任务状态" clearable>
|
||||
<el-option
|
||||
v-for="dict in dict.type.sys_job_status"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
icon="el-icon-plus"
|
||||
size="mini"
|
||||
@click="handleAdd"
|
||||
v-hasPermi="['monitor:job:add']"
|
||||
>新增</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
icon="el-icon-edit"
|
||||
size="mini"
|
||||
:disabled="single"
|
||||
@click="handleUpdate"
|
||||
v-hasPermi="['monitor:job:edit']"
|
||||
>修改</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="danger"
|
||||
plain
|
||||
icon="el-icon-delete"
|
||||
size="mini"
|
||||
:disabled="multiple"
|
||||
@click="handleDelete"
|
||||
v-hasPermi="['monitor:job:remove']"
|
||||
>删除</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="warning"
|
||||
plain
|
||||
icon="el-icon-download"
|
||||
size="mini"
|
||||
@click="handleExport"
|
||||
v-hasPermi="['monitor:job:export']"
|
||||
>导出</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="info"
|
||||
plain
|
||||
icon="el-icon-s-operation"
|
||||
size="mini"
|
||||
@click="handleJobLog"
|
||||
v-hasPermi="['monitor:job:query']"
|
||||
>日志</el-button>
|
||||
</el-col>
|
||||
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
|
||||
<el-table v-loading="loading" :data="jobList" @selection-change="handleSelectionChange" :height="tableHeight">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="任务编号" width="100" align="center" prop="jobId" />
|
||||
<el-table-column label="任务名称" align="center" :show-overflow-tooltip="true">
|
||||
<template slot-scope="scope">
|
||||
<a class="link-type" style="cursor:pointer" @click="handleView(scope.row)">{{ scope.row.jobName }}</a>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="任务组名" align="center" prop="jobGroup">
|
||||
<template slot-scope="scope">
|
||||
<dict-tag :options="dict.type.sys_job_group" :value="scope.row.jobGroup"/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="调用目标字符串" align="center" prop="invokeTarget" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="cron执行表达式" align="center" prop="cronExpression" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="状态" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-switch
|
||||
v-model="scope.row.status"
|
||||
active-value="0"
|
||||
inactive-value="1"
|
||||
@change="handleStatusChange(scope.row)"
|
||||
></el-switch>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
|
||||
<template slot-scope="scope">
|
||||
<el-button
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-edit"
|
||||
@click="handleUpdate(scope.row)"
|
||||
v-hasPermi="['monitor:job:edit']"
|
||||
>修改</el-button>
|
||||
<el-button
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-delete"
|
||||
@click="handleDelete(scope.row)"
|
||||
v-hasPermi="['monitor:job:remove']"
|
||||
>删除</el-button>
|
||||
<el-dropdown size="mini" @command="(command) => handleCommand(command, scope.row)" v-hasPermi="['monitor:job:changeStatus', 'monitor:job:query']">
|
||||
<el-button size="mini" type="text" icon="el-icon-d-arrow-right">更多</el-button>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<el-dropdown-item command="handleRun" icon="el-icon-caret-right"
|
||||
v-hasPermi="['monitor:job:changeStatus']">执行一次</el-dropdown-item>
|
||||
<el-dropdown-item command="handleJobLog" icon="el-icon-s-operation"
|
||||
v-hasPermi="['monitor:job:query']">调度日志</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination
|
||||
v-show="total>0"
|
||||
:total="total"
|
||||
:page.sync="queryParams.pageNum"
|
||||
:limit.sync="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
|
||||
<!-- 添加或修改定时任务对话框 -->
|
||||
<el-dialog :title="title" :visible.sync="open" width="800px" append-to-body>
|
||||
<el-form ref="form" :model="form" :rules="rules" label-width="120px">
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="任务名称" prop="jobName">
|
||||
<el-input v-model="form.jobName" placeholder="请输入任务名称" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="任务分组" prop="jobGroup">
|
||||
<el-select v-model="form.jobGroup" placeholder="请选择任务分组">
|
||||
<el-option
|
||||
v-for="dict in dict.type.sys_job_group"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-form-item prop="invokeTarget">
|
||||
<span slot="label">
|
||||
调用方法
|
||||
<el-tooltip placement="top">
|
||||
<div slot="content">
|
||||
Bean调用示例:ryTask.ryParams('ry')
|
||||
<br />Class类调用示例:com.roomroot.quartz.task.RyTask.ryParams('ry')
|
||||
<br />参数说明:支持字符串,布尔类型,长整型,浮点型,整型
|
||||
</div>
|
||||
<i class="el-icon-question"></i>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
<el-input v-model="form.invokeTarget" placeholder="请输入调用目标字符串" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="cron表达式" prop="cronExpression">
|
||||
<el-input v-model="form.cronExpression" placeholder="请输入cron执行表达式">
|
||||
<template slot="append">
|
||||
<el-button type="primary" @click="handleShowCron">
|
||||
生成表达式
|
||||
<i class="el-icon-time el-icon--right"></i>
|
||||
</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" v-if="form.jobId !== undefined">
|
||||
<el-form-item label="状态">
|
||||
<el-radio-group v-model="form.status">
|
||||
<el-radio
|
||||
v-for="dict in dict.type.sys_job_status"
|
||||
:key="dict.value"
|
||||
:label="dict.value"
|
||||
>{{dict.label}}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="执行策略" prop="misfirePolicy">
|
||||
<el-radio-group v-model="form.misfirePolicy" size="small">
|
||||
<el-radio-button label="1">立即执行</el-radio-button>
|
||||
<el-radio-button label="2">执行一次</el-radio-button>
|
||||
<el-radio-button label="3">放弃执行</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="是否并发" prop="concurrent">
|
||||
<el-radio-group v-model="form.concurrent" size="small">
|
||||
<el-radio-button label="0">允许</el-radio-button>
|
||||
<el-radio-button label="1">禁止</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog title="Cron表达式生成器" :visible.sync="openCron" append-to-body destroy-on-close class="scrollbar">
|
||||
<crontab @hide="openCron=false" @fill="crontabFill" :expression="expression"></crontab>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 任务日志详细 -->
|
||||
<job-detail :visible.sync="openView" :row="form" type="job" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listJob, getJob, delJob, addJob, updateJob, runJob, changeJobStatus } from "@/api/monitor/job"
|
||||
import JobDetail from './detail'
|
||||
import Crontab from '@/components/Crontab'
|
||||
|
||||
export default {
|
||||
components: { Crontab, JobDetail },
|
||||
name: "Job",
|
||||
dicts: ['sys_job_group', 'sys_job_status'],
|
||||
data() {
|
||||
return {
|
||||
// 遮罩层
|
||||
loading: true,
|
||||
// 选中数组
|
||||
ids: [],
|
||||
// 非单个禁用
|
||||
single: true,
|
||||
// 非多个禁用
|
||||
multiple: true,
|
||||
// 显示搜索条件
|
||||
showSearch: true,
|
||||
// 总条数
|
||||
total: 0,
|
||||
// 定时任务表格数据
|
||||
jobList: [],
|
||||
// 弹出层标题
|
||||
title: "",
|
||||
// 是否显示弹出层
|
||||
open: false,
|
||||
// 是否显示详细弹出层
|
||||
openView: false,
|
||||
// 是否显示Cron表达式弹出层
|
||||
openCron: false,
|
||||
// 传入的表达式
|
||||
expression: "",
|
||||
// 表格高度
|
||||
tableHeight: window.innerHeight - 240,
|
||||
// 查询参数
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
jobName: undefined,
|
||||
jobGroup: undefined,
|
||||
status: undefined
|
||||
},
|
||||
// 表单参数
|
||||
form: {},
|
||||
// 表单校验
|
||||
rules: {
|
||||
jobName: [
|
||||
{ required: true, message: "任务名称不能为空", trigger: "blur" }
|
||||
],
|
||||
invokeTarget: [
|
||||
{ required: true, message: "调用目标字符串不能为空", trigger: "blur" }
|
||||
],
|
||||
cronExpression: [
|
||||
{ required: true, message: "cron执行表达式不能为空", trigger: "blur" }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getList()
|
||||
},
|
||||
mounted() {
|
||||
this.getTableHeight()
|
||||
window.addEventListener('resize', this.getTableHeight)
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.removeEventListener('resize', this.getTableHeight)
|
||||
},
|
||||
watch: {
|
||||
showSearch() {
|
||||
this.$nextTick(() => {
|
||||
this.getTableHeight()
|
||||
})
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getTableHeight() {
|
||||
const offset = this.showSearch ? 240 : 190
|
||||
this.tableHeight = window.innerHeight - offset
|
||||
},
|
||||
/** 查询定时任务列表 */
|
||||
getList() {
|
||||
this.loading = true
|
||||
listJob(this.queryParams).then(response => {
|
||||
this.jobList = response.rows
|
||||
this.total = response.total
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
// 任务组名字典翻译
|
||||
jobGroupFormat(row, column) {
|
||||
return this.selectDictLabel(this.dict.type.sys_job_group, row.jobGroup)
|
||||
},
|
||||
// 取消按钮
|
||||
cancel() {
|
||||
this.open = false
|
||||
this.reset()
|
||||
},
|
||||
// 表单重置
|
||||
reset() {
|
||||
this.form = {
|
||||
jobId: undefined,
|
||||
jobName: undefined,
|
||||
jobGroup: undefined,
|
||||
invokeTarget: undefined,
|
||||
cronExpression: undefined,
|
||||
misfirePolicy: 1,
|
||||
concurrent: 1,
|
||||
status: "0"
|
||||
}
|
||||
this.resetForm("form")
|
||||
},
|
||||
/** 搜索按钮操作 */
|
||||
handleQuery() {
|
||||
this.queryParams.pageNum = 1
|
||||
this.getList()
|
||||
},
|
||||
/** 重置按钮操作 */
|
||||
resetQuery() {
|
||||
this.resetForm("queryForm")
|
||||
this.handleQuery()
|
||||
},
|
||||
// 多选框选中数据
|
||||
handleSelectionChange(selection) {
|
||||
this.ids = selection.map(item => item.jobId)
|
||||
this.single = selection.length != 1
|
||||
this.multiple = !selection.length
|
||||
},
|
||||
// 更多操作触发
|
||||
handleCommand(command, row) {
|
||||
switch (command) {
|
||||
case "handleRun":
|
||||
this.handleRun(row)
|
||||
break
|
||||
case "handleView":
|
||||
this.handleView(row)
|
||||
break
|
||||
case "handleJobLog":
|
||||
this.handleJobLog(row)
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
},
|
||||
// 任务状态修改
|
||||
handleStatusChange(row) {
|
||||
let text = row.status === "0" ? "启用" : "停用"
|
||||
this.$modal.confirm('确认要"' + text + '""' + row.jobName + '"任务吗?').then(function() {
|
||||
return changeJobStatus(row.jobId, row.status)
|
||||
}).then(() => {
|
||||
this.$modal.msgSuccess(text + "成功")
|
||||
}).catch(function() {
|
||||
row.status = row.status === "0" ? "1" : "0"
|
||||
})
|
||||
},
|
||||
/* 立即执行一次 */
|
||||
handleRun(row) {
|
||||
this.$modal.confirm('确认要立即执行一次"' + row.jobName + '"任务吗?').then(function() {
|
||||
return runJob(row.jobId, row.jobGroup)
|
||||
}).then(() => {
|
||||
this.$modal.msgSuccess("执行成功")
|
||||
}).catch(() => {})
|
||||
},
|
||||
/** 任务详细信息 */
|
||||
handleView(row) {
|
||||
getJob(row.jobId).then(response => {
|
||||
this.form = response.data
|
||||
this.openView = true
|
||||
})
|
||||
},
|
||||
/** cron表达式按钮操作 */
|
||||
handleShowCron() {
|
||||
this.expression = this.form.cronExpression
|
||||
this.openCron = true
|
||||
},
|
||||
/** 确定后回传值 */
|
||||
crontabFill(value) {
|
||||
this.form.cronExpression = value
|
||||
},
|
||||
/** 任务日志列表查询 */
|
||||
handleJobLog(row) {
|
||||
const jobId = row.jobId || 0
|
||||
this.$router.push('/monitor/job-log/index/' + jobId)
|
||||
},
|
||||
/** 新增按钮操作 */
|
||||
handleAdd() {
|
||||
this.reset()
|
||||
this.open = true
|
||||
this.title = "添加任务"
|
||||
},
|
||||
/** 修改按钮操作 */
|
||||
handleUpdate(row) {
|
||||
this.reset()
|
||||
const jobId = row.jobId || this.ids
|
||||
getJob(jobId).then(response => {
|
||||
this.form = response.data
|
||||
this.open = true
|
||||
this.title = "修改任务"
|
||||
})
|
||||
},
|
||||
/** 提交按钮 */
|
||||
submitForm() {
|
||||
this.$refs["form"].validate(valid => {
|
||||
if (valid) {
|
||||
if (this.form.jobId != undefined) {
|
||||
updateJob(this.form).then(() => {
|
||||
this.$modal.msgSuccess("修改成功")
|
||||
this.open = false
|
||||
this.getList()
|
||||
})
|
||||
} else {
|
||||
addJob(this.form).then(() => {
|
||||
this.$modal.msgSuccess("新增成功")
|
||||
this.open = false
|
||||
this.getList()
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
/** 删除按钮操作 */
|
||||
handleDelete(row) {
|
||||
const jobIds = row.jobId || this.ids
|
||||
this.$modal.confirm('是否确认删除定时任务编号为"' + jobIds + '"的数据项?').then(function() {
|
||||
return delJob(jobIds)
|
||||
}).then(() => {
|
||||
this.getList()
|
||||
this.$modal.msgSuccess("删除成功")
|
||||
}).catch(() => {})
|
||||
},
|
||||
/** 导出按钮操作 */
|
||||
handleExport() {
|
||||
this.download('monitor/job/export', {
|
||||
...this.queryParams
|
||||
}, `job_${new Date().getTime()}.xlsx`)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.monitor-job ::v-deep .el-table ::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
.monitor-job ::v-deep .el-table {
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
.monitor-job ::v-deep .el-table__body-wrapper::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
.monitor-job ::v-deep .el-table__body-wrapper {
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,300 @@
|
||||
<template>
|
||||
<div class="app-container monitor-job-log">
|
||||
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
|
||||
<el-form-item label="任务名称" prop="jobName">
|
||||
<el-input
|
||||
v-model="queryParams.jobName"
|
||||
placeholder="请输入任务名称"
|
||||
clearable
|
||||
style="width: 240px"
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="任务组名" prop="jobGroup">
|
||||
<el-select
|
||||
v-model="queryParams.jobGroup"
|
||||
placeholder="请选择任务组名"
|
||||
clearable
|
||||
style="width: 240px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in dict.type.sys_job_group"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="执行状态" prop="status">
|
||||
<el-select
|
||||
v-model="queryParams.status"
|
||||
placeholder="请选择执行状态"
|
||||
clearable
|
||||
style="width: 240px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in dict.type.sys_common_status"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="执行时间">
|
||||
<el-date-picker
|
||||
v-model="dateRange"
|
||||
style="width: 240px"
|
||||
value-format="yyyy-MM-dd"
|
||||
type="daterange"
|
||||
range-separator="-"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
></el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="danger"
|
||||
plain
|
||||
icon="el-icon-delete"
|
||||
size="mini"
|
||||
:disabled="multiple"
|
||||
@click="handleDelete"
|
||||
v-hasPermi="['monitor:job:remove']"
|
||||
>删除</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="danger"
|
||||
plain
|
||||
icon="el-icon-delete"
|
||||
size="mini"
|
||||
@click="handleClean"
|
||||
v-hasPermi="['monitor:job:remove']"
|
||||
>清空</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="warning"
|
||||
plain
|
||||
icon="el-icon-download"
|
||||
size="mini"
|
||||
@click="handleExport"
|
||||
v-hasPermi="['monitor:job:export']"
|
||||
>导出</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
icon="el-icon-close"
|
||||
size="mini"
|
||||
@click="handleClose"
|
||||
>关闭</el-button>
|
||||
</el-col>
|
||||
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
|
||||
<el-table v-loading="loading" :data="jobLogList" @selection-change="handleSelectionChange" :height="tableHeight">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="日志编号" width="80" align="center" prop="jobLogId" />
|
||||
<el-table-column label="任务名称" align="center" prop="jobName" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="任务组名" align="center" prop="jobGroup" :show-overflow-tooltip="true">
|
||||
<template slot-scope="scope">
|
||||
<dict-tag :options="dict.type.sys_job_group" :value="scope.row.jobGroup"/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="调用目标字符串" align="center" prop="invokeTarget" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="日志信息" align="center" prop="jobMessage" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="执行状态" align="center" prop="status">
|
||||
<template slot-scope="scope">
|
||||
<dict-tag :options="dict.type.sys_common_status" :value="scope.row.status"/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="执行时间" align="center" prop="createTime" width="180">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ parseTime(scope.row.createTime) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
|
||||
<template slot-scope="scope">
|
||||
<el-button
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-view"
|
||||
@click="handleView(scope.row)"
|
||||
v-hasPermi="['monitor:job:query']"
|
||||
>详细</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination
|
||||
v-show="total>0"
|
||||
:total="total"
|
||||
:page.sync="queryParams.pageNum"
|
||||
:limit.sync="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
|
||||
<job-log-detail :visible.sync="open" :row="form" type="log" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getJob} from "@/api/monitor/job"
|
||||
import { listJobLog, delJobLog, cleanJobLog } from "@/api/monitor/jobLog"
|
||||
import JobLogDetail from './detail'
|
||||
|
||||
export default {
|
||||
name: "JobLog",
|
||||
components: { JobLogDetail },
|
||||
dicts: ['sys_common_status', 'sys_job_group'],
|
||||
data() {
|
||||
return {
|
||||
// 遮罩层
|
||||
loading: true,
|
||||
// 选中数组
|
||||
ids: [],
|
||||
// 非多个禁用
|
||||
multiple: true,
|
||||
// 显示搜索条件
|
||||
showSearch: true,
|
||||
// 总条数
|
||||
total: 0,
|
||||
// 调度日志表格数据
|
||||
jobLogList: [],
|
||||
// 是否显示弹出层
|
||||
open: false,
|
||||
// 日期范围
|
||||
dateRange: [],
|
||||
// 表单参数
|
||||
form: {},
|
||||
// 表格高度
|
||||
tableHeight: window.innerHeight - 240,
|
||||
// 查询参数
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
jobName: undefined,
|
||||
jobGroup: undefined,
|
||||
status: undefined
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
const jobId = this.$route.params && this.$route.params.jobId
|
||||
if (jobId !== undefined && jobId != 0) {
|
||||
getJob(jobId).then(response => {
|
||||
this.queryParams.jobName = response.data.jobName
|
||||
this.queryParams.jobGroup = response.data.jobGroup
|
||||
this.getList()
|
||||
})
|
||||
} else {
|
||||
this.getList()
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.getTableHeight()
|
||||
window.addEventListener('resize', this.getTableHeight)
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.removeEventListener('resize', this.getTableHeight)
|
||||
},
|
||||
watch: {
|
||||
showSearch() {
|
||||
this.$nextTick(() => {
|
||||
this.getTableHeight()
|
||||
})
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getTableHeight() {
|
||||
const offset = this.showSearch ? 240 : 190
|
||||
this.tableHeight = window.innerHeight - offset
|
||||
},
|
||||
/** 查询调度日志列表 */
|
||||
getList() {
|
||||
this.loading = true
|
||||
listJobLog(this.addDateRange(this.queryParams, this.dateRange)).then(response => {
|
||||
this.jobLogList = response.rows
|
||||
this.total = response.total
|
||||
this.loading = false
|
||||
}
|
||||
)
|
||||
},
|
||||
// 返回按钮
|
||||
handleClose() {
|
||||
const obj = { path: "/monitor/job" }
|
||||
this.$tab.closeOpenPage(obj)
|
||||
},
|
||||
/** 搜索按钮操作 */
|
||||
handleQuery() {
|
||||
this.queryParams.pageNum = 1
|
||||
this.getList()
|
||||
},
|
||||
/** 重置按钮操作 */
|
||||
resetQuery() {
|
||||
this.dateRange = []
|
||||
this.resetForm("queryForm")
|
||||
this.handleQuery()
|
||||
},
|
||||
// 多选框选中数据
|
||||
handleSelectionChange(selection) {
|
||||
this.ids = selection.map(item => item.jobLogId)
|
||||
this.multiple = !selection.length
|
||||
},
|
||||
/** 详细按钮操作 */
|
||||
handleView(row) {
|
||||
this.open = true
|
||||
this.form = row
|
||||
},
|
||||
/** 删除按钮操作 */
|
||||
handleDelete(row) {
|
||||
const jobLogIds = this.ids
|
||||
this.$modal.confirm('是否确认删除调度日志编号为"' + jobLogIds + '"的数据项?').then(function() {
|
||||
return delJobLog(jobLogIds)
|
||||
}).then(() => {
|
||||
this.getList()
|
||||
this.$modal.msgSuccess("删除成功")
|
||||
}).catch(() => {})
|
||||
},
|
||||
/** 清空按钮操作 */
|
||||
handleClean() {
|
||||
this.$modal.confirm('是否确认清空所有调度日志数据项?').then(function() {
|
||||
return cleanJobLog()
|
||||
}).then(() => {
|
||||
this.getList()
|
||||
this.$modal.msgSuccess("清空成功")
|
||||
}).catch(() => {})
|
||||
},
|
||||
/** 导出按钮操作 */
|
||||
handleExport() {
|
||||
this.download('/monitor/jobLog/export', {
|
||||
...this.queryParams
|
||||
}, `log_${new Date().getTime()}.xlsx`)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.monitor-job-log ::v-deep .el-table ::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
.monitor-job-log ::v-deep .el-table {
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
.monitor-job-log ::v-deep .el-table__body-wrapper::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
.monitor-job-log ::v-deep .el-table__body-wrapper {
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,274 @@
|
||||
<template>
|
||||
<div class="app-container monitor-logininfor">
|
||||
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
|
||||
<el-form-item label="登录地址" prop="ipaddr">
|
||||
<el-input
|
||||
v-model="queryParams.ipaddr"
|
||||
placeholder="请输入登录地址"
|
||||
clearable
|
||||
style="width: 240px;"
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="用户名称" prop="userName">
|
||||
<el-input
|
||||
v-model="queryParams.userName"
|
||||
placeholder="请输入用户名称"
|
||||
clearable
|
||||
style="width: 240px;"
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select
|
||||
v-model="queryParams.status"
|
||||
placeholder="登录状态"
|
||||
clearable
|
||||
style="width: 240px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in dict.type.sys_common_status"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="登录时间">
|
||||
<el-date-picker
|
||||
v-model="dateRange"
|
||||
style="width: 240px"
|
||||
value-format="yyyy-MM-dd HH:mm:ss"
|
||||
type="daterange"
|
||||
range-separator="-"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
:default-time="['00:00:00', '23:59:59']"
|
||||
></el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="danger"
|
||||
plain
|
||||
icon="el-icon-delete"
|
||||
size="mini"
|
||||
:disabled="multiple"
|
||||
@click="handleDelete"
|
||||
v-hasPermi="['monitor:logininfor:remove']"
|
||||
>删除</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="danger"
|
||||
plain
|
||||
icon="el-icon-delete"
|
||||
size="mini"
|
||||
@click="handleClean"
|
||||
v-hasPermi="['monitor:logininfor:remove']"
|
||||
>清空</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
icon="el-icon-unlock"
|
||||
size="mini"
|
||||
:disabled="single"
|
||||
@click="handleUnlock"
|
||||
v-hasPermi="['monitor:logininfor:unlock']"
|
||||
>解锁</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="warning"
|
||||
plain
|
||||
icon="el-icon-download"
|
||||
size="mini"
|
||||
@click="handleExport"
|
||||
v-hasPermi="['monitor:logininfor:export']"
|
||||
>导出</el-button>
|
||||
</el-col>
|
||||
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
|
||||
<div class="table-wrapper">
|
||||
<el-table ref="tables" v-loading="loading" :data="list" @selection-change="handleSelectionChange" :default-sort="defaultSort" @sort-change="handleSortChange" height="100%">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="访问编号" align="center" prop="infoId" />
|
||||
<el-table-column label="用户名称" align="center" prop="userName" :show-overflow-tooltip="true" sortable="custom" :sort-orders="['descending', 'ascending']" />
|
||||
<el-table-column label="登录地址" align="center" prop="ipaddr" width="130" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="登录地点" align="center" prop="loginLocation" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="浏览器" align="center" prop="browser" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="操作系统" align="center" prop="os" />
|
||||
<el-table-column label="登录状态" align="center" prop="status">
|
||||
<template slot-scope="scope">
|
||||
<dict-tag :options="dict.type.sys_common_status" :value="scope.row.status"/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作信息" align="center" prop="msg" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="登录日期" align="center" prop="loginTime" sortable="custom" :sort-orders="['descending', 'ascending']" width="180">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ parseTime(scope.row.loginTime) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<pagination
|
||||
v-show="total>0"
|
||||
:total="total"
|
||||
:page.sync="queryParams.pageNum"
|
||||
:limit.sync="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { list, delLogininfor, cleanLogininfor, unlockLogininfor } from "@/api/monitor/logininfor"
|
||||
|
||||
export default {
|
||||
name: "Logininfor",
|
||||
dicts: ['sys_common_status'],
|
||||
data() {
|
||||
return {
|
||||
// 遮罩层
|
||||
loading: true,
|
||||
// 选中数组
|
||||
ids: [],
|
||||
// 非单个禁用
|
||||
single: true,
|
||||
// 非多个禁用
|
||||
multiple: true,
|
||||
// 选择用户名
|
||||
selectName: "",
|
||||
// 显示搜索条件
|
||||
showSearch: true,
|
||||
// 总条数
|
||||
total: 0,
|
||||
// 表格数据
|
||||
list: [],
|
||||
// 日期范围
|
||||
dateRange: [],
|
||||
// 默认排序
|
||||
defaultSort: { prop: "loginTime", order: "descending" },
|
||||
// 查询参数
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
ipaddr: undefined,
|
||||
userName: undefined,
|
||||
status: undefined
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getList()
|
||||
},
|
||||
methods: {
|
||||
/** 查询登录日志列表 */
|
||||
getList() {
|
||||
this.loading = true
|
||||
list(this.addDateRange(this.queryParams, this.dateRange)).then(response => {
|
||||
this.list = response.rows
|
||||
this.total = response.total
|
||||
this.loading = false
|
||||
}
|
||||
)
|
||||
},
|
||||
/** 搜索按钮操作 */
|
||||
handleQuery() {
|
||||
this.queryParams.pageNum = 1
|
||||
this.getList()
|
||||
},
|
||||
/** 重置按钮操作 */
|
||||
resetQuery() {
|
||||
this.dateRange = []
|
||||
this.resetForm("queryForm")
|
||||
this.queryParams.pageNum = 1
|
||||
this.$refs.tables.sort(this.defaultSort.prop, this.defaultSort.order)
|
||||
},
|
||||
/** 多选框选中数据 */
|
||||
handleSelectionChange(selection) {
|
||||
this.ids = selection.map(item => item.infoId)
|
||||
this.single = selection.length!=1
|
||||
this.multiple = !selection.length
|
||||
this.selectName = selection.map(item => item.userName)
|
||||
},
|
||||
/** 排序触发事件 */
|
||||
handleSortChange(column, prop, order) {
|
||||
this.queryParams.orderByColumn = column.prop
|
||||
this.queryParams.isAsc = column.order
|
||||
this.getList()
|
||||
},
|
||||
/** 删除按钮操作 */
|
||||
handleDelete(row) {
|
||||
const infoIds = row.infoId || this.ids
|
||||
this.$modal.confirm('是否确认删除访问编号为"' + infoIds + '"的数据项?').then(function() {
|
||||
return delLogininfor(infoIds)
|
||||
}).then(() => {
|
||||
this.getList()
|
||||
this.$modal.msgSuccess("删除成功")
|
||||
}).catch(() => {})
|
||||
},
|
||||
/** 清空按钮操作 */
|
||||
handleClean() {
|
||||
this.$modal.confirm('是否确认清空所有登录日志数据项?').then(function() {
|
||||
return cleanLogininfor()
|
||||
}).then(() => {
|
||||
this.getList()
|
||||
this.$modal.msgSuccess("清空成功")
|
||||
}).catch(() => {})
|
||||
},
|
||||
/** 解锁按钮操作 */
|
||||
handleUnlock() {
|
||||
const username = this.selectName
|
||||
this.$modal.confirm('是否确认解锁用户"' + username + '"数据项?').then(function() {
|
||||
return unlockLogininfor(username)
|
||||
}).then(() => {
|
||||
this.$modal.msgSuccess("用户" + username + "解锁成功")
|
||||
}).catch(() => {})
|
||||
},
|
||||
/** 导出按钮操作 */
|
||||
handleExport() {
|
||||
this.download('monitor/logininfor/export', {
|
||||
...this.queryParams
|
||||
}, `logininfor_${new Date().getTime()}.xlsx`)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.monitor-logininfor {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: calc(100vh - 124px);
|
||||
}
|
||||
.table-wrapper {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
.monitor-logininfor ::v-deep .el-table ::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
.monitor-logininfor ::v-deep .el-table {
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
.monitor-logininfor ::v-deep .el-table__body-wrapper::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
.monitor-logininfor ::v-deep .el-table__body-wrapper {
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
<template>
|
||||
<div class="app-container monitor-online">
|
||||
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" label-width="68px">
|
||||
<el-form-item label="登录地址" prop="ipaddr">
|
||||
<el-input
|
||||
v-model="queryParams.ipaddr"
|
||||
placeholder="请输入登录地址"
|
||||
clearable
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="用户名称" prop="userName">
|
||||
<el-input
|
||||
v-model="queryParams.userName"
|
||||
placeholder="请输入用户名称"
|
||||
clearable
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
|
||||
</el-form>
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="list.slice((pageNum-1)*pageSize,pageNum*pageSize)"
|
||||
style="width: 100%;"
|
||||
:height="tableHeight"
|
||||
>
|
||||
<el-table-column label="序号" type="index" align="center">
|
||||
<template slot-scope="scope">
|
||||
<span>{{(pageNum - 1) * pageSize + scope.$index + 1}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="会话编号" align="center" prop="tokenId" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="登录名称" align="center" prop="userName" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="部门名称" align="center" prop="deptName" />
|
||||
<el-table-column label="主机" align="center" prop="ipaddr" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="登录地点" align="center" prop="loginLocation" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="浏览器" align="center" prop="browser" />
|
||||
<el-table-column label="操作系统" align="center" prop="os" />
|
||||
<el-table-column label="登录时间" align="center" prop="loginTime" width="180">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ parseTime(scope.row.loginTime) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
|
||||
<template slot-scope="scope">
|
||||
<el-button
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-delete"
|
||||
@click="handleForceLogout(scope.row)"
|
||||
v-hasPermi="['monitor:online:forceLogout']"
|
||||
>强退</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination v-show="total>0" :total="total" :page.sync="pageNum" :limit.sync="pageSize" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { list, forceLogout } from "@/api/monitor/online"
|
||||
|
||||
export default {
|
||||
name: "Online",
|
||||
data() {
|
||||
return {
|
||||
// 遮罩层
|
||||
loading: true,
|
||||
// 总条数
|
||||
total: 0,
|
||||
// 表格数据
|
||||
list: [],
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
// 表格高度
|
||||
tableHeight: window.innerHeight - 240,
|
||||
// 查询参数
|
||||
queryParams: {
|
||||
ipaddr: undefined,
|
||||
userName: undefined
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getList()
|
||||
},
|
||||
mounted() {
|
||||
this.getTableHeight()
|
||||
window.addEventListener('resize', this.getTableHeight)
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.removeEventListener('resize', this.getTableHeight)
|
||||
},
|
||||
methods: {
|
||||
getTableHeight() {
|
||||
this.tableHeight = window.innerHeight - 240
|
||||
},
|
||||
/** 查询登录日志列表 */
|
||||
getList() {
|
||||
this.loading = true
|
||||
list(this.queryParams).then(response => {
|
||||
this.list = response.rows
|
||||
this.total = response.total
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
/** 搜索按钮操作 */
|
||||
handleQuery() {
|
||||
this.pageNum = 1
|
||||
this.getList()
|
||||
},
|
||||
/** 重置按钮操作 */
|
||||
resetQuery() {
|
||||
this.resetForm("queryForm")
|
||||
this.handleQuery()
|
||||
},
|
||||
/** 强退按钮操作 */
|
||||
handleForceLogout(row) {
|
||||
this.$modal.confirm('是否确认强退名称为"' + row.userName + '"的用户?').then(function() {
|
||||
return forceLogout(row.tokenId)
|
||||
}).then(() => {
|
||||
this.getList()
|
||||
this.$modal.msgSuccess("强退成功")
|
||||
}).catch(() => {})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.monitor-online ::v-deep .el-table ::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
.monitor-online ::v-deep .el-table {
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
.monitor-online ::v-deep .el-table__body-wrapper::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
.monitor-online ::v-deep .el-table__body-wrapper {
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
<template>
|
||||
<el-dialog title="操作日志详细" :visible.sync="visible" width="780px" append-to-body @close="$emit('update:visible', false)">
|
||||
<div class="detail-wrap">
|
||||
<!-- 基本信息 -->
|
||||
<div class="detail-card">
|
||||
<div class="detail-card-title"><i class="el-icon-info"></i> 基本信息</div>
|
||||
<el-row class="detail-row">
|
||||
<el-col :span="12">
|
||||
<div class="detail-item"><span class="detail-label">操作模块</span><span class="detail-value">{{ form.title }}</span></div>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<div class="detail-item"><span class="detail-label">业务类型</span><span class="detail-value">{{ typeLabel }}</span></div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row class="detail-row">
|
||||
<el-col :span="12">
|
||||
<div class="detail-item"><span class="detail-label">操作时间</span><span class="detail-value">{{ form.operTime }}</span></div>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">执行状态</span>
|
||||
<el-tag v-if="form.status === 0" type="success" size="small"><i class="el-icon-check"></i> 正常</el-tag>
|
||||
<el-tag v-else type="danger" size="small"><i class="el-icon-close"></i> 异常</el-tag>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
<!-- 操作人员 -->
|
||||
<div class="detail-card">
|
||||
<div class="detail-card-title"><i class="el-icon-user"></i> 操作人员</div>
|
||||
<el-row class="detail-row">
|
||||
<el-col :span="12">
|
||||
<div class="detail-item"><span class="detail-label">操作人员</span><span class="detail-value">{{ form.operName }}</span></div>
|
||||
</el-col>
|
||||
<el-col :span="12" v-if="form.deptName">
|
||||
<div class="detail-item"><span class="detail-label">所属部门</span><span class="detail-value">{{ form.deptName }}</span></div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row class="detail-row">
|
||||
<el-col :span="24">
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">操作地址</span>
|
||||
<span class="detail-value">{{ form.operIp }} <span class="detail-location">{{ form.operLocation }}</span></span>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
<!-- 请求信息 -->
|
||||
<div class="detail-card">
|
||||
<div class="detail-card-title"><i class="el-icon-sort"></i> 请求信息</div>
|
||||
<el-row class="detail-row">
|
||||
<el-col :span="24">
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">请求地址</span>
|
||||
<span class="detail-value">
|
||||
<span :class="'method-tag method-' + form.requestMethod">{{ form.requestMethod }}</span>
|
||||
{{ form.operUrl }}
|
||||
</span>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row class="detail-row">
|
||||
<el-col :span="24">
|
||||
<div class="detail-item"><span class="detail-label">操作方法</span><span class="detail-value mono">{{ form.method }}</span></div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row class="detail-row">
|
||||
<el-col :span="12">
|
||||
<div class="detail-item"><span class="detail-label">消耗时间</span><span class="detail-value">{{ form.costTime }} 毫秒</span></div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
<!-- 请求参数 -->
|
||||
<div class="detail-card">
|
||||
<div class="detail-card-title"><i class="el-icon-upload2"></i> 请求参数</div>
|
||||
<div class="code-body">
|
||||
<div class="code-wrap">
|
||||
<div class="code-action">
|
||||
<el-button size="mini" icon="el-icon-copy-document" @click="copyText(form.operParam)">复制</el-button>
|
||||
</div>
|
||||
<pre class="code-pre">{{ formatJson(form.operParam) }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 返回参数 -->
|
||||
<div class="detail-card">
|
||||
<div class="detail-card-title"><i class="el-icon-download"></i> 返回参数</div>
|
||||
<div class="code-body">
|
||||
<div class="code-wrap">
|
||||
<div class="code-action">
|
||||
<el-button size="mini" icon="el-icon-copy-document" @click="copyText(form.jsonResult)">复制</el-button>
|
||||
</div>
|
||||
<pre class="code-pre">{{ formatJson(form.jsonResult) }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 异常信息 -->
|
||||
<div class="detail-card" v-if="form.status !== 0">
|
||||
<div class="detail-card-title error-title"><i class="el-icon-warning"></i> 异常信息</div>
|
||||
<div class="error-body">
|
||||
<div class="error-msg">{{ form.errorMsg }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'OperlogDetail',
|
||||
dicts: ['sys_oper_type'],
|
||||
props: {
|
||||
visible: { type: Boolean, default: false },
|
||||
row: { type: Object, default: () => ({}) }
|
||||
},
|
||||
computed: {
|
||||
form() { return this.row || {} },
|
||||
typeLabel() { return this.selectDictLabel(this.dict.type.sys_oper_type, this.form.businessType) || '-' }
|
||||
},
|
||||
methods: {
|
||||
formatJson(str) {
|
||||
if (!str) return '(无数据)'
|
||||
try { return JSON.stringify(JSON.parse(str), null, 2) } catch { return str }
|
||||
},
|
||||
copyText(str) {
|
||||
const text = this.formatJson(str)
|
||||
if (navigator.clipboard) {
|
||||
navigator.clipboard.writeText(text).then(() => this.$message({ message: '已复制', type: 'success', duration: 1500 }))
|
||||
} else {
|
||||
const ta = document.createElement('textarea')
|
||||
ta.value = text
|
||||
document.body.appendChild(ta)
|
||||
ta.select()
|
||||
document.execCommand('copy')
|
||||
document.body.removeChild(ta)
|
||||
this.$message({ message: '已复制', type: 'success', duration: 1500 })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,306 @@
|
||||
<template>
|
||||
<div class="app-container monitor-operlog">
|
||||
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
|
||||
<el-form-item label="操作地址" prop="operIp">
|
||||
<el-input
|
||||
v-model="queryParams.operIp"
|
||||
placeholder="请输入操作地址"
|
||||
clearable
|
||||
style="width: 240px;"
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="系统模块" prop="title">
|
||||
<el-input
|
||||
v-model="queryParams.title"
|
||||
placeholder="请输入系统模块"
|
||||
clearable
|
||||
style="width: 240px;"
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="操作人员" prop="operName">
|
||||
<el-input
|
||||
v-model="queryParams.operName"
|
||||
placeholder="请输入操作人员"
|
||||
clearable
|
||||
style="width: 240px;"
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="类型" prop="businessType">
|
||||
<el-select
|
||||
v-model="queryParams.businessType"
|
||||
placeholder="操作类型"
|
||||
clearable
|
||||
style="width: 240px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in dict.type.sys_oper_type"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select
|
||||
v-model="queryParams.status"
|
||||
placeholder="操作状态"
|
||||
clearable
|
||||
style="width: 240px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in dict.type.sys_common_status"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="操作时间">
|
||||
<el-date-picker
|
||||
v-model="dateRange"
|
||||
style="width: 240px"
|
||||
value-format="yyyy-MM-dd HH:mm:ss"
|
||||
type="daterange"
|
||||
range-separator="-"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
:default-time="['00:00:00', '23:59:59']"
|
||||
></el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="danger"
|
||||
plain
|
||||
icon="el-icon-delete"
|
||||
size="mini"
|
||||
:disabled="multiple"
|
||||
@click="handleDelete"
|
||||
v-hasPermi="['monitor:operlog:remove']"
|
||||
>删除</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="danger"
|
||||
plain
|
||||
icon="el-icon-delete"
|
||||
size="mini"
|
||||
@click="handleClean"
|
||||
v-hasPermi="['monitor:operlog:remove']"
|
||||
>清空</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="warning"
|
||||
plain
|
||||
icon="el-icon-download"
|
||||
size="mini"
|
||||
@click="handleExport"
|
||||
v-hasPermi="['monitor:operlog:export']"
|
||||
>导出</el-button>
|
||||
</el-col>
|
||||
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
|
||||
<div class="table-wrapper">
|
||||
<el-table ref="tables" v-loading="loading" :data="list" @selection-change="handleSelectionChange" :default-sort="defaultSort" @sort-change="handleSortChange" height="100%">
|
||||
<el-table-column type="selection" width="50" align="center" />
|
||||
<el-table-column label="日志编号" align="center" prop="operId" />
|
||||
<el-table-column label="系统模块" align="center" prop="title" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="操作类型" align="center" prop="businessType">
|
||||
<template slot-scope="scope">
|
||||
<dict-tag :options="dict.type.sys_oper_type" :value="scope.row.businessType"/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作人员" align="center" prop="operName" width="110" :show-overflow-tooltip="true" sortable="custom" :sort-orders="['descending', 'ascending']" />
|
||||
<el-table-column label="操作地址" align="center" prop="operIp" width="130" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="操作地点" align="center" prop="operLocation" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="操作状态" align="center" prop="status">
|
||||
<template slot-scope="scope">
|
||||
<dict-tag :options="dict.type.sys_common_status" :value="scope.row.status"/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作日期" align="center" prop="operTime" width="160" sortable="custom" :sort-orders="['descending', 'ascending']">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ parseTime(scope.row.operTime) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="消耗时间" align="center" prop="costTime" width="110" :show-overflow-tooltip="true" sortable="custom" :sort-orders="['descending', 'ascending']">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ scope.row.costTime }}毫秒</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
|
||||
<template slot-scope="scope">
|
||||
<el-button
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-view"
|
||||
@click="handleDetail(scope.row,scope.index)"
|
||||
v-hasPermi="['monitor:operlog:query']"
|
||||
>详细</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<pagination
|
||||
v-show="total>0"
|
||||
:total="total"
|
||||
:page.sync="queryParams.pageNum"
|
||||
:limit.sync="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
|
||||
<operlog-detail :visible.sync="detailVisible" :row="detailRow" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import OperlogDetail from './detail'
|
||||
import { list, delOperlog, cleanOperlog } from "@/api/monitor/operlog"
|
||||
|
||||
export default {
|
||||
name: "Operlog",
|
||||
components: { OperlogDetail },
|
||||
dicts: ['sys_oper_type', 'sys_common_status'],
|
||||
data() {
|
||||
return {
|
||||
// 遮罩层
|
||||
loading: true,
|
||||
// 选中数组
|
||||
ids: [],
|
||||
// 非多个禁用
|
||||
multiple: true,
|
||||
// 显示搜索条件
|
||||
showSearch: true,
|
||||
// 总条数
|
||||
total: 0,
|
||||
// 表格数据
|
||||
list: [],
|
||||
// 是否显示弹出层
|
||||
detailVisible: false,
|
||||
detailRow: {},
|
||||
// 日期范围
|
||||
dateRange: [],
|
||||
// 默认排序
|
||||
defaultSort: { prop: "operTime", order: "descending" },
|
||||
// 表单参数
|
||||
form: {},
|
||||
// 查询参数
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
operIp: undefined,
|
||||
title: undefined,
|
||||
operName: undefined,
|
||||
businessType: undefined,
|
||||
status: undefined
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getList()
|
||||
},
|
||||
methods: {
|
||||
/** 查询登录日志 */
|
||||
getList() {
|
||||
this.loading = true
|
||||
list(this.addDateRange(this.queryParams, this.dateRange)).then( response => {
|
||||
this.list = response.rows
|
||||
this.total = response.total
|
||||
this.loading = false
|
||||
}
|
||||
)
|
||||
},
|
||||
// 详细按钮操作
|
||||
handleDetail(row) {
|
||||
this.detailRow = row
|
||||
this.detailVisible = true
|
||||
},
|
||||
/** 搜索按钮操作 */
|
||||
handleQuery() {
|
||||
this.queryParams.pageNum = 1
|
||||
this.getList()
|
||||
},
|
||||
/** 重置按钮操作 */
|
||||
resetQuery() {
|
||||
this.dateRange = []
|
||||
this.resetForm("queryForm")
|
||||
this.queryParams.pageNum = 1
|
||||
this.$refs.tables.sort(this.defaultSort.prop, this.defaultSort.order)
|
||||
},
|
||||
/** 多选框选中数据 */
|
||||
handleSelectionChange(selection) {
|
||||
this.ids = selection.map(item => item.operId)
|
||||
this.multiple = !selection.length
|
||||
},
|
||||
/** 排序触发事件 */
|
||||
handleSortChange(column, prop, order) {
|
||||
this.queryParams.orderByColumn = column.prop
|
||||
this.queryParams.isAsc = column.order
|
||||
this.getList()
|
||||
},
|
||||
/** 删除按钮操作 */
|
||||
handleDelete(row) {
|
||||
const operIds = row.operId || this.ids
|
||||
this.$modal.confirm('是否确认删除日志编号为"' + operIds + '"的数据项?').then(function() {
|
||||
return delOperlog(operIds)
|
||||
}).then(() => {
|
||||
this.getList()
|
||||
this.$modal.msgSuccess("删除成功")
|
||||
}).catch(() => {})
|
||||
},
|
||||
/** 清空按钮操作 */
|
||||
handleClean() {
|
||||
this.$modal.confirm('是否确认清空所有操作日志数据项?').then(function() {
|
||||
return cleanOperlog()
|
||||
}).then(() => {
|
||||
this.getList()
|
||||
this.$modal.msgSuccess("清空成功")
|
||||
}).catch(() => {})
|
||||
},
|
||||
/** 导出按钮操作 */
|
||||
handleExport() {
|
||||
this.download('monitor/operlog/export', {
|
||||
...this.queryParams
|
||||
}, `operlog_${new Date().getTime()}.xlsx`)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.monitor-operlog {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: calc(100vh - 124px);
|
||||
}
|
||||
.table-wrapper {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
.monitor-operlog ::v-deep .el-table ::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
.monitor-operlog ::v-deep .el-table {
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
.monitor-operlog ::v-deep .el-table__body-wrapper::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
.monitor-operlog ::v-deep .el-table__body-wrapper {
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,207 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="12" class="card-box">
|
||||
<el-card>
|
||||
<div slot="header"><span><i class="el-icon-cpu"></i> CPU</span></div>
|
||||
<div class="el-table el-table--enable-row-hover el-table--medium">
|
||||
<table cellspacing="0" style="width: 100%;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="el-table__cell is-leaf"><div class="cell">属性</div></th>
|
||||
<th class="el-table__cell is-leaf"><div class="cell">值</div></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">核心数</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="server.cpu">{{ server.cpu.cpuNum }}</div></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">用户使用率</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="server.cpu">{{ server.cpu.used }}%</div></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">系统使用率</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="server.cpu">{{ server.cpu.sys }}%</div></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">当前空闲率</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="server.cpu">{{ server.cpu.free }}%</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12" class="card-box">
|
||||
<el-card>
|
||||
<div slot="header"><span><i class="el-icon-tickets"></i> 内存</span></div>
|
||||
<div class="el-table el-table--enable-row-hover el-table--medium">
|
||||
<table cellspacing="0" style="width: 100%;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="el-table__cell is-leaf"><div class="cell">属性</div></th>
|
||||
<th class="el-table__cell is-leaf"><div class="cell">内存</div></th>
|
||||
<th class="el-table__cell is-leaf"><div class="cell">JVM</div></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">总内存</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="server.mem">{{ server.mem.total }}G</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="server.jvm">{{ server.jvm.total }}M</div></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">已用内存</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="server.mem">{{ server.mem.used}}G</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="server.jvm">{{ server.jvm.used}}M</div></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">剩余内存</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="server.mem">{{ server.mem.free }}G</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="server.jvm">{{ server.jvm.free }}M</div></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">使用率</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="server.mem" :class="{'text-danger': server.mem.usage > 80}">{{ server.mem.usage }}%</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="server.jvm" :class="{'text-danger': server.jvm.usage > 80}">{{ server.jvm.usage }}%</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="24" class="card-box">
|
||||
<el-card>
|
||||
<div slot="header">
|
||||
<span><i class="el-icon-monitor"></i> 服务器信息</span>
|
||||
</div>
|
||||
<div class="el-table el-table--enable-row-hover el-table--medium">
|
||||
<table cellspacing="0" style="width: 100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">服务器名称</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="server.sys">{{ server.sys.computerName }}</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">操作系统</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="server.sys">{{ server.sys.osName }}</div></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">服务器IP</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="server.sys">{{ server.sys.computerIp }}</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">系统架构</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="server.sys">{{ server.sys.osArch }}</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="24" class="card-box">
|
||||
<el-card>
|
||||
<div slot="header">
|
||||
<span><i class="el-icon-coffee-cup"></i> Java虚拟机信息</span>
|
||||
</div>
|
||||
<div class="el-table el-table--enable-row-hover el-table--medium">
|
||||
<table cellspacing="0" style="width: 100%;table-layout:fixed;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">Java名称</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="server.jvm">{{ server.jvm.name }}</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">Java版本</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="server.jvm">{{ server.jvm.version }}</div></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">启动时间</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="server.jvm">{{ server.jvm.startTime }}</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">运行时长</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="server.jvm">{{ server.jvm.runTime }}</div></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="1" class="el-table__cell is-leaf"><div class="cell">安装路径</div></td>
|
||||
<td colspan="3" class="el-table__cell is-leaf"><div class="cell" v-if="server.jvm">{{ server.jvm.home }}</div></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="1" class="el-table__cell is-leaf"><div class="cell">项目路径</div></td>
|
||||
<td colspan="3" class="el-table__cell is-leaf"><div class="cell" v-if="server.sys">{{ server.sys.userDir }}</div></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="1" class="el-table__cell is-leaf"><div class="cell">运行参数</div></td>
|
||||
<td colspan="3" class="el-table__cell is-leaf"><div class="cell" v-if="server.jvm">{{ server.jvm.inputArgs }}</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="24" class="card-box">
|
||||
<el-card>
|
||||
<div slot="header">
|
||||
<span><i class="el-icon-receiving"></i> 磁盘状态</span>
|
||||
</div>
|
||||
<div class="el-table el-table--enable-row-hover el-table--medium">
|
||||
<table cellspacing="0" style="width: 100%;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="el-table__cell el-table__cell is-leaf"><div class="cell">盘符路径</div></th>
|
||||
<th class="el-table__cell is-leaf"><div class="cell">文件系统</div></th>
|
||||
<th class="el-table__cell is-leaf"><div class="cell">盘符类型</div></th>
|
||||
<th class="el-table__cell is-leaf"><div class="cell">总大小</div></th>
|
||||
<th class="el-table__cell is-leaf"><div class="cell">可用大小</div></th>
|
||||
<th class="el-table__cell is-leaf"><div class="cell">已用大小</div></th>
|
||||
<th class="el-table__cell is-leaf"><div class="cell">已用百分比</div></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody v-if="server.sysFiles">
|
||||
<tr v-for="(sysFile, index) in server.sysFiles" :key="index">
|
||||
<td class="el-table__cell is-leaf"><div class="cell">{{ sysFile.dirName }}</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">{{ sysFile.sysTypeName }}</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">{{ sysFile.typeName }}</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">{{ sysFile.total }}</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">{{ sysFile.free }}</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">{{ sysFile.used }}</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" :class="{'text-danger': sysFile.usage > 80}">{{ sysFile.usage }}%</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getServer } from "@/api/monitor/server"
|
||||
|
||||
export default {
|
||||
name: "Server",
|
||||
data() {
|
||||
return {
|
||||
// 服务器信息
|
||||
server: []
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getList()
|
||||
this.openLoading()
|
||||
},
|
||||
methods: {
|
||||
/** 查询服务器信息 */
|
||||
getList() {
|
||||
getServer().then(response => {
|
||||
this.server = response.data
|
||||
this.$modal.closeLoading()
|
||||
})
|
||||
},
|
||||
// 打开加载层
|
||||
openLoading() {
|
||||
this.$modal.loading("正在加载服务监控数据,请稍候!")
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,12 @@
|
||||
<script>
|
||||
export default {
|
||||
created() {
|
||||
const { params, query } = this.$route
|
||||
const { path } = params
|
||||
this.$router.replace({ path: '/' + path, query })
|
||||
},
|
||||
render: function(h) {
|
||||
return h() // avoid warning message
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,167 @@
|
||||
<template>
|
||||
<div class="register">
|
||||
<el-form ref="registerForm" :model="registerForm" :rules="registerRules" class="register-form">
|
||||
<h3 class="title">{{title}}</h3>
|
||||
<el-form-item prop="username">
|
||||
<el-input v-model="registerForm.username" type="text" auto-complete="off" placeholder="账号">
|
||||
<svg-icon slot="prefix" icon-class="user" class="el-input__icon input-icon" />
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="password" :rules="registerPwdValidator">
|
||||
<el-input
|
||||
v-model="registerForm.password"
|
||||
type="password"
|
||||
auto-complete="off"
|
||||
placeholder="密码"
|
||||
@keyup.enter.native="handleRegister"
|
||||
>
|
||||
<svg-icon slot="prefix" icon-class="password" class="el-input__icon input-icon" />
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="confirmPassword">
|
||||
<el-input
|
||||
v-model="registerForm.confirmPassword"
|
||||
type="password"
|
||||
auto-complete="off"
|
||||
placeholder="确认密码"
|
||||
@keyup.enter.native="handleRegister"
|
||||
>
|
||||
<svg-icon slot="prefix" icon-class="password" class="el-input__icon input-icon" />
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item style="width:100%;">
|
||||
<el-button
|
||||
:loading="loading"
|
||||
size="medium"
|
||||
type="primary"
|
||||
style="width:100%;"
|
||||
@click.native.prevent="handleRegister"
|
||||
>
|
||||
<span v-if="!loading">注 册</span>
|
||||
<span v-else>注 册 中...</span>
|
||||
</el-button>
|
||||
<div style="float: right;">
|
||||
<router-link class="link-type" :to="'/login'">使用已有账户登录</router-link>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<!-- 底部 -->
|
||||
<div class="el-register-footer">
|
||||
<span>{{ footerContent }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { register } from "@/api/login"
|
||||
import passwordRule from "@/utils/passwordRule"
|
||||
|
||||
export default {
|
||||
mixins: [passwordRule],
|
||||
data() {
|
||||
return {
|
||||
title: process.env.VUE_APP_TITLE,
|
||||
footerContent: "Copyright © 2018-2026 roomroot. All Rights Reserved.",
|
||||
registerForm: {
|
||||
username: "",
|
||||
password: "",
|
||||
confirmPassword: ""
|
||||
},
|
||||
loading: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
registerRules() {
|
||||
return {
|
||||
username: [
|
||||
{ required: true, trigger: "blur", message: "请输入您的账号" },
|
||||
{ min: 2, max: 20, message: '用户账号长度必须介于 2 和 20 之间', trigger: 'blur' }
|
||||
],
|
||||
confirmPassword: [
|
||||
{ required: true, message: "请再次输入您的密码", trigger: "blur" },
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (this.registerForm.password !== value) {
|
||||
callback(new Error("两次输入的密码不一致"))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}, trigger: "blur"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleRegister() {
|
||||
this.$refs.registerForm.validate(valid => {
|
||||
if (valid) {
|
||||
this.loading = true
|
||||
register(this.registerForm).then(() => {
|
||||
const username = this.registerForm.username
|
||||
this.$alert("<font color='red'>恭喜你,您的账号 " + username + " 注册成功!</font>", '系统提示', {
|
||||
dangerouslyUseHTMLString: true,
|
||||
type: 'success'
|
||||
}).then(() => {
|
||||
this.$router.push("/login")
|
||||
}).catch(() => {})
|
||||
}).catch(() => {
|
||||
this.loading = false
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style rel="stylesheet/scss" lang="scss" scoped>
|
||||
.register {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
background-image: url("../assets/images/login-background.jpg");
|
||||
background-size: cover;
|
||||
}
|
||||
.title {
|
||||
margin: 0px auto 30px auto;
|
||||
text-align: center;
|
||||
color: #707070;
|
||||
}
|
||||
|
||||
.register-form {
|
||||
border-radius: 6px;
|
||||
background: #ffffff;
|
||||
width: 400px;
|
||||
padding: 25px 25px 5px 25px;
|
||||
.el-input {
|
||||
height: 38px;
|
||||
input {
|
||||
height: 38px;
|
||||
}
|
||||
}
|
||||
.input-icon {
|
||||
height: 39px;
|
||||
width: 14px;
|
||||
margin-left: 2px;
|
||||
}
|
||||
}
|
||||
.register-tip {
|
||||
font-size: 13px;
|
||||
text-align: center;
|
||||
color: #bfbfbf;
|
||||
}
|
||||
.el-register-footer {
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
font-family: Arial;
|
||||
font-size: 12px;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,399 @@
|
||||
<template>
|
||||
<div class="app-container plan-import-page">
|
||||
<!-- ==================== 1. 查询条件 ==================== -->
|
||||
<el-card shadow="never" class="search-card">
|
||||
<el-form :model="searchForm" label-width="100px" class="search-form">
|
||||
<!-- 第一行:课程时间独占整行 -->
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="课程时间">
|
||||
<div class="date-range">
|
||||
<span>从</span>
|
||||
<el-date-picker
|
||||
v-model="searchForm.kssj"
|
||||
type="datetime"
|
||||
value-format="yyyy-MM-dd HH:mm:ss"
|
||||
placeholder="开始时间"
|
||||
clearable
|
||||
class="date-input"
|
||||
/>
|
||||
<span>到</span>
|
||||
<el-date-picker
|
||||
v-model="searchForm.jssj"
|
||||
type="datetime"
|
||||
value-format="yyyy-MM-dd HH:mm:ss"
|
||||
placeholder="结束时间"
|
||||
clearable
|
||||
class="date-input"
|
||||
/>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 下面分左右两栏 -->
|
||||
<el-row :gutter="24">
|
||||
<el-col :xs="24" :md="12">
|
||||
<el-form-item label="检查状态">
|
||||
<div class="checkbox-group">
|
||||
<el-checkbox v-model="searchForm.jcztYtg">已通过</el-checkbox>
|
||||
<el-checkbox v-model="searchForm.jcztWtg">未通过</el-checkbox>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="教员姓名">
|
||||
<el-input v-model="searchForm.jyxm" placeholder="请输入教员姓名" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="教学场地名称">
|
||||
<el-input v-model="searchForm.jxcdmc" placeholder="请输入教学场地名称" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :md="12">
|
||||
<el-form-item label="忽略状态">
|
||||
<div class="checkbox-group">
|
||||
<el-checkbox v-model="searchForm.hlztYc">有用</el-checkbox>
|
||||
<el-checkbox v-model="searchForm.hlztYbl">已被忽略</el-checkbox>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="班次名称">
|
||||
<el-input v-model="searchForm.bcmc" placeholder="请输入班次名称" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="科目名称">
|
||||
<el-input v-model="searchForm.kmmc" placeholder="请输入科目名称" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row>
|
||||
<el-col :span="24" class="search-actions">
|
||||
<el-button type="primary" @click="handleSearch">查询</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<!-- ==================== 2. 操作按钮区 ==================== -->
|
||||
<el-card shadow="never" class="action-card">
|
||||
<div class="action-bar">
|
||||
<div class="bar-left">
|
||||
<el-button icon="el-icon-download" @click="handleDownloadTemplate">
|
||||
【课程表数据文件模板】下载
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<div class="bar-center">
|
||||
<div class="file-area">
|
||||
<input ref="fileInputRef" type="file" accept=".xlsx,.xls" style="display: none" @change="handleFileChange" />
|
||||
<el-button icon="el-icon-folder-opened" @click="handleChooseFile">选择文件</el-button>
|
||||
<span class="file-name">{{ selectedFileName }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bar-right">
|
||||
<el-button type="primary" @click="handleImportToSystem" :loading="importing">缓存数据导入到系统</el-button>
|
||||
<el-button type="primary" icon="el-icon-upload2" @click="handleUploadToCache">上传数据至缓存</el-button>
|
||||
<el-button @click="handleCheckCache">检查缓存数据</el-button>
|
||||
<el-button type="danger" plain @click="handleDeleteCache">删除缓存数据</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- ==================== 3. 数据表格 ==================== -->
|
||||
<el-card shadow="never" class="table-card">
|
||||
<el-table v-loading="loading" :data="tableData" stripe border highlight-current-row class="import-table">
|
||||
<el-table-column prop="nd" label="年度" width="60" align="center" />
|
||||
<el-table-column prop="xh" label="序号" width="53" align="center" />
|
||||
<el-table-column prop="yxh" label="原序号" width="67" align="center" />
|
||||
<el-table-column prop="rq" label="日期" width="120" align="center" :formatter="formatDate" />
|
||||
<el-table-column prop="yjc" label="原节次" width="67" align="center" />
|
||||
<el-table-column prop="jc" label="节次" width="53" align="center" />
|
||||
<el-table-column prop="kcmc" label="课程名称" width="85" show-overflow-tooltip />
|
||||
<el-table-column prop="bc" label="班次" width="53" show-overflow-tooltip />
|
||||
<el-table-column prop="zrdw" label="责任单位" width="81" show-overflow-tooltip />
|
||||
<el-table-column prop="skjy" label="授课教员" width="81" align="center" />
|
||||
<el-table-column prop="jxcd" label="教学场地" width="81" align="center" />
|
||||
<el-table-column prop="jxnr" label="教学内容" width="81" show-overflow-tooltip />
|
||||
<el-table-column prop="jxff" label="教学方法" width="81" align="center" />
|
||||
<el-table-column prop="jybz" label="教员备注" width="81" show-overflow-tooltip />
|
||||
<el-table-column prop="jwbz" label="教务备注" width="81" show-overflow-tooltip />
|
||||
<el-table-column prop="jcjg" label="检查结果" width="81" align="center" />
|
||||
<el-table-column prop="ytgjc" label="已通过检查" width="95" align="center">
|
||||
<template slot-scope="{ row }">
|
||||
<span>{{ row.ytgjc ? '是' : '否' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="ybhl" label="已被忽略" width="81" align="center">
|
||||
<template slot-scope="{ row }">
|
||||
<span>{{ row.ybhl ? '是' : '否' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="ybcl" label="已被处理" width="81" align="center">
|
||||
<template slot-scope="{ row }">
|
||||
<span>{{ row.ybcl ? '是' : '否' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="ybdr" label="已被导入" align="center">
|
||||
<template slot-scope="{ row }">
|
||||
<span>{{ row.ybdr ? '是' : '否' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-pagination
|
||||
:current-page="pageNum"
|
||||
:page-size="pageSize"
|
||||
:total="total"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
class="pagination-wrapper"
|
||||
@current-change="handlePageChange"
|
||||
@size-change="handleSizeChange"
|
||||
/>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listTeachingPlan, importTeachingPlan, downloadTeachingPlanTemplate } from '@/api/schedule/teachingPlan'
|
||||
|
||||
export default {
|
||||
name: 'PlanImportIndex',
|
||||
data() {
|
||||
return {
|
||||
// ==================== 查询表单 ====================
|
||||
searchForm: {
|
||||
kssj: '',
|
||||
jssj: '',
|
||||
jcztYtg: false,
|
||||
jcztWtg: true,
|
||||
hlztYc: false,
|
||||
hlztYbl: true,
|
||||
jyxm: '',
|
||||
bcmc: '',
|
||||
jxcdmc: '',
|
||||
kmmc: ''
|
||||
},
|
||||
|
||||
// ==================== 文件上传与导入 ====================
|
||||
selectedFileName: '未选择任何文件',
|
||||
selectedFile: null,
|
||||
importing: false,
|
||||
|
||||
// ==================== 表格数据 ====================
|
||||
tableData: [],
|
||||
loading: false,
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
total: 0
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.fetchList()
|
||||
},
|
||||
methods: {
|
||||
// ==================== 查询 ====================
|
||||
// 仅传后端 Mapper 支持的字段:skjy(授课教员) bc(班次) kcmc(课程名称) ytgjc(已通过检查)
|
||||
// 课程时间、忽略状态、教学场地名称后端列表查询不支持,不传
|
||||
buildSearchParams() {
|
||||
const f = this.searchForm
|
||||
const params = { pageNum: this.pageNum, pageSize: this.pageSize }
|
||||
if (f.jyxm && f.jyxm.trim()) params.skjy = f.jyxm.trim()
|
||||
if (f.bcmc && f.bcmc.trim()) params.bc = f.bcmc.trim()
|
||||
if (f.kmmc && f.kmmc.trim()) params.kcmc = f.kmmc.trim()
|
||||
// 检查状态:仅勾选「已通过」或「未通过」其中之一时才按该条件过滤
|
||||
if (f.jcztYtg && !f.jcztWtg) params.ytgjc = true
|
||||
else if (!f.jcztYtg && f.jcztWtg) params.ytgjc = false
|
||||
return params
|
||||
},
|
||||
|
||||
fetchList() {
|
||||
this.loading = true
|
||||
listTeachingPlan(this.buildSearchParams()).then(response => {
|
||||
const data = response.data || {}
|
||||
this.tableData = data.records || []
|
||||
this.total = data.total || 0
|
||||
this.loading = false
|
||||
}).catch(() => {
|
||||
this.tableData = []
|
||||
this.total = 0
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
|
||||
handleSearch() {
|
||||
this.pageNum = 1
|
||||
this.fetchList()
|
||||
},
|
||||
|
||||
handlePageChange(current) {
|
||||
this.pageNum = current
|
||||
this.fetchList()
|
||||
},
|
||||
|
||||
handleSizeChange(size) {
|
||||
this.pageSize = size
|
||||
this.pageNum = 1
|
||||
this.fetchList()
|
||||
},
|
||||
|
||||
/** 日期格式化 */
|
||||
formatDate(row, column, cellValue) {
|
||||
return cellValue ? String(cellValue).substring(0, 10) : ''
|
||||
},
|
||||
|
||||
// ==================== 文件选择 ====================
|
||||
handleChooseFile() {
|
||||
this.$refs.fileInputRef && this.$refs.fileInputRef.click()
|
||||
},
|
||||
|
||||
handleFileChange(event) {
|
||||
const target = event.target
|
||||
const file = target.files && target.files[0]
|
||||
if (file) {
|
||||
this.selectedFile = file
|
||||
this.selectedFileName = file.name
|
||||
} else {
|
||||
this.selectedFile = null
|
||||
this.selectedFileName = '未选择任何文件'
|
||||
}
|
||||
},
|
||||
|
||||
// ==================== 下载模板 ====================
|
||||
handleDownloadTemplate() {
|
||||
downloadTeachingPlanTemplate().then(blob => {
|
||||
this.downloadBlob(blob, '教学实施计划导入模板.xlsx')
|
||||
this.$message.success('模板下载成功')
|
||||
}).catch(() => {})
|
||||
},
|
||||
|
||||
downloadBlob(blob, fileName) {
|
||||
const url = window.URL.createObjectURL(blob)
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.download = fileName
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
window.URL.revokeObjectURL(url)
|
||||
},
|
||||
|
||||
// ==================== 导入到系统 ====================
|
||||
handleImportToSystem() {
|
||||
if (!this.selectedFile) {
|
||||
this.$message.warning('请先选择文件')
|
||||
return
|
||||
}
|
||||
this.importing = true
|
||||
importTeachingPlan(this.selectedFile).then(res => {
|
||||
const count = res && res.data
|
||||
if (count !== null && count !== undefined) {
|
||||
this.$message.success(`导入成功,共 ${count} 条记录`)
|
||||
} else {
|
||||
this.$message.success((res && res.msg) || '导入成功')
|
||||
}
|
||||
this.selectedFile = null
|
||||
this.selectedFileName = '未选择任何文件'
|
||||
this.$refs.fileInputRef && (this.$refs.fileInputRef.value = '')
|
||||
this.pageNum = 1
|
||||
this.fetchList()
|
||||
}).catch(() => {}).finally(() => {
|
||||
this.importing = false
|
||||
})
|
||||
},
|
||||
|
||||
// ==================== 缓存操作(后端暂未提供) ====================
|
||||
handleUploadToCache() {
|
||||
this.$message.info('后端暂未提供该接口')
|
||||
},
|
||||
|
||||
handleCheckCache() {
|
||||
this.$message.info('后端暂未提供该接口')
|
||||
},
|
||||
|
||||
handleDeleteCache() {
|
||||
this.$message.info('后端暂未提供该接口')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.plan-import-page {
|
||||
.search-card {
|
||||
margin-bottom: 16px;
|
||||
|
||||
.search-form {
|
||||
.date-range {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
.date-input {
|
||||
width: 180px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.checkbox-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.form-tip {
|
||||
color: #f56c6c;
|
||||
font-size: 12px;
|
||||
margin-bottom: 12px;
|
||||
padding-left: 100px;
|
||||
}
|
||||
|
||||
.search-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding-top: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.action-card {
|
||||
margin-bottom: 16px;
|
||||
|
||||
.action-bar {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
|
||||
.bar-left,
|
||||
.bar-center,
|
||||
.bar-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.bar-right {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.file-area {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
.file-name {
|
||||
color: #606266;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.table-card {
|
||||
.import-table {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,249 @@
|
||||
<template>
|
||||
<div class="app-container score-page">
|
||||
<!-- ==================== 1. 查询条件区域 ==================== -->
|
||||
<el-card shadow="never" class="search-card">
|
||||
<el-form :model="queryForm" label-width="90px" inline class="search-form">
|
||||
<el-form-item label="学员编号">
|
||||
<el-input v-model="queryForm.xybh" placeholder="请输入学员编号" clearable style="width: 200px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="年度">
|
||||
<el-input v-model="queryForm.nd" placeholder="如 2026" clearable style="width: 140px" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleSearch">查询</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<!-- ==================== 2. 成绩列表区域 ==================== -->
|
||||
<div class="toolbar">
|
||||
<el-button type="primary" :loading="exportLoading" @click="handleExportGrades">导出课程成绩</el-button>
|
||||
<el-button type="primary" :loading="exportLoading" @click="handleExportProcessGrades">导出课程过程成绩</el-button>
|
||||
</div>
|
||||
|
||||
<el-card shadow="never" class="table-card">
|
||||
<el-tabs v-model="activeTab">
|
||||
<!-- 课程成绩 -->
|
||||
<el-tab-pane label="课程成绩" name="grades">
|
||||
<el-table v-loading="loading" :data="gradesData" stripe border style="width: 100%">
|
||||
<el-table-column type="index" label="序号" width="55" align="center" />
|
||||
<el-table-column prop="xybh" label="学员编号" min-width="110" show-overflow-tooltip />
|
||||
<el-table-column prop="kmbh" label="科目编号" min-width="110" show-overflow-tooltip />
|
||||
<el-table-column prop="klx" label="课类型" min-width="90" align="center" />
|
||||
<el-table-column prop="kscj" label="考试成绩" min-width="90" align="center" />
|
||||
<el-table-column prop="pscj" label="平时成绩" min-width="90" align="center" />
|
||||
<el-table-column prop="zzcj" label="最终成绩" min-width="90" align="center" />
|
||||
<el-table-column prop="bkcj" label="补考成绩" min-width="90" align="center" />
|
||||
<el-table-column prop="bkcs" label="补考次数" min-width="90" align="center" />
|
||||
<el-table-column prop="ksqk" label="考试情况" min-width="90" align="center" />
|
||||
<el-table-column prop="qwxf" label="期望学分" min-width="90" align="center" />
|
||||
<el-table-column prop="ytg" label="已通过" min-width="80" align="center" />
|
||||
<el-table-column prop="nd" label="年度" min-width="80" align="center" />
|
||||
</el-table>
|
||||
<el-empty v-if="!loading && gradesData.length === 0" description="暂无课程成绩数据" :image-size="60" />
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 课程过程成绩 -->
|
||||
<el-tab-pane label="课程过程成绩" name="process">
|
||||
<el-table v-loading="loading" :data="processGradesData" stripe border style="width: 100%">
|
||||
<el-table-column type="index" label="序号" width="55" align="center" />
|
||||
<el-table-column prop="xybh" label="学员编号" min-width="110" show-overflow-tooltip />
|
||||
<el-table-column prop="kmbh" label="科目编号" min-width="110" show-overflow-tooltip />
|
||||
<el-table-column prop="klx" label="课类型" min-width="90" align="center" />
|
||||
<el-table-column prop="yscj" label="原始成绩" min-width="90" align="center" />
|
||||
<el-table-column prop="zzcj" label="最终成绩" min-width="90" align="center" />
|
||||
<el-table-column prop="bkcj1" label="补考1" min-width="80" align="center" />
|
||||
<el-table-column prop="bkcj2" label="补考2" min-width="80" align="center" />
|
||||
<el-table-column prop="bkcj3" label="补考3" min-width="80" align="center" />
|
||||
<el-table-column prop="ksqk" label="考试情况" min-width="90" align="center" />
|
||||
<el-table-column prop="nd" label="年度" min-width="80" align="center" />
|
||||
</el-table>
|
||||
<el-empty v-if="!loading && processGradesData.length === 0" description="暂无课程过程成绩数据" :image-size="60" />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
<div class="pagination">
|
||||
<el-pagination
|
||||
:current-page="pagination.pageNum"
|
||||
:page-size="pagination.pageSize"
|
||||
:total="pagination.total"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
background
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handlePageChange"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
listStudentGrades,
|
||||
listStudentProcessGrades,
|
||||
exportStudentGrades,
|
||||
exportStudentProcessGrades
|
||||
} from "@/api/studentRecords/studentRecords"
|
||||
|
||||
export default {
|
||||
name: 'ScoreIndex',
|
||||
data() {
|
||||
return {
|
||||
// ==================== 查询条件 ====================
|
||||
queryForm: {
|
||||
xybh: '',
|
||||
nd: ''
|
||||
},
|
||||
|
||||
// ==================== 数据表格 ====================
|
||||
// 数据来源:grades 课程成绩 / process 课程过程成绩
|
||||
activeTab: 'grades',
|
||||
loading: false,
|
||||
gradesData: [],
|
||||
processGradesData: [],
|
||||
pagination: { pageNum: 1, pageSize: 20, total: 0 },
|
||||
|
||||
// ==================== 导出 ====================
|
||||
exportLoading: false
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
activeTab() {
|
||||
this.handleSearch()
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadData()
|
||||
},
|
||||
methods: {
|
||||
/** 组装查询参数,仅传非空值 */
|
||||
buildQueryParams(params) {
|
||||
const xybh = this.queryForm.xybh && this.queryForm.xybh.trim()
|
||||
const nd = this.queryForm.nd && this.queryForm.nd.trim()
|
||||
if (xybh) params.xybh = xybh
|
||||
if (nd) params.nd = nd
|
||||
},
|
||||
|
||||
loadData() {
|
||||
this.loading = true
|
||||
const params = {
|
||||
pageNum: this.pagination.pageNum,
|
||||
pageSize: this.pagination.pageSize
|
||||
}
|
||||
this.buildQueryParams(params)
|
||||
const request = this.activeTab === 'grades' ? listStudentGrades(params) : listStudentProcessGrades(params)
|
||||
request.then(res => {
|
||||
const data = res.data || {}
|
||||
const records = data.records || []
|
||||
if (this.activeTab === 'grades') {
|
||||
this.gradesData = records
|
||||
} else {
|
||||
this.processGradesData = records
|
||||
}
|
||||
this.pagination.total = data.total || 0
|
||||
this.loading = false
|
||||
}).catch(() => {
|
||||
if (this.activeTab === 'grades') {
|
||||
this.gradesData = []
|
||||
} else {
|
||||
this.processGradesData = []
|
||||
}
|
||||
this.pagination.total = 0
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
|
||||
handleSearch() {
|
||||
this.pagination.pageNum = 1
|
||||
this.loadData()
|
||||
},
|
||||
handlePageChange(page) {
|
||||
this.pagination.pageNum = page
|
||||
this.loadData()
|
||||
},
|
||||
handleSizeChange(size) {
|
||||
this.pagination.pageSize = size
|
||||
this.pagination.pageNum = 1
|
||||
this.loadData()
|
||||
},
|
||||
|
||||
/** 通用 blob 下载 */
|
||||
downloadBlob(blob, filename) {
|
||||
const link = document.createElement('a')
|
||||
link.href = URL.createObjectURL(blob)
|
||||
link.download = filename
|
||||
link.click()
|
||||
URL.revokeObjectURL(link.href)
|
||||
},
|
||||
|
||||
/** 校验导出所需学员编号 */
|
||||
requireXybh(exportName) {
|
||||
const xybh = this.queryForm.xybh && this.queryForm.xybh.trim()
|
||||
if (!xybh) {
|
||||
this.$message.warning('导出' + exportName + '前请先输入学员编号')
|
||||
return null
|
||||
}
|
||||
return xybh
|
||||
},
|
||||
|
||||
/** 导出课程成绩 Excel */
|
||||
handleExportGrades() {
|
||||
const xybh = this.requireXybh('课程成绩')
|
||||
if (!xybh) return
|
||||
this.exportLoading = true
|
||||
exportStudentGrades(xybh).then(res => {
|
||||
this.downloadBlob(res, `课程成绩_${xybh}.xls`)
|
||||
this.$message.success('课程成绩导出成功')
|
||||
}).catch(() => {}).finally(() => {
|
||||
this.exportLoading = false
|
||||
})
|
||||
},
|
||||
|
||||
/** 导出课程过程成绩 Excel */
|
||||
handleExportProcessGrades() {
|
||||
const xybh = this.requireXybh('课程过程成绩')
|
||||
if (!xybh) return
|
||||
this.exportLoading = true
|
||||
exportStudentProcessGrades(xybh).then(res => {
|
||||
this.downloadBlob(res, `课程过程成绩_${xybh}.xls`)
|
||||
this.$message.success('课程过程成绩导出成功')
|
||||
}).catch(() => {}).finally(() => {
|
||||
this.exportLoading = false
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.score-page {
|
||||
padding: 20px;
|
||||
|
||||
.search-card {
|
||||
margin-bottom: 16px;
|
||||
|
||||
.search-form {
|
||||
.el-form-item {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.table-card {
|
||||
.el-table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,717 @@
|
||||
<template>
|
||||
<div class="app-container shift-team-page">
|
||||
<!-- ==================== 1. 查询条件 ==================== -->
|
||||
<el-card shadow="never" class="search-card">
|
||||
<el-form ref="searchFormRef" :model="searchForm" label-width="90px" class="search-form">
|
||||
<el-row :gutter="24">
|
||||
<el-col :xs="24" :sm="12" :md="12" :lg="8" :xl="8">
|
||||
<el-form-item label="学员队名称">
|
||||
<el-input v-model="searchForm.xydmc" placeholder="请输入学员队名称" clearable @keyup.enter.native="handleSearch" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="12" :lg="8" :xl="8">
|
||||
<el-form-item label="年级">
|
||||
<el-input v-model="searchForm.nj" placeholder="请输入年级" clearable @keyup.enter.native="handleSearch" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="12" :lg="8" :xl="8">
|
||||
<el-form-item label="专业代号">
|
||||
<el-input v-model="searchForm.zydh" placeholder="请输入专业代号" clearable @keyup.enter.native="handleSearch" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="12" :lg="8" :xl="8">
|
||||
<el-form-item label="学员队类型">
|
||||
<el-input v-model="searchForm.xydlx" placeholder="请输入学员队类型" clearable @keyup.enter.native="handleSearch" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="12" :lg="8" :xl="8">
|
||||
<el-form-item label="任务类别">
|
||||
<el-input v-model="searchForm.rwlb" placeholder="请输入任务类别" clearable @keyup.enter.native="handleSearch" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="12" :lg="8" :xl="8">
|
||||
<el-form-item label="虚实类型">
|
||||
<el-select v-model="searchForm.xslx" placeholder="请选择虚实类型" clearable class="w-full">
|
||||
<el-option label="虚" :value="0" />
|
||||
<el-option label="实" :value="1" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row class="search-actions-row">
|
||||
<el-col :span="24" class="search-actions">
|
||||
<el-button type="primary" icon="el-icon-search" @click="handleSearch">查询</el-button>
|
||||
<el-button icon="el-icon-refresh" @click="handleReset">重置</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<!-- ==================== 2. 工具栏:新增 / 导入 / 导出 ==================== -->
|
||||
<div class="toolbar">
|
||||
<el-button type="primary" icon="el-icon-plus" @click="handleAdd">新增学员队</el-button>
|
||||
<el-button icon="el-icon-download" @click="handleExport">导出 Excel</el-button>
|
||||
<el-button icon="el-icon-download" @click="handleDownloadTemplate">模板下载</el-button>
|
||||
<div class="file-input">
|
||||
<input ref="fileInputRef" type="file" accept=".xls,.xlsx" style="display: none" @change="handleFileChange" />
|
||||
<el-button icon="el-icon-folder-opened" @click="handleChooseFile">选择文件</el-button>
|
||||
<span class="file-name">{{ fileName }}</span>
|
||||
<el-button type="primary" icon="el-icon-upload2" :loading="uploading" @click="handleUpload">上传数据</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ==================== 3. 列表 ==================== -->
|
||||
<el-card shadow="never" class="table-card">
|
||||
<div class="table-header">
|
||||
<span class="table-title">教学班次列表:</span>
|
||||
</div>
|
||||
<el-table v-loading="loading" :data="tableData" stripe border highlight-current-row>
|
||||
<el-table-column prop="xydbh" label="学员队编号" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column prop="xydmc" label="学员队名称" min-width="130" show-overflow-tooltip />
|
||||
<el-table-column prop="nj" label="年级" width="90" align="center" />
|
||||
<el-table-column prop="zydh" label="专业代号" min-width="110" show-overflow-tooltip />
|
||||
<el-table-column prop="rwlb" label="任务类别" width="100" align="center" />
|
||||
<el-table-column label="虚实类型" width="90" align="center">
|
||||
<template slot-scope="{ row }">
|
||||
<el-tag :type="Number(row.xslx) === 1 ? 'success' : 'info'" size="mini">
|
||||
{{ Number(row.xslx) === 1 ? '实' : '虚' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="在校状态" width="90" align="center">
|
||||
<template slot-scope="{ row }">
|
||||
<el-tag :type="Number(row.zxzt) === 1 ? 'success' : 'danger'" size="mini">
|
||||
{{ Number(row.zxzt) === 1 ? '在校' : '毕业' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="xydrs" label="人数" width="80" align="center" />
|
||||
<el-table-column label="入学日期" width="110" align="center">
|
||||
<template slot-scope="{ row }">{{ formatDate(row.rxrq) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="毕业日期" width="110" align="center">
|
||||
<template slot-scope="{ row }">{{ formatDate(row.byrq) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="190" align="center" fixed="right">
|
||||
<template slot-scope="{ row }">
|
||||
<el-button type="text" size="small" icon="el-icon-view" @click="handleDetail(row)">详情</el-button>
|
||||
<el-button type="text" size="small" icon="el-icon-edit" @click="handleEdit(row)">编辑</el-button>
|
||||
<el-button type="text" size="small" class="text-danger" @click="handleDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-pagination
|
||||
:current-page="pageNum"
|
||||
:page-size="pageSize"
|
||||
:total="total"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
class="pagination-wrapper"
|
||||
@current-change="handlePageChange"
|
||||
@size-change="handleSizeChange"
|
||||
/>
|
||||
</el-card>
|
||||
|
||||
<!-- ==================== 4. 新增/编辑对话框 ==================== -->
|
||||
<el-dialog
|
||||
:visible="formDialogVisible"
|
||||
:title="dialogTitle"
|
||||
width="760px"
|
||||
:close-on-click-modal="false"
|
||||
@update:visible="val => formDialogVisible = val"
|
||||
>
|
||||
<el-form ref="formRef" :model="form" :rules="formRules" label-width="130px" class="form-dialog-form">
|
||||
<el-divider content-position="left">基本信息</el-divider>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="学员队编号" prop="xydbh">
|
||||
<el-input v-model="form.xydbh" placeholder="请输入学员队编号" :disabled="!isAdd" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="学员队名称" prop="xydmc">
|
||||
<el-input v-model="form.xydmc" placeholder="请输入学员队名称" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="学员队人数" prop="xydrs">
|
||||
<el-input-number v-model="form.xydrs" :min="0" controls-position="right" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="年级">
|
||||
<el-input v-model="form.nj" placeholder="请输入年级" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="专业代号" prop="zydh">
|
||||
<el-input v-model="form.zydh" placeholder="请输入专业代号" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="专业教室编号">
|
||||
<el-input v-model="form.zyjsbh" placeholder="请输入专业教室编号" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="学员队类型" prop="xydlx">
|
||||
<el-input v-model="form.xydlx" placeholder="请输入学员队类型" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="节次类别" prop="jclb">
|
||||
<el-input v-model="form.jclb" placeholder="请输入节次类别" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="系统模式" prop="xtms">
|
||||
<el-input v-model="form.xtms" placeholder="请输入系统模式" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="简称">
|
||||
<el-input v-model="form.jc" placeholder="请输入简称" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="所属单位">
|
||||
<el-input v-model="form.ssdw" placeholder="请输入所属单位" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="序号">
|
||||
<el-input-number v-model="form.xh" :min="0" controls-position="right" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-divider content-position="left">培训与状态</el-divider>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="任务类别" prop="rwlb">
|
||||
<el-input v-model="form.rwlb" placeholder="请输入任务类别" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="虚实类型" prop="xslx">
|
||||
<el-select v-model="form.xslx" class="w-full">
|
||||
<el-option label="虚" :value="0" />
|
||||
<el-option label="实" :value="1" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="主体培训任务" prop="ztpxrw">
|
||||
<el-select v-model="form.ztpxrw" class="w-full">
|
||||
<el-option label="是" :value="1" />
|
||||
<el-option label="否" :value="0" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="培训任务标识号">
|
||||
<el-input v-model="form.pxrwbsh" placeholder="请输入培训任务标识号" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="在校状态">
|
||||
<el-select v-model="form.zxzt" class="w-full">
|
||||
<el-option label="在校" :value="1" />
|
||||
<el-option label="毕业" :value="0" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="入学日期" prop="rxrq">
|
||||
<el-date-picker v-model="form.rxrq" type="date" value-format="yyyy-MM-dd" placeholder="请选择入学日期" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="毕业日期" prop="byrq">
|
||||
<el-date-picker v-model="form.byrq" type="date" value-format="yyyy-MM-dd" placeholder="请选择毕业日期" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="JSON字段">
|
||||
<el-input v-model="form.jsonzd" placeholder="请输入 JSON 字段" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="form.bz" type="textarea" :rows="2" placeholder="请输入备注" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer">
|
||||
<el-button @click="formDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="formSaving" @click="handleFormSubmit">确定</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- ==================== 5. 详情对话框 ==================== -->
|
||||
<el-dialog title="学员队详情" :visible="detailDialogVisible" width="720px" :close-on-click-modal="false"
|
||||
@update:visible="val => detailDialogVisible = val">
|
||||
<div v-loading="detailLoading" class="detail-body">
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="学员队编号">{{ detailForm.xydbh || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="学员队名称">{{ detailForm.xydmc || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="学员队人数">{{ fmtValue(detailForm.xydrs) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="年级">{{ detailForm.nj || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="专业代号">{{ detailForm.zydh || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="专业教室编号">{{ detailForm.zyjsbh || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="学员队类型">{{ detailForm.xydlx || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="节次类别">{{ detailForm.jclb || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="系统模式">{{ detailForm.xtms || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="简称">{{ detailForm.jc || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="所属单位">{{ detailForm.ssdw || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="序号">{{ fmtValue(detailForm.xh) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="任务类别">{{ detailForm.rwlb || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="虚实类型">
|
||||
<el-tag :type="Number(detailForm.xslx) === 1 ? 'success' : 'info'" size="mini">
|
||||
{{ Number(detailForm.xslx) === 1 ? '实' : '虚' }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="主体培训任务">
|
||||
<el-tag :type="Number(detailForm.ztpxrw) === 1 ? 'success' : 'info'" size="mini">
|
||||
{{ Number(detailForm.ztpxrw) === 1 ? '是' : '否' }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="培训任务标识号">{{ detailForm.pxrwbsh || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="在校状态">
|
||||
<el-tag :type="Number(detailForm.zxzt) === 1 ? 'success' : 'danger'" size="mini">
|
||||
{{ Number(detailForm.zxzt) === 1 ? '在校' : '毕业' }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="入学日期">{{ detailForm.rxrq || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="毕业日期">{{ detailForm.byrq || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="JSON字段" :span="2">{{ detailForm.jsonzd || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="备注" :span="2">{{ detailForm.bz || '-' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
<div slot="footer">
|
||||
<el-button @click="detailDialogVisible = false">关闭</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { saveAs } from 'file-saver'
|
||||
import {
|
||||
listTeam,
|
||||
getTeam,
|
||||
addTeam,
|
||||
updateTeam,
|
||||
delTeam,
|
||||
importTeam,
|
||||
exportTeam
|
||||
} from '@/api/studentRecords/team'
|
||||
|
||||
export default {
|
||||
name: 'ShiftTeamIndex',
|
||||
data() {
|
||||
return {
|
||||
// ==================== 查询条件 ====================
|
||||
searchForm: {
|
||||
xydmc: '',
|
||||
nj: '',
|
||||
zydh: '',
|
||||
xydlx: '',
|
||||
rwlb: '',
|
||||
xslx: undefined
|
||||
},
|
||||
|
||||
// ==================== 文件导入 ====================
|
||||
selectedFile: null,
|
||||
uploading: false,
|
||||
|
||||
// ==================== 列表数据 ====================
|
||||
loading: false,
|
||||
tableData: [],
|
||||
total: 0,
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
|
||||
// ==================== 新增/编辑 ====================
|
||||
formDialogVisible: false,
|
||||
dialogTitle: '新增学员队',
|
||||
isAdd: true,
|
||||
formSaving: false,
|
||||
form: this.createEmptyForm(),
|
||||
formRules: {
|
||||
xydbh: [{ required: true, message: '请输入学员队编号', trigger: 'blur' }],
|
||||
xydmc: [{ required: true, message: '请输入学员队名称', trigger: 'blur' }],
|
||||
xydrs: [{ required: true, message: '请输入学员队人数', trigger: 'change' }],
|
||||
zydh: [{ required: true, message: '请输入专业代号', trigger: 'blur' }],
|
||||
rwlb: [{ required: true, message: '请输入任务类别', trigger: 'blur' }],
|
||||
xslx: [{ required: true, message: '请选择虚实类型', trigger: 'change' }],
|
||||
rxrq: [{ required: true, message: '请选择入学日期', trigger: 'change' }],
|
||||
byrq: [{ required: true, message: '请选择毕业日期', trigger: 'change' }],
|
||||
xydlx: [{ required: true, message: '请输入学员队类型', trigger: 'blur' }],
|
||||
jclb: [{ required: true, message: '请输入节次类别', trigger: 'blur' }],
|
||||
xtms: [{ required: true, message: '请输入系统模式', trigger: 'blur' }],
|
||||
ztpxrw: [{ required: true, message: '请选择主体培训任务', trigger: 'change' }]
|
||||
},
|
||||
|
||||
// ==================== 详情 ====================
|
||||
detailDialogVisible: false,
|
||||
detailLoading: false,
|
||||
detailForm: {}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
fileName() {
|
||||
return (this.selectedFile && this.selectedFile.name) || '未选择任何文件'
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.fetchList()
|
||||
},
|
||||
methods: {
|
||||
createEmptyForm() {
|
||||
return {
|
||||
xydbh: '',
|
||||
xydmc: '',
|
||||
xydrs: undefined,
|
||||
nj: '',
|
||||
zydh: '',
|
||||
zyjsbh: '',
|
||||
bz: '',
|
||||
zxzt: 1,
|
||||
xh: undefined,
|
||||
rwlb: '',
|
||||
xslx: 1,
|
||||
rxrq: '',
|
||||
byrq: '',
|
||||
xydlx: '',
|
||||
pxrwbsh: '',
|
||||
jclb: '',
|
||||
xtms: '',
|
||||
jsonzd: '',
|
||||
jc: '',
|
||||
ssdw: '',
|
||||
ztpxrw: 1
|
||||
}
|
||||
},
|
||||
|
||||
// ==================== 日期/数值展示 ====================
|
||||
formatDate(val) {
|
||||
if (!val) return ''
|
||||
return String(val).substring(0, 10)
|
||||
},
|
||||
fmtValue(val) {
|
||||
return val === null || val === undefined || val === '' ? '-' : val
|
||||
},
|
||||
|
||||
/** 构建查询条件(不含分页),供列表查询与导出共用 */
|
||||
buildQuery() {
|
||||
const params = {}
|
||||
;['xydmc', 'nj', 'zydh', 'xydlx', 'rwlb'].forEach(key => {
|
||||
const value = this.searchForm[key]
|
||||
if (value !== '' && value !== null && value !== undefined) {
|
||||
params[key] = String(value).trim()
|
||||
}
|
||||
})
|
||||
if (this.searchForm.xslx !== undefined && this.searchForm.xslx !== '' && this.searchForm.xslx !== null) {
|
||||
params.xslx = this.searchForm.xslx
|
||||
}
|
||||
return params
|
||||
},
|
||||
|
||||
// ==================== 查询列表 ====================
|
||||
fetchList() {
|
||||
this.loading = true
|
||||
const params = {
|
||||
pageNum: this.pageNum,
|
||||
pageSize: this.pageSize,
|
||||
...this.buildQuery()
|
||||
}
|
||||
listTeam(params).then(response => {
|
||||
const data = response.data || {}
|
||||
this.tableData = data.records || []
|
||||
this.total = data.total || 0
|
||||
this.loading = false
|
||||
}).catch(() => {
|
||||
this.tableData = []
|
||||
this.total = 0
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
|
||||
handleSearch() {
|
||||
this.pageNum = 1
|
||||
this.fetchList()
|
||||
},
|
||||
|
||||
handleReset() {
|
||||
this.$refs.searchFormRef.resetFields()
|
||||
this.pageNum = 1
|
||||
this.fetchList()
|
||||
},
|
||||
|
||||
handlePageChange(current) {
|
||||
this.pageNum = current
|
||||
this.fetchList()
|
||||
},
|
||||
|
||||
handleSizeChange(size) {
|
||||
this.pageSize = size
|
||||
this.pageNum = 1
|
||||
this.fetchList()
|
||||
},
|
||||
|
||||
// ==================== 新增/编辑 ====================
|
||||
handleAdd() {
|
||||
this.isAdd = true
|
||||
this.dialogTitle = '新增学员队'
|
||||
this.form = this.createEmptyForm()
|
||||
this.formDialogVisible = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.formRef && this.$refs.formRef.clearValidate()
|
||||
})
|
||||
},
|
||||
|
||||
handleEdit(row) {
|
||||
this.isAdd = false
|
||||
this.dialogTitle = '编辑学员队'
|
||||
this.form = this.createEmptyForm()
|
||||
this.formDialogVisible = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.formRef && this.$refs.formRef.clearValidate()
|
||||
})
|
||||
getTeam(row.xydbh).then(response => {
|
||||
const d = response.data || {}
|
||||
this.form = {
|
||||
xydbh: d.xydbh || '',
|
||||
xydmc: d.xydmc || '',
|
||||
xydrs: d.xydrs !== null && d.xydrs !== undefined ? d.xydrs : undefined,
|
||||
nj: d.nj || '',
|
||||
zydh: d.zydh || '',
|
||||
zyjsbh: d.zyjsbh || '',
|
||||
bz: d.bz || '',
|
||||
zxzt: d.zxzt !== null && d.zxzt !== undefined ? Number(d.zxzt) : 1,
|
||||
xh: d.xh !== null && d.xh !== undefined ? d.xh : undefined,
|
||||
rwlb: d.rwlb || '',
|
||||
xslx: d.xslx !== null && d.xslx !== undefined ? Number(d.xslx) : 1,
|
||||
rxrq: this.formatDate(d.rxrq),
|
||||
byrq: this.formatDate(d.byrq),
|
||||
xydlx: d.xydlx || '',
|
||||
pxrwbsh: d.pxrwbsh || '',
|
||||
jclb: d.jclb || '',
|
||||
xtms: d.xtms || '',
|
||||
jsonzd: d.jsonzd || '',
|
||||
jc: d.jc || '',
|
||||
ssdw: d.ssdw || '',
|
||||
ztpxrw: d.ztpxrw !== null && d.ztpxrw !== undefined ? Number(d.ztpxrw) : 1
|
||||
}
|
||||
}).catch(() => {})
|
||||
},
|
||||
|
||||
buildPayload() {
|
||||
const f = this.form
|
||||
const payload = {
|
||||
xydbh: f.xydbh,
|
||||
xydmc: f.xydmc,
|
||||
zydh: f.zydh,
|
||||
rwlb: f.rwlb,
|
||||
xydlx: f.xydlx,
|
||||
jclb: f.jclb,
|
||||
xtms: f.xtms,
|
||||
zxzt: Number(f.zxzt),
|
||||
xslx: Number(f.xslx),
|
||||
ztpxrw: Number(f.ztpxrw)
|
||||
}
|
||||
// 数值字段
|
||||
if (f.xydrs !== '' && f.xydrs !== null && f.xydrs !== undefined) payload.xydrs = Number(f.xydrs)
|
||||
if (f.xh !== '' && f.xh !== null && f.xh !== undefined) payload.xh = Number(f.xh)
|
||||
// 日期字段
|
||||
if (f.rxrq) payload.rxrq = f.rxrq
|
||||
if (f.byrq) payload.byrq = f.byrq
|
||||
// 其余文本字段非空才传
|
||||
;['nj', 'zyjsbh', 'pxrwbsh', 'jsonzd', 'jc', 'ssdw', 'bz'].forEach(key => {
|
||||
if (f[key] !== '' && f[key] !== null && f[key] !== undefined) {
|
||||
payload[key] = String(f[key]).trim()
|
||||
}
|
||||
})
|
||||
return payload
|
||||
},
|
||||
|
||||
handleFormSubmit() {
|
||||
this.$refs.formRef.validate(valid => {
|
||||
if (!valid) return
|
||||
this.formSaving = true
|
||||
const payload = this.buildPayload()
|
||||
const request = this.isAdd ? addTeam(payload) : updateTeam(payload)
|
||||
request.then(() => {
|
||||
this.$message.success(this.isAdd ? '新增成功' : '修改成功')
|
||||
this.formDialogVisible = false
|
||||
this.fetchList()
|
||||
}).catch(() => {
|
||||
}).finally(() => {
|
||||
this.formSaving = false
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
// ==================== 删除 ====================
|
||||
handleDelete(row) {
|
||||
this.$confirm(`确定要删除「${row.xydmc || row.xydbh}」吗?`, '系统提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
return delTeam(row.xydbh)
|
||||
}).then(() => {
|
||||
this.$message.success('删除成功')
|
||||
this.fetchList()
|
||||
}).catch(() => {})
|
||||
},
|
||||
|
||||
// ==================== 详情 ====================
|
||||
handleDetail(row) {
|
||||
this.detailDialogVisible = true
|
||||
this.detailLoading = true
|
||||
this.detailForm = {}
|
||||
getTeam(row.xydbh).then(response => {
|
||||
this.detailForm = response.data || {}
|
||||
this.detailLoading = false
|
||||
}).catch(() => {
|
||||
this.detailLoading = false
|
||||
})
|
||||
},
|
||||
|
||||
// ==================== 导出 ====================
|
||||
handleExport() {
|
||||
exportTeam(this.buildQuery()).then(blob => {
|
||||
saveAs(blob, '班次(学员队)管理.xlsx')
|
||||
this.$message.success('导出成功')
|
||||
}).catch(() => {})
|
||||
},
|
||||
|
||||
// ==================== 模板下载 / 导入 ====================
|
||||
handleDownloadTemplate() {
|
||||
// 后端暂未提供模板下载接口,仅作提示
|
||||
this.$message.info('后端暂未提供该接口')
|
||||
},
|
||||
|
||||
handleChooseFile() {
|
||||
this.$refs.fileInputRef && this.$refs.fileInputRef.click()
|
||||
},
|
||||
|
||||
handleFileChange(e) {
|
||||
const input = e.target
|
||||
this.selectedFile = (input.files && input.files[0]) || null
|
||||
},
|
||||
|
||||
handleUpload() {
|
||||
if (!this.selectedFile) {
|
||||
this.$message.warning('请先选择文件')
|
||||
return
|
||||
}
|
||||
this.uploading = true
|
||||
importTeam(this.selectedFile).then(res => {
|
||||
const count = res && res.data
|
||||
if (count !== null && count !== undefined) {
|
||||
this.$message.success(`导入成功,共 ${count} 条记录`)
|
||||
} else {
|
||||
this.$message.success((res && res.msg) || '导入成功')
|
||||
}
|
||||
this.selectedFile = null
|
||||
this.$refs.fileInputRef && (this.$refs.fileInputRef.value = '')
|
||||
this.pageNum = 1
|
||||
this.fetchList()
|
||||
}).catch(() => {}).finally(() => {
|
||||
this.uploading = false
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.app-container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.shift-team-page {
|
||||
.search-card {
|
||||
margin-bottom: 16px;
|
||||
|
||||
.search-form {
|
||||
.w-full {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.search-actions-row {
|
||||
margin-top: 2px;
|
||||
border-top: 1px dashed #ebeef5;
|
||||
padding-top: 14px;
|
||||
}
|
||||
|
||||
.search-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 16px;
|
||||
|
||||
.file-input {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
.file-name {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
max-width: 180px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.table-card {
|
||||
.table-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16px;
|
||||
|
||||
.table-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
}
|
||||
|
||||
.pagination-wrapper {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.form-dialog-form {
|
||||
max-height: 62vh;
|
||||
overflow-y: auto;
|
||||
padding-right: 6px;
|
||||
|
||||
::v-deep .el-divider--horizontal {
|
||||
margin: 4px 0 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.text-danger {
|
||||
color: #f56c6c;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,494 @@
|
||||
<template>
|
||||
<div class="app-container status-change-page">
|
||||
<!-- ==================== 1. 查询条件 ==================== -->
|
||||
<el-card shadow="never" class="search-card">
|
||||
<el-form ref="searchFormRef" :model="searchForm" label-width="90px" class="search-form">
|
||||
<el-row :gutter="24">
|
||||
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
|
||||
<el-form-item label="编号">
|
||||
<el-input v-model="searchForm.bh" placeholder="请输入编号" clearable @keyup.enter.native="handleSearch" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
|
||||
<el-form-item label="学员编号">
|
||||
<el-input v-model="searchForm.xybh" placeholder="请输入学员编号" clearable @keyup.enter.native="handleSearch" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
|
||||
<el-form-item label="申请类型">
|
||||
<el-select v-model="searchForm.sqlx" placeholder="请选择申请类型" clearable class="w-full">
|
||||
<el-option v-for="item in applyTypeOptions" :key="item" :label="item" :value="item" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
|
||||
<el-form-item label="状态">
|
||||
<el-select v-model="searchForm.zt" placeholder="请选择状态" clearable class="w-full">
|
||||
<el-option v-for="(item, key) in statusMap" :key="key" :label="item.label" :value="Number(key)" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="24" class="search-actions">
|
||||
<div class="search-buttons">
|
||||
<el-button type="primary" @click="handleSearch">查询</el-button>
|
||||
<el-button @click="handleReset">重置</el-button>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<!-- ==================== 2. 列表 ==================== -->
|
||||
<el-card shadow="never" class="table-card">
|
||||
<div class="table-header">
|
||||
<span class="table-title">学籍异动申请列表:</span>
|
||||
<el-button type="primary" icon="el-icon-plus" @click="handleAdd">新增学籍异动申请</el-button>
|
||||
</div>
|
||||
<el-table v-loading="loading" :data="tableData" stripe border highlight-current-row>
|
||||
<el-table-column type="index" label="序号" width="60" align="center" />
|
||||
<el-table-column prop="bh" label="编号" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column prop="xybh" label="学员编号" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column prop="sqlx" label="申请类型" width="100" align="center" />
|
||||
<el-table-column prop="sy" label="事由" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column prop="bz" label="备注" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column label="状态" width="90" align="center">
|
||||
<template slot-scope="{ row }">
|
||||
<el-tag :type="statusTagType(row.zt)" size="mini" effect="light">{{ statusLabel(row.zt) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="提交时间" width="160" align="center">
|
||||
<template slot-scope="{ row }">{{ row.tjsj ? fmtDateTime(row.tjsj) : '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" width="160" align="center">
|
||||
<template slot-scope="{ row }">{{ row.cjsj ? fmtDateTime(row.cjsj) : '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="修改时间" width="160" align="center">
|
||||
<template slot-scope="{ row }">{{ row.xgsj ? fmtDateTime(row.xgsj) : '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="230" align="center" fixed="right">
|
||||
<template slot-scope="{ row }">
|
||||
<el-button v-if="!row.tjsj" type="text" size="small" icon="el-icon-upload2" @click="handleSubmit(row)">提交</el-button>
|
||||
<el-button v-else type="text" size="small" icon="el-icon-check" disabled>已提交</el-button>
|
||||
<el-button type="text" size="small" icon="el-icon-view" @click="handleDetail(row)">详情</el-button>
|
||||
<el-button type="text" size="small" icon="el-icon-edit" @click="handleEdit(row)">编辑</el-button>
|
||||
<el-button type="text" size="small" class="text-danger" @click="handleDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty v-if="!loading && tableData.length === 0" description="暂无学籍异动申请数据" :image-size="60" />
|
||||
|
||||
<el-pagination
|
||||
:current-page="pageNum"
|
||||
:page-size="pageSize"
|
||||
:total="total"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
class="pagination-wrapper"
|
||||
@current-change="handlePageChange"
|
||||
@size-change="handleSizeChange"
|
||||
/>
|
||||
</el-card>
|
||||
|
||||
<!-- ==================== 3. 新增/编辑对话框 ==================== -->
|
||||
<el-dialog
|
||||
:visible="formDialogVisible"
|
||||
:title="dialogTitle"
|
||||
width="600px"
|
||||
:close-on-click-modal="false"
|
||||
@update:visible="val => formDialogVisible = val"
|
||||
>
|
||||
<el-form ref="formRef" :model="form" :rules="formRules" label-width="110px" class="add-form">
|
||||
<el-form-item label="编号" prop="bh">
|
||||
<el-input v-model="form.bh" placeholder="请输入编号" :disabled="!isAdd" />
|
||||
</el-form-item>
|
||||
<el-form-item label="学员编号" prop="xybh">
|
||||
<el-input v-model="form.xybh" placeholder="请输入学员编号" />
|
||||
</el-form-item>
|
||||
<el-form-item label="申请类型" prop="sqlx">
|
||||
<el-select v-model="form.sqlx" placeholder="请选择申请类型" class="w-full">
|
||||
<el-option v-for="item in applyTypeOptions" :key="item" :label="item" :value="item" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="事由" prop="sy">
|
||||
<el-input v-model="form.sy" type="textarea" :rows="3" placeholder="请输入事由" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="form.bz" type="textarea" :rows="3" placeholder="请输入备注" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="zt">
|
||||
<el-select v-model="form.zt" placeholder="请选择状态" class="w-full">
|
||||
<el-option v-for="(item, key) in statusMap" :key="key" :label="item.label" :value="Number(key)" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer">
|
||||
<el-button @click="formDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="formSaving" @click="handleFormSubmit">确定</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- ==================== 4. 详情对话框 ==================== -->
|
||||
<el-dialog title="学籍异动申请详情" :visible="detailDialogVisible" width="760px" :close-on-click-modal="false"
|
||||
@update:visible="val => detailDialogVisible = val">
|
||||
<div v-loading="detailLoading" class="detail-body">
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="编号">{{ detailForm.bh || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="学员编号">{{ detailForm.xybh || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="申请类型">{{ detailForm.sqlx || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="状态">
|
||||
<el-tag :type="statusTagType(detailForm.zt)" size="mini">{{ statusLabel(detailForm.zt) }}</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="事由" :span="2">{{ detailForm.sy || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="备注" :span="2">{{ detailForm.bz || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">{{ detailForm.cjsj || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="修改时间">{{ detailForm.xgsj || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="提交时间">{{ detailForm.tjsj || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="学员队审核意见">{{ detailForm.xydshyj || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="学员队干部编号">{{ detailForm.xydgbbh || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="学员队干部审核时间">{{ detailForm.xydshsj || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="管理员账户编号">{{ detailForm.glyzhbh || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="管理员审核意见">{{ detailForm.glyshyj || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="管理员审核时间">{{ detailForm.glyshsj || '-' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
<div slot="footer">
|
||||
<el-button @click="detailDialogVisible = false">关闭</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
listApplication,
|
||||
getApplication,
|
||||
addApplication,
|
||||
updateApplication,
|
||||
delApplication
|
||||
} from '@/api/studentRecords/application'
|
||||
|
||||
export default {
|
||||
name: 'StatusChangeIndex',
|
||||
data() {
|
||||
return {
|
||||
// ==================== 申请类型选项(学籍异动) ====================
|
||||
applyTypeOptions: ['休学', '复学', '退学', '转学', '留级', '转专业'],
|
||||
|
||||
// ==================== 状态映射:0-待审核 1-已审核 2-驳回等 ====================
|
||||
statusMap: {
|
||||
0: { label: '待审核', type: 'warning' },
|
||||
1: { label: '已审核', type: 'success' },
|
||||
2: { label: '驳回', type: 'danger' }
|
||||
},
|
||||
|
||||
// ==================== 查询条件 ====================
|
||||
searchForm: {
|
||||
bh: '',
|
||||
xybh: '',
|
||||
sqlx: '',
|
||||
zt: undefined
|
||||
},
|
||||
|
||||
// ==================== 列表数据 ====================
|
||||
loading: false,
|
||||
tableData: [],
|
||||
total: 0,
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
|
||||
// ==================== 新增/编辑 ====================
|
||||
formDialogVisible: false,
|
||||
dialogTitle: '新增学籍异动申请',
|
||||
isAdd: true,
|
||||
formSaving: false,
|
||||
form: this.createEmptyForm(),
|
||||
formRules: {
|
||||
bh: [{ required: true, message: '请输入编号', trigger: 'blur' }],
|
||||
xybh: [{ required: true, message: '请输入学员编号', trigger: 'blur' }],
|
||||
sqlx: [{ required: true, message: '请选择申请类型', trigger: 'change' }],
|
||||
sy: [{ required: true, message: '请输入事由', trigger: 'blur' }],
|
||||
zt: [{ required: true, message: '请选择状态', trigger: 'change' }]
|
||||
},
|
||||
|
||||
// ==================== 详情 ====================
|
||||
detailDialogVisible: false,
|
||||
detailLoading: false,
|
||||
detailForm: {}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.fetchList()
|
||||
},
|
||||
methods: {
|
||||
createEmptyForm() {
|
||||
return {
|
||||
bh: '',
|
||||
xybh: '',
|
||||
sqlx: '',
|
||||
sy: '',
|
||||
bz: '',
|
||||
zt: 0
|
||||
}
|
||||
},
|
||||
|
||||
// ==================== 状态展示 ====================
|
||||
// 统一格式化后端 LocalDateTime(去除 T、截断到秒)
|
||||
fmtDateTime(val) {
|
||||
if (val === null || val === undefined || val === '') return ''
|
||||
return String(val).replace('T', ' ').slice(0, 19)
|
||||
},
|
||||
statusLabel(zt) {
|
||||
const item = this.statusMap[zt]
|
||||
return item ? item.label : (zt !== null && zt !== undefined ? String(zt) : '-')
|
||||
},
|
||||
statusTagType(zt) {
|
||||
const item = this.statusMap[zt]
|
||||
return item ? item.type : 'info'
|
||||
},
|
||||
|
||||
// ==================== 查询列表 ====================
|
||||
fetchList() {
|
||||
this.loading = true
|
||||
const params = {
|
||||
pageNum: this.pageNum,
|
||||
pageSize: this.pageSize
|
||||
}
|
||||
// 等值筛选,留空不传
|
||||
;['bh', 'xybh', 'sqlx'].forEach(key => {
|
||||
const value = this.searchForm[key]
|
||||
if (value !== '' && value !== null && value !== undefined) {
|
||||
params[key] = String(value).trim()
|
||||
}
|
||||
})
|
||||
if (this.searchForm.zt !== undefined && this.searchForm.zt !== '' && this.searchForm.zt !== null) {
|
||||
params.zt = this.searchForm.zt
|
||||
}
|
||||
listApplication(params).then(response => {
|
||||
const data = response.data || {}
|
||||
this.tableData = data.records || []
|
||||
this.total = data.total || 0
|
||||
this.loading = false
|
||||
}).catch(() => {
|
||||
this.tableData = []
|
||||
this.total = 0
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
|
||||
handleSearch() {
|
||||
this.pageNum = 1
|
||||
this.fetchList()
|
||||
},
|
||||
|
||||
handleReset() {
|
||||
this.$refs.searchFormRef.resetFields()
|
||||
this.pageNum = 1
|
||||
this.fetchList()
|
||||
},
|
||||
|
||||
handlePageChange(current) {
|
||||
this.pageNum = current
|
||||
this.fetchList()
|
||||
},
|
||||
|
||||
handleSizeChange(size) {
|
||||
this.pageSize = size
|
||||
this.pageNum = 1
|
||||
this.fetchList()
|
||||
},
|
||||
|
||||
// ==================== 新增/编辑 ====================
|
||||
handleAdd() {
|
||||
this.isAdd = true
|
||||
this.dialogTitle = '新增学籍异动申请'
|
||||
this.form = this.createEmptyForm()
|
||||
this.formDialogVisible = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.formRef && this.$refs.formRef.clearValidate()
|
||||
})
|
||||
},
|
||||
|
||||
handleEdit(row) {
|
||||
this.isAdd = false
|
||||
this.dialogTitle = '编辑学籍异动申请'
|
||||
this.form = this.createEmptyForm()
|
||||
this.formDialogVisible = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.formRef && this.$refs.formRef.clearValidate()
|
||||
})
|
||||
getApplication(row.bh).then(response => {
|
||||
const data = response.data || {}
|
||||
this.form = {
|
||||
bh: data.bh || '',
|
||||
xybh: data.xybh || '',
|
||||
sqlx: data.sqlx || '',
|
||||
sy: data.sy || '',
|
||||
bz: data.bz || '',
|
||||
zt: data.zt !== null && data.zt !== undefined ? Number(data.zt) : 0
|
||||
}
|
||||
}).catch(() => {})
|
||||
},
|
||||
|
||||
buildPayload() {
|
||||
const f = this.form
|
||||
const payload = {
|
||||
bh: f.bh,
|
||||
xybh: f.xybh,
|
||||
sqlx: f.sqlx,
|
||||
sy: f.sy,
|
||||
zt: Number(f.zt)
|
||||
}
|
||||
// 备注留空则移除
|
||||
if (f.bz !== '' && f.bz !== null && f.bz !== undefined) {
|
||||
payload.bz = f.bz
|
||||
}
|
||||
return payload
|
||||
},
|
||||
|
||||
handleFormSubmit() {
|
||||
this.$refs.formRef.validate(valid => {
|
||||
if (!valid) return
|
||||
this.formSaving = true
|
||||
const payload = this.buildPayload()
|
||||
const request = this.isAdd ? addApplication(payload) : updateApplication(payload)
|
||||
request.then(() => {
|
||||
this.$message.success(this.isAdd ? '新增成功' : '修改成功')
|
||||
this.formDialogVisible = false
|
||||
this.fetchList()
|
||||
}).catch(() => {
|
||||
}).finally(() => {
|
||||
this.formSaving = false
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
// ==================== 提交 ====================
|
||||
// 无独立提交接口,通过编辑接口写入提交时间 tjsj(zt 保持原值不变)
|
||||
formatNow() {
|
||||
const d = new Date()
|
||||
const pad = n => String(n).padStart(2, '0')
|
||||
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`
|
||||
},
|
||||
|
||||
handleSubmit(row) {
|
||||
this.$confirm(`确定要提交编号为「${row.bh}」的学籍异动申请吗?`, '系统提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
const payload = {
|
||||
bh: row.bh,
|
||||
xybh: row.xybh,
|
||||
sqlx: row.sqlx,
|
||||
sy: row.sy,
|
||||
zt: row.zt !== null && row.zt !== undefined ? Number(row.zt) : 0,
|
||||
tjsj: this.formatNow()
|
||||
}
|
||||
if (row.bz) {
|
||||
payload.bz = row.bz
|
||||
}
|
||||
return updateApplication(payload)
|
||||
}).then(() => {
|
||||
this.$message.success('提交成功')
|
||||
this.fetchList()
|
||||
}).catch(() => {})
|
||||
},
|
||||
|
||||
// ==================== 删除 ====================
|
||||
handleDelete(row) {
|
||||
this.$confirm(`确定要删除编号为「${row.bh}」的学籍异动申请吗?`, '系统提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
return delApplication(row.bh)
|
||||
}).then(() => {
|
||||
this.$message.success('删除成功')
|
||||
this.fetchList()
|
||||
}).catch(() => {})
|
||||
},
|
||||
|
||||
// ==================== 详情 ====================
|
||||
handleDetail(row) {
|
||||
this.detailDialogVisible = true
|
||||
this.detailLoading = true
|
||||
this.detailForm = {}
|
||||
getApplication(row.bh).then(response => {
|
||||
this.detailForm = response.data || {}
|
||||
this.detailLoading = false
|
||||
}).catch(() => {
|
||||
this.detailLoading = false
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.app-container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.status-change-page {
|
||||
.search-card {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
.w-full {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.search-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding-top: 8px;
|
||||
|
||||
.search-tip {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.search-buttons {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.table-card {
|
||||
.table-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16px;
|
||||
|
||||
.table-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
}
|
||||
|
||||
.pagination-wrapper {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.add-form {
|
||||
max-height: 60vh;
|
||||
overflow-y: auto;
|
||||
padding-right: 4px;
|
||||
}
|
||||
|
||||
.text-danger {
|
||||
color: #f56c6c;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,256 @@
|
||||
<template>
|
||||
<div class="app-container warning-result-page">
|
||||
<!-- ==================== 1. 查询条件 ==================== -->
|
||||
<el-card shadow="never" class="search-card">
|
||||
<el-form ref="searchFormRef" :model="searchForm" label-width="110px" class="search-form">
|
||||
<el-row :gutter="24">
|
||||
<el-col :xs="24" :sm="12" :md="12" :lg="8" :xl="8">
|
||||
<el-form-item label="年度">
|
||||
<el-input v-model="searchForm.nd" placeholder="请输入年度" clearable @keyup.enter.native="handleSearch" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="12" :lg="8" :xl="8">
|
||||
<el-form-item label="预警条件编号">
|
||||
<el-input v-model="searchForm.yjtjbh" placeholder="请输入预警条件编号" clearable @keyup.enter.native="handleSearch" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="12" :lg="8" :xl="8">
|
||||
<el-form-item label="学员编号">
|
||||
<el-input v-model="searchForm.xybh" placeholder="请输入学员编号" clearable @keyup.enter.native="handleSearch" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="12" :lg="8" :xl="8">
|
||||
<el-form-item label="发布">
|
||||
<el-select v-model="searchForm.fb" placeholder="请选择发布状态" clearable class="w-full">
|
||||
<el-option label="未发布" :value="0" />
|
||||
<el-option label="已发布" :value="1" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row class="search-actions-row">
|
||||
<el-col :span="24" class="search-actions">
|
||||
<el-button type="primary" icon="el-icon-search" @click="handleSearch">查询</el-button>
|
||||
<el-button icon="el-icon-refresh" @click="handleReset">重置</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<!-- ==================== 2. 列表 ==================== -->
|
||||
<el-card shadow="never" class="table-card">
|
||||
<div class="table-header">
|
||||
<span class="table-title">预警结果列表:</span>
|
||||
</div>
|
||||
<el-table v-loading="loading" :data="tableData" stripe border highlight-current-row>
|
||||
<el-table-column prop="bh" label="编号" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column prop="nd" label="年度" width="90" align="center" />
|
||||
<el-table-column prop="yjtjbh" label="预警条件编号" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column prop="xybh" label="学员编号" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column prop="cjsj" label="创建时间" width="160" show-overflow-tooltip />
|
||||
<el-table-column label="发布" width="90" align="center">
|
||||
<template slot-scope="{ row }">
|
||||
<el-tag :type="Number(row.fb) === 1 ? 'success' : 'info'" size="mini">
|
||||
{{ Number(row.fb) === 1 ? '是' : '否' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="fbsj" label="发布时间" width="160" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="100" align="center" fixed="right">
|
||||
<template slot-scope="{ row }">
|
||||
<el-button type="text" size="small" icon="el-icon-view" @click="handleDetail(row)">详情</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-pagination
|
||||
:current-page="pageNum"
|
||||
:page-size="pageSize"
|
||||
:total="total"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
class="pagination-wrapper"
|
||||
@current-change="handlePageChange"
|
||||
@size-change="handleSizeChange"
|
||||
/>
|
||||
</el-card>
|
||||
|
||||
<!-- ==================== 3. 详情对话框 ==================== -->
|
||||
<el-dialog title="预警结果详情" :visible="detailDialogVisible" width="560px" :close-on-click-modal="false"
|
||||
@update:visible="val => detailDialogVisible = val">
|
||||
<div v-loading="detailLoading" class="detail-body">
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="编号">{{ detailForm.bh || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="年度">{{ detailForm.nd || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="预警条件编号">{{ detailForm.yjtjbh || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="学员编号">{{ detailForm.xybh || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">{{ detailForm.cjsj || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="发布时间">{{ detailForm.fbsj || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="发布">
|
||||
<el-tag :type="Number(detailForm.fb) === 1 ? 'success' : 'info'" size="mini">
|
||||
{{ Number(detailForm.fb) === 1 ? '是' : '否' }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
<div slot="footer">
|
||||
<el-button @click="detailDialogVisible = false">关闭</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
listWarningResult,
|
||||
getWarningResult
|
||||
} from '@/api/studentRecords/warningResult'
|
||||
|
||||
export default {
|
||||
name: 'WarningResult',
|
||||
data() {
|
||||
return {
|
||||
// ==================== 查询条件 ====================
|
||||
searchForm: {
|
||||
nd: '',
|
||||
yjtjbh: '',
|
||||
xybh: '',
|
||||
fb: undefined
|
||||
},
|
||||
|
||||
// ==================== 列表数据 ====================
|
||||
loading: false,
|
||||
tableData: [],
|
||||
total: 0,
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
|
||||
// ==================== 详情 ====================
|
||||
detailDialogVisible: false,
|
||||
detailLoading: false,
|
||||
detailForm: {}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.fetchList()
|
||||
},
|
||||
methods: {
|
||||
// ==================== 查询列表 ====================
|
||||
fetchList() {
|
||||
this.loading = true
|
||||
const params = {
|
||||
pageNum: this.pageNum,
|
||||
pageSize: this.pageSize
|
||||
}
|
||||
// 文本条件非空时才传
|
||||
;['nd', 'yjtjbh', 'xybh'].forEach(key => {
|
||||
const value = this.searchForm[key]
|
||||
if (value !== '' && value !== null && value !== undefined) {
|
||||
params[key] = String(value).trim()
|
||||
}
|
||||
})
|
||||
// 发布:等值筛选(选中时才传)
|
||||
if (this.searchForm.fb !== undefined && this.searchForm.fb !== '' && this.searchForm.fb !== null) {
|
||||
params.fb = this.searchForm.fb
|
||||
}
|
||||
listWarningResult(params).then(response => {
|
||||
const data = response.data || {}
|
||||
this.tableData = data.records || []
|
||||
this.total = data.total || 0
|
||||
this.loading = false
|
||||
}).catch(() => {
|
||||
this.tableData = []
|
||||
this.total = 0
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
|
||||
handleSearch() {
|
||||
this.pageNum = 1
|
||||
this.fetchList()
|
||||
},
|
||||
|
||||
handleReset() {
|
||||
this.$refs.searchFormRef.resetFields()
|
||||
this.pageNum = 1
|
||||
this.fetchList()
|
||||
},
|
||||
|
||||
handlePageChange(current) {
|
||||
this.pageNum = current
|
||||
this.fetchList()
|
||||
},
|
||||
|
||||
handleSizeChange(size) {
|
||||
this.pageSize = size
|
||||
this.pageNum = 1
|
||||
this.fetchList()
|
||||
},
|
||||
|
||||
// ==================== 详情 ====================
|
||||
handleDetail(row) {
|
||||
this.detailDialogVisible = true
|
||||
this.detailLoading = true
|
||||
this.detailForm = {}
|
||||
getWarningResult(row.bh).then(response => {
|
||||
this.detailForm = response.data || {}
|
||||
this.detailLoading = false
|
||||
}).catch(() => {
|
||||
this.detailLoading = false
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.app-container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.warning-result-page {
|
||||
.search-card {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
.w-full {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.search-actions-row {
|
||||
margin-top: 2px;
|
||||
border-top: 1px dashed #ebeef5;
|
||||
padding-top: 14px;
|
||||
}
|
||||
|
||||
.search-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.table-card {
|
||||
.table-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16px;
|
||||
|
||||
.table-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
}
|
||||
|
||||
.pagination-wrapper {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,380 @@
|
||||
<template>
|
||||
<div class="app-container system-config">
|
||||
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
|
||||
<el-form-item label="参数名称" prop="configName">
|
||||
<el-input
|
||||
v-model="queryParams.configName"
|
||||
placeholder="请输入参数名称"
|
||||
clearable
|
||||
style="width: 240px"
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="参数键名" prop="configKey">
|
||||
<el-input
|
||||
v-model="queryParams.configKey"
|
||||
placeholder="请输入参数键名"
|
||||
clearable
|
||||
style="width: 240px"
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="系统内置" prop="configType">
|
||||
<el-select v-model="queryParams.configType" placeholder="系统内置" clearable>
|
||||
<el-option
|
||||
v-for="dict in dict.type.sys_yes_no"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="创建时间">
|
||||
<el-date-picker
|
||||
v-model="dateRange"
|
||||
style="width: 240px"
|
||||
value-format="yyyy-MM-dd"
|
||||
type="daterange"
|
||||
range-separator="-"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
></el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
icon="el-icon-plus"
|
||||
size="mini"
|
||||
@click="handleAdd"
|
||||
v-hasPermi="['system:config:add']"
|
||||
>新增</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
icon="el-icon-edit"
|
||||
size="mini"
|
||||
:disabled="single"
|
||||
@click="handleUpdate"
|
||||
v-hasPermi="['system:config:edit']"
|
||||
>修改</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="danger"
|
||||
plain
|
||||
icon="el-icon-delete"
|
||||
size="mini"
|
||||
:disabled="multiple"
|
||||
@click="handleDelete"
|
||||
v-hasPermi="['system:config:remove']"
|
||||
>删除</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="warning"
|
||||
plain
|
||||
icon="el-icon-download"
|
||||
size="mini"
|
||||
@click="handleExport"
|
||||
v-hasPermi="['system:config:export']"
|
||||
>导出</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="danger"
|
||||
plain
|
||||
icon="el-icon-refresh"
|
||||
size="mini"
|
||||
@click="handleRefreshCache"
|
||||
v-hasPermi="['system:config:remove']"
|
||||
>刷新缓存</el-button>
|
||||
</el-col>
|
||||
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
|
||||
<el-table v-loading="loading" :data="configList" :height="tableHeight" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="参数主键" align="center" prop="configId" />
|
||||
<el-table-column label="参数名称" align="center" prop="configName" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="参数键名" align="center" prop="configKey" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="参数键值" align="center" prop="configValue" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="系统内置" align="center" prop="configType">
|
||||
<template slot-scope="scope">
|
||||
<dict-tag :options="dict.type.sys_yes_no" :value="scope.row.configType"/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ parseTime(scope.row.createTime) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
|
||||
<template slot-scope="scope">
|
||||
<el-button
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-edit"
|
||||
@click="handleUpdate(scope.row)"
|
||||
v-hasPermi="['system:config:edit']"
|
||||
>修改</el-button>
|
||||
<el-button
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-delete"
|
||||
@click="handleDelete(scope.row)"
|
||||
v-hasPermi="['system:config:remove']"
|
||||
>删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination
|
||||
v-show="total>0"
|
||||
:total="total"
|
||||
:page.sync="queryParams.pageNum"
|
||||
:limit.sync="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
|
||||
<!-- 添加或修改参数配置对话框 -->
|
||||
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
|
||||
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
|
||||
<el-form-item label="参数名称" prop="configName">
|
||||
<el-input v-model="form.configName" placeholder="请输入参数名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="参数键名" prop="configKey">
|
||||
<el-input v-model="form.configKey" placeholder="请输入参数键名" />
|
||||
</el-form-item>
|
||||
<el-form-item label="参数键值" prop="configValue">
|
||||
<el-input v-model="form.configValue" type="textarea" placeholder="请输入参数键值" />
|
||||
</el-form-item>
|
||||
<el-form-item label="系统内置" prop="configType">
|
||||
<el-radio-group v-model="form.configType">
|
||||
<el-radio
|
||||
v-for="dict in dict.type.sys_yes_no"
|
||||
:key="dict.value"
|
||||
:label="dict.value"
|
||||
>{{dict.label}}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listConfig, getConfig, delConfig, addConfig, updateConfig, refreshCache } from "@/api/system/config"
|
||||
|
||||
export default {
|
||||
name: "Config",
|
||||
dicts: ['sys_yes_no'],
|
||||
data() {
|
||||
return {
|
||||
// 遮罩层
|
||||
loading: true,
|
||||
// 选中数组
|
||||
ids: [],
|
||||
// 非单个禁用
|
||||
single: true,
|
||||
// 非多个禁用
|
||||
multiple: true,
|
||||
// 显示搜索条件
|
||||
showSearch: true,
|
||||
// 表格高度
|
||||
tableHeight: window.innerHeight - 240,
|
||||
// 总条数
|
||||
total: 0,
|
||||
// 参数表格数据
|
||||
configList: [],
|
||||
// 弹出层标题
|
||||
title: "",
|
||||
// 是否显示弹出层
|
||||
open: false,
|
||||
// 日期范围
|
||||
dateRange: [],
|
||||
// 查询参数
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
configName: undefined,
|
||||
configKey: undefined,
|
||||
configType: undefined
|
||||
},
|
||||
// 表单参数
|
||||
form: {},
|
||||
// 表单校验
|
||||
rules: {
|
||||
configName: [
|
||||
{ required: true, message: "参数名称不能为空", trigger: "blur" }
|
||||
],
|
||||
configKey: [
|
||||
{ required: true, message: "参数键名不能为空", trigger: "blur" }
|
||||
],
|
||||
configValue: [
|
||||
{ required: true, message: "参数键值不能为空", trigger: "blur" }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getList()
|
||||
},
|
||||
mounted() {
|
||||
this.getTableHeight()
|
||||
window.addEventListener('resize', this.getTableHeight)
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.removeEventListener('resize', this.getTableHeight)
|
||||
},
|
||||
watch: {
|
||||
showSearch() {
|
||||
this.$nextTick(() => {
|
||||
this.getTableHeight()
|
||||
})
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getTableHeight() {
|
||||
const offset = this.showSearch ? 240 : 190
|
||||
this.tableHeight = window.innerHeight - offset
|
||||
},
|
||||
/** 查询参数列表 */
|
||||
getList() {
|
||||
this.loading = true
|
||||
listConfig(this.addDateRange(this.queryParams, this.dateRange)).then(response => {
|
||||
this.configList = response.rows
|
||||
this.total = response.total
|
||||
this.loading = false
|
||||
}
|
||||
)
|
||||
},
|
||||
// 取消按钮
|
||||
cancel() {
|
||||
this.open = false
|
||||
this.reset()
|
||||
},
|
||||
// 表单重置
|
||||
reset() {
|
||||
this.form = {
|
||||
configId: undefined,
|
||||
configName: undefined,
|
||||
configKey: undefined,
|
||||
configValue: undefined,
|
||||
configType: "Y",
|
||||
remark: undefined
|
||||
}
|
||||
this.resetForm("form")
|
||||
},
|
||||
/** 搜索按钮操作 */
|
||||
handleQuery() {
|
||||
this.queryParams.pageNum = 1
|
||||
this.getList()
|
||||
},
|
||||
/** 重置按钮操作 */
|
||||
resetQuery() {
|
||||
this.dateRange = []
|
||||
this.resetForm("queryForm")
|
||||
this.handleQuery()
|
||||
},
|
||||
/** 新增按钮操作 */
|
||||
handleAdd() {
|
||||
this.reset()
|
||||
this.open = true
|
||||
this.title = "添加参数"
|
||||
},
|
||||
// 多选框选中数据
|
||||
handleSelectionChange(selection) {
|
||||
this.ids = selection.map(item => item.configId)
|
||||
this.single = selection.length != 1
|
||||
this.multiple = !selection.length
|
||||
},
|
||||
/** 修改按钮操作 */
|
||||
handleUpdate(row) {
|
||||
this.reset()
|
||||
const configId = row.configId || this.ids
|
||||
getConfig(configId).then(response => {
|
||||
this.form = response.data
|
||||
this.open = true
|
||||
this.title = "修改参数"
|
||||
})
|
||||
},
|
||||
/** 提交按钮 */
|
||||
submitForm() {
|
||||
this.$refs["form"].validate(valid => {
|
||||
if (valid) {
|
||||
if (this.form.configId != undefined) {
|
||||
updateConfig(this.form).then(() => {
|
||||
this.$modal.msgSuccess("修改成功")
|
||||
this.open = false
|
||||
this.getList()
|
||||
})
|
||||
} else {
|
||||
addConfig(this.form).then(() => {
|
||||
this.$modal.msgSuccess("新增成功")
|
||||
this.open = false
|
||||
this.getList()
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
/** 删除按钮操作 */
|
||||
handleDelete(row) {
|
||||
const configIds = row.configId || this.ids
|
||||
this.$modal.confirm('是否确认删除参数编号为"' + configIds + '"的数据项?').then(function() {
|
||||
return delConfig(configIds)
|
||||
}).then(() => {
|
||||
this.getList()
|
||||
this.$modal.msgSuccess("删除成功")
|
||||
}).catch(() => {})
|
||||
},
|
||||
/** 导出按钮操作 */
|
||||
handleExport() {
|
||||
this.download('system/config/export', {
|
||||
...this.queryParams
|
||||
}, `config_${new Date().getTime()}.xlsx`)
|
||||
},
|
||||
/** 刷新缓存按钮操作 */
|
||||
handleRefreshCache() {
|
||||
refreshCache().then(() => {
|
||||
this.$modal.msgSuccess("刷新成功")
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.system-config ::v-deep .el-table ::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
.system-config ::v-deep .el-table {
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
.system-config ::v-deep .el-table__body-wrapper::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
.system-config ::v-deep .el-table__body-wrapper {
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,203 @@
|
||||
<template>
|
||||
<el-drawer :title="drawerTitle" :visible.sync="localVisible" direction="rtl" size="700px" append-to-body>
|
||||
<div class="drawer-wrap">
|
||||
<div v-if="loading" class="drawer-loading">
|
||||
<i class="el-icon-loading"></i>
|
||||
<span>加载中...</span>
|
||||
</div>
|
||||
<div v-else-if="!dataList.length" class="drawer-empty">
|
||||
<i class="el-icon-document"></i>
|
||||
<div>暂无字典数据</div>
|
||||
</div>
|
||||
<template v-else>
|
||||
<el-row :gutter="12" class="stat-row">
|
||||
<el-col :span="disabledCount > 0 ? 8 : 12">
|
||||
<div class="stat-card">
|
||||
<div class="stat-num">{{ dataList.length }}</div>
|
||||
<div class="stat-label">共计条目</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="disabledCount > 0 ? 8 : 12">
|
||||
<div class="stat-card">
|
||||
<div class="stat-num success">{{ normalCount }}</div>
|
||||
<div class="stat-label">正常</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="8" v-if="disabledCount > 0">
|
||||
<div class="stat-card">
|
||||
<div class="stat-num danger">{{ disabledCount }}</div>
|
||||
<div class="stat-label">停用</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<div v-for="item in dataList" :key="item.dictCode" class="dict-item">
|
||||
<div class="dict-cell">
|
||||
<div class="dict-cell-key">标签</div>
|
||||
<div class="dict-cell-val">
|
||||
<el-tag v-if="item.listClass && item.listClass !== 'default'" :type="item.listClass === 'primary' ? '' : item.listClass" size="small">{{ item.dictLabel }}</el-tag>
|
||||
<span v-else>{{ item.dictLabel }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dict-cell">
|
||||
<div class="dict-cell-key">键值</div>
|
||||
<div class="dict-cell-val">{{ item.dictValue }}</div>
|
||||
</div>
|
||||
<div class="dict-cell">
|
||||
<div class="dict-cell-key">状态</div>
|
||||
<div class="dict-cell-val">
|
||||
<el-tag :type="item.status === '0' ? 'success' : 'danger'" size="small">
|
||||
{{ item.status === '0' ? '正常' : '停用' }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listData } from '@/api/system/dict/data'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
visible: { type: Boolean, default: false },
|
||||
row: { type: Object, default: () => ({}) }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
localVisible: false,
|
||||
loading: false,
|
||||
dataList: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
drawerTitle() {
|
||||
return this.row.dictName + ' ' + (this.row.dictType || '')
|
||||
},
|
||||
normalCount() {
|
||||
return this.dataList.filter(r => r.status === '0').length
|
||||
},
|
||||
disabledCount() {
|
||||
return this.dataList.filter(r => r.status !== '0').length
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
visible(val) {
|
||||
this.localVisible = val
|
||||
},
|
||||
localVisible(val) {
|
||||
this.$emit('update:visible', val)
|
||||
if (val) {
|
||||
this.loadData()
|
||||
} else {
|
||||
this.dataList = []
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
loadData() {
|
||||
if (!this.row || !this.row.dictType) return
|
||||
this.loading = true
|
||||
this.dataList = []
|
||||
listData({ dictType: this.row.dictType, pageSize: 100, pageNum: 1 }).then(response => {
|
||||
this.dataList = response.rows || []
|
||||
}).catch(() => {}).finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.drawer-wrap {
|
||||
padding: 0 20px 20px;
|
||||
}
|
||||
|
||||
.drawer-loading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 120px;
|
||||
color: #aaa;
|
||||
font-size: 13px;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.drawer-empty {
|
||||
text-align: center;
|
||||
color: #bbb;
|
||||
padding: 60px 0;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.drawer-empty i {
|
||||
font-size: 36px;
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.stat-row {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background: #f7f9fb;
|
||||
border: 1px solid #e8ecf0;
|
||||
border-radius: 6px;
|
||||
padding: 10px 14px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stat-num {
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.stat-num.success { color: #27ae60; }
|
||||
.stat-num.danger { color: #e74c3c; }
|
||||
|
||||
.stat-label {
|
||||
font-size: 11px;
|
||||
color: #95a5a6;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.dict-item {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
border: 1px solid #e8ecf0;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.dict-cell {
|
||||
display: grid;
|
||||
grid-template-columns: 70px 1fr;
|
||||
border-right: 1px solid #f0f4f8;
|
||||
}
|
||||
|
||||
.dict-cell:last-child {
|
||||
border-right: 0;
|
||||
}
|
||||
|
||||
.dict-cell-key {
|
||||
padding: 9px 14px;
|
||||
font-size: 12px;
|
||||
color: #888;
|
||||
background: #f7f9fb;
|
||||
border-right: 1px solid #f0f4f8;
|
||||
}
|
||||
|
||||
.dict-cell-val {
|
||||
padding: 9px 14px;
|
||||
font-size: 13px;
|
||||
color: #2c3e50;
|
||||
word-break: break-all;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,109 @@
|
||||
<template>
|
||||
<el-dialog :title="`「${noticeTitle}」已读用户`" :visible.sync="visible" width="760px" top="6vh" append-to-body @close="handleClose">
|
||||
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" style="margin-bottom: 4px;">
|
||||
<el-form-item prop="searchValue">
|
||||
<el-input
|
||||
v-model="queryParams.searchValue"
|
||||
placeholder="登录名称 / 用户名称"
|
||||
clearable
|
||||
prefix-icon="el-icon-search"
|
||||
style="width: 220px;"
|
||||
@keyup.enter.native="handleQuery"
|
||||
@clear="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item style="float: right; margin-right: 0;">
|
||||
<span class="read-stat">
|
||||
共 <strong>{{ total }}</strong> 人已读
|
||||
</span>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-table v-loading="loading" :data="userList" size="small" stripe height="340px">
|
||||
<el-table-column type="index" label="序号" width="55" align="center" />
|
||||
<el-table-column label="登录名称" prop="userName" align="center" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="用户名称" prop="nickName" align="center" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="所属部门" prop="deptName" align="center" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="手机号码" prop="phonenumber" align="center" width="120" />
|
||||
<el-table-column label="阅读时间" prop="readTime" align="center" width="160">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ parseTime(scope.row.readTime) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList" style="padding: 6px 0px;"/>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listNoticeReadUsers } from "@/api/system/notice"
|
||||
|
||||
export default {
|
||||
name: "ReadUsers",
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
loading: false,
|
||||
noticeId: undefined,
|
||||
noticeTitle: "",
|
||||
total: 0,
|
||||
userList: [],
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
noticeId: undefined,
|
||||
searchValue: undefined
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
open(row) {
|
||||
this.noticeId = row.noticeId
|
||||
this.noticeTitle = row.noticeTitle
|
||||
this.queryParams.noticeId = row.noticeId
|
||||
this.queryParams.searchValue = undefined
|
||||
this.queryParams.pageNum = 1
|
||||
this.visible = true
|
||||
this.getList()
|
||||
},
|
||||
getList() {
|
||||
this.loading = true
|
||||
listNoticeReadUsers(this.queryParams).then(res => {
|
||||
this.userList = res.rows
|
||||
this.total = res.total
|
||||
}).finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
handleQuery() {
|
||||
this.queryParams.pageNum = 1
|
||||
this.getList()
|
||||
},
|
||||
resetQuery() {
|
||||
this.resetForm("queryForm")
|
||||
this.handleQuery()
|
||||
},
|
||||
handleClose() {
|
||||
this.userList = []
|
||||
this.total = 0
|
||||
this.queryParams.searchValue = undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.read-stat {
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
line-height: 28px;
|
||||
}
|
||||
.read-stat strong {
|
||||
color: #00875A;
|
||||
font-size: 15px;
|
||||
margin: 0 2px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,369 @@
|
||||
<template>
|
||||
<div class="app-container system-notice">
|
||||
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
|
||||
<el-form-item label="公告标题" prop="noticeTitle">
|
||||
<el-input
|
||||
v-model="queryParams.noticeTitle"
|
||||
placeholder="请输入公告标题"
|
||||
clearable
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="操作人员" prop="createBy">
|
||||
<el-input
|
||||
v-model="queryParams.createBy"
|
||||
placeholder="请输入操作人员"
|
||||
clearable
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="类型" prop="noticeType">
|
||||
<el-select v-model="queryParams.noticeType" placeholder="公告类型" clearable>
|
||||
<el-option
|
||||
v-for="dict in dict.type.sys_notice_type"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
icon="el-icon-plus"
|
||||
size="mini"
|
||||
@click="handleAdd"
|
||||
v-hasPermi="['system:notice:add']"
|
||||
>新增</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
icon="el-icon-edit"
|
||||
size="mini"
|
||||
:disabled="single"
|
||||
@click="handleUpdate"
|
||||
v-hasPermi="['system:notice:edit']"
|
||||
>修改</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="danger"
|
||||
plain
|
||||
icon="el-icon-delete"
|
||||
size="mini"
|
||||
:disabled="multiple"
|
||||
@click="handleDelete"
|
||||
v-hasPermi="['system:notice:remove']"
|
||||
>删除</el-button>
|
||||
</el-col>
|
||||
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
|
||||
<el-table v-loading="loading" :data="noticeList" @selection-change="handleSelectionChange" :height="tableHeight">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="序号" align="center" prop="noticeId" width="100" />
|
||||
<el-table-column label="公告标题" align="center" :show-overflow-tooltip="true">
|
||||
<template slot-scope="scope">
|
||||
<a class="link-type" style="cursor:pointer" @click="handleViewData(scope.row)">{{ scope.row.noticeTitle }}</a>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="公告类型" align="center" prop="noticeType" width="100">
|
||||
<template slot-scope="scope">
|
||||
<dict-tag :options="dict.type.sys_notice_type" :value="scope.row.noticeType"/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" align="center" prop="status" width="100">
|
||||
<template slot-scope="scope">
|
||||
<dict-tag :options="dict.type.sys_notice_status" :value="scope.row.status"/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建者" align="center" prop="createBy" width="100" />
|
||||
<el-table-column label="创建时间" align="center" prop="createTime" width="100">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d}') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
|
||||
<template slot-scope="scope">
|
||||
<el-button
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-user"
|
||||
@click="handleReadUsers(scope.row)"
|
||||
v-hasPermi="['system:notice:list']"
|
||||
>阅读用户</el-button>
|
||||
<el-button
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-edit"
|
||||
@click="handleUpdate(scope.row)"
|
||||
v-hasPermi="['system:notice:edit']"
|
||||
>修改</el-button>
|
||||
<el-button
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-delete"
|
||||
@click="handleDelete(scope.row)"
|
||||
v-hasPermi="['system:notice:remove']"
|
||||
>删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination
|
||||
v-show="total>0"
|
||||
:total="total"
|
||||
:page.sync="queryParams.pageNum"
|
||||
:limit.sync="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
|
||||
<!-- 添加或修改公告对话框 -->
|
||||
<el-dialog :title="title" :visible.sync="open" width="780px" append-to-body>
|
||||
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="公告标题" prop="noticeTitle">
|
||||
<el-input v-model="form.noticeTitle" placeholder="请输入公告标题" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="公告类型" prop="noticeType">
|
||||
<el-select v-model="form.noticeType" placeholder="请选择公告类型">
|
||||
<el-option
|
||||
v-for="dict in dict.type.sys_notice_type"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="状态">
|
||||
<el-radio-group v-model="form.status">
|
||||
<el-radio
|
||||
v-for="dict in dict.type.sys_notice_status"
|
||||
:key="dict.value"
|
||||
:label="dict.value"
|
||||
>{{dict.label}}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="内容">
|
||||
<editor v-model="form.noticeContent" :min-height="192"/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<notice-detail-view ref="noticeViewRef" />
|
||||
<read-users-dialog ref="readUsersRef" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import NoticeDetailView from "@/layout/components/HeaderNotice/DetailView"
|
||||
import ReadUsersDialog from "./ReadUsers"
|
||||
import { listNotice, getNotice, delNotice, addNotice, updateNotice } from "@/api/system/notice"
|
||||
|
||||
export default {
|
||||
name: "Notice",
|
||||
components: { NoticeDetailView, ReadUsersDialog },
|
||||
dicts: ['sys_notice_status', 'sys_notice_type'],
|
||||
data() {
|
||||
return {
|
||||
// 遮罩层
|
||||
loading: true,
|
||||
// 表格高度
|
||||
tableHeight: window.innerHeight - 240,
|
||||
// 选中数组
|
||||
ids: [],
|
||||
// 非单个禁用
|
||||
single: true,
|
||||
// 非多个禁用
|
||||
multiple: true,
|
||||
// 显示搜索条件
|
||||
showSearch: true,
|
||||
// 总条数
|
||||
total: 0,
|
||||
// 公告表格数据
|
||||
noticeList: [],
|
||||
// 弹出层标题
|
||||
title: "",
|
||||
// 是否显示弹出层
|
||||
open: false,
|
||||
// 查询参数
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
noticeTitle: undefined,
|
||||
createBy: undefined,
|
||||
status: undefined
|
||||
},
|
||||
// 表单参数
|
||||
form: {},
|
||||
// 表单校验
|
||||
rules: {
|
||||
noticeTitle: [
|
||||
{ required: true, message: "公告标题不能为空", trigger: "blur" }
|
||||
],
|
||||
noticeType: [
|
||||
{ required: true, message: "公告类型不能为空", trigger: "change" }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getList()
|
||||
},
|
||||
mounted() {
|
||||
this.getTableHeight()
|
||||
window.addEventListener('resize', this.getTableHeight)
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.removeEventListener('resize', this.getTableHeight)
|
||||
},
|
||||
watch: {
|
||||
showSearch() {
|
||||
this.$nextTick(() => {
|
||||
this.getTableHeight()
|
||||
})
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/** 获取表格高度 */
|
||||
getTableHeight() {
|
||||
const offset = this.showSearch ? 240 : 190
|
||||
this.tableHeight = window.innerHeight - offset
|
||||
},
|
||||
/** 查询公告列表 */
|
||||
getList() {
|
||||
this.loading = true
|
||||
listNotice(this.queryParams).then(response => {
|
||||
this.noticeList = response.rows
|
||||
this.total = response.total
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
// 取消按钮
|
||||
cancel() {
|
||||
this.open = false
|
||||
this.reset()
|
||||
},
|
||||
// 表单重置
|
||||
reset() {
|
||||
this.form = {
|
||||
noticeId: undefined,
|
||||
noticeTitle: undefined,
|
||||
noticeType: undefined,
|
||||
noticeContent: undefined,
|
||||
status: "0"
|
||||
}
|
||||
this.resetForm("form")
|
||||
},
|
||||
/** 搜索按钮操作 */
|
||||
handleQuery() {
|
||||
this.queryParams.pageNum = 1
|
||||
this.getList()
|
||||
},
|
||||
/** 重置按钮操作 */
|
||||
resetQuery() {
|
||||
this.resetForm("queryForm")
|
||||
this.handleQuery()
|
||||
},
|
||||
// 多选框选中数据
|
||||
handleSelectionChange(selection) {
|
||||
this.ids = selection.map(item => item.noticeId)
|
||||
this.single = selection.length != 1
|
||||
this.multiple = !selection.length
|
||||
},
|
||||
/** 新增按钮操作 */
|
||||
handleAdd() {
|
||||
this.reset()
|
||||
this.open = true
|
||||
this.title = "添加公告"
|
||||
},
|
||||
/** 修改按钮操作 */
|
||||
handleUpdate(row) {
|
||||
this.reset()
|
||||
const noticeId = row.noticeId || this.ids
|
||||
getNotice(noticeId).then(response => {
|
||||
this.form = response.data
|
||||
this.open = true
|
||||
this.title = "修改公告"
|
||||
})
|
||||
},
|
||||
/** 提交按钮 */
|
||||
submitForm() {
|
||||
this.$refs["form"].validate(valid => {
|
||||
if (valid) {
|
||||
if (this.form.noticeId != undefined) {
|
||||
updateNotice(this.form).then(() => {
|
||||
this.$modal.msgSuccess("修改成功")
|
||||
this.open = false
|
||||
this.getList()
|
||||
})
|
||||
} else {
|
||||
addNotice(this.form).then(() => {
|
||||
this.$modal.msgSuccess("新增成功")
|
||||
this.open = false
|
||||
this.getList()
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
/** 查看公告详情 */
|
||||
handleViewData(row) {
|
||||
this.$refs.noticeViewRef.open(row)
|
||||
},
|
||||
/** 查看已读用户 */
|
||||
handleReadUsers(row) {
|
||||
this.$refs.readUsersRef.open(row)
|
||||
},
|
||||
/** 删除按钮操作 */
|
||||
handleDelete(row) {
|
||||
const noticeIds = row.noticeId || this.ids
|
||||
this.$modal.confirm('是否确认删除公告编号为"' + noticeIds + '"的数据项?').then(function() {
|
||||
return delNotice(noticeIds)
|
||||
}).then(() => {
|
||||
this.getList()
|
||||
this.$modal.msgSuccess("删除成功")
|
||||
}).catch(() => {})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.system-notice ::v-deep .el-table ::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
.system-notice ::v-deep .el-table {
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
.system-notice ::v-deep .el-table__body-wrapper::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
.system-notice ::v-deep .el-table__body-wrapper {
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,234 @@
|
||||
<template>
|
||||
<div class="app-container system-role-auth-user">
|
||||
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch">
|
||||
<el-form-item label="用户名称" prop="userName">
|
||||
<el-input
|
||||
v-model="queryParams.userName"
|
||||
placeholder="请输入用户名称"
|
||||
clearable
|
||||
style="width: 240px"
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="手机号码" prop="phonenumber">
|
||||
<el-input
|
||||
v-model="queryParams.phonenumber"
|
||||
placeholder="请输入手机号码"
|
||||
clearable
|
||||
style="width: 240px"
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
icon="el-icon-plus"
|
||||
size="mini"
|
||||
@click="openSelectUser"
|
||||
v-hasPermi="['system:role:add']"
|
||||
>添加用户</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="danger"
|
||||
plain
|
||||
icon="el-icon-circle-close"
|
||||
size="mini"
|
||||
:disabled="multiple"
|
||||
@click="cancelAuthUserAll"
|
||||
v-hasPermi="['system:role:remove']"
|
||||
>批量取消授权</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
icon="el-icon-close"
|
||||
size="mini"
|
||||
@click="handleClose"
|
||||
>关闭</el-button>
|
||||
</el-col>
|
||||
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
|
||||
<el-table v-loading="loading" :data="userList" @selection-change="handleSelectionChange" :height="tableHeight">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="用户名称" prop="userName" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="用户昵称" prop="nickName" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="邮箱" prop="email" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="手机" prop="phonenumber" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="状态" align="center" prop="status">
|
||||
<template slot-scope="scope">
|
||||
<dict-tag :options="dict.type.sys_normal_disable" :value="scope.row.status"/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ parseTime(scope.row.createTime) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
|
||||
<template slot-scope="scope">
|
||||
<el-button
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-circle-close"
|
||||
@click="cancelAuthUser(scope.row)"
|
||||
v-hasPermi="['system:role:remove']"
|
||||
>取消授权</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination
|
||||
v-show="total>0"
|
||||
:total="total"
|
||||
:page.sync="queryParams.pageNum"
|
||||
:limit.sync="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
<select-user ref="select" :roleId="queryParams.roleId" @ok="handleQuery" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { allocatedUserList, authUserCancel, authUserCancelAll } from "@/api/system/role"
|
||||
import selectUser from "./selectUser"
|
||||
|
||||
export default {
|
||||
name: "AuthUser",
|
||||
dicts: ['sys_normal_disable'],
|
||||
components: { selectUser },
|
||||
data() {
|
||||
return {
|
||||
// 遮罩层
|
||||
loading: true,
|
||||
// 表格高度
|
||||
tableHeight: window.innerHeight - 240,
|
||||
// 选中用户组
|
||||
userIds: [],
|
||||
// 非多个禁用
|
||||
multiple: true,
|
||||
// 显示搜索条件
|
||||
showSearch: true,
|
||||
// 总条数
|
||||
total: 0,
|
||||
// 用户表格数据
|
||||
userList: [],
|
||||
// 查询参数
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
roleId: undefined,
|
||||
userName: undefined,
|
||||
phonenumber: undefined
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
const roleId = this.$route.params && this.$route.params.roleId
|
||||
if (roleId) {
|
||||
this.queryParams.roleId = roleId
|
||||
this.getList()
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.getTableHeight()
|
||||
window.addEventListener('resize', this.getTableHeight)
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.removeEventListener('resize', this.getTableHeight)
|
||||
},
|
||||
watch: {
|
||||
showSearch() {
|
||||
this.$nextTick(() => {
|
||||
this.getTableHeight()
|
||||
})
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/** 获取表格高度 */
|
||||
getTableHeight() {
|
||||
const offset = this.showSearch ? 240 : 190
|
||||
this.tableHeight = window.innerHeight - offset
|
||||
},
|
||||
/** 查询授权用户列表 */
|
||||
getList() {
|
||||
this.loading = true
|
||||
allocatedUserList(this.queryParams).then(response => {
|
||||
this.userList = response.rows
|
||||
this.total = response.total
|
||||
this.loading = false
|
||||
}
|
||||
)
|
||||
},
|
||||
// 返回按钮
|
||||
handleClose() {
|
||||
const obj = { path: "/system/role" }
|
||||
this.$tab.closeOpenPage(obj)
|
||||
},
|
||||
/** 搜索按钮操作 */
|
||||
handleQuery() {
|
||||
this.queryParams.pageNum = 1
|
||||
this.getList()
|
||||
},
|
||||
/** 重置按钮操作 */
|
||||
resetQuery() {
|
||||
this.resetForm("queryForm")
|
||||
this.handleQuery()
|
||||
},
|
||||
// 多选框选中数据
|
||||
handleSelectionChange(selection) {
|
||||
this.userIds = selection.map(item => item.userId)
|
||||
this.multiple = !selection.length
|
||||
},
|
||||
/** 打开授权用户表弹窗 */
|
||||
openSelectUser() {
|
||||
this.$refs.select.show()
|
||||
},
|
||||
/** 取消授权按钮操作 */
|
||||
cancelAuthUser(row) {
|
||||
const roleId = this.queryParams.roleId
|
||||
this.$modal.confirm('确认要取消该用户"' + row.userName + '"角色吗?').then(function() {
|
||||
return authUserCancel({ userId: row.userId, roleId: roleId })
|
||||
}).then(() => {
|
||||
this.getList()
|
||||
this.$modal.msgSuccess("取消授权成功")
|
||||
}).catch(() => {})
|
||||
},
|
||||
/** 批量取消授权按钮操作 */
|
||||
cancelAuthUserAll() {
|
||||
const roleId = this.queryParams.roleId
|
||||
const userIds = this.userIds.join(",")
|
||||
this.$modal.confirm('是否取消选中用户授权数据项?').then(function() {
|
||||
return authUserCancelAll({ roleId: roleId, userIds: userIds })
|
||||
}).then(() => {
|
||||
this.getList()
|
||||
this.$modal.msgSuccess("取消授权成功")
|
||||
}).catch(() => {})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.system-role-auth-user ::v-deep .el-table ::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
.system-role-auth-user ::v-deep .el-table {
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
.system-role-auth-user ::v-deep .el-table__body-wrapper::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
.system-role-auth-user ::v-deep .el-table__body-wrapper {
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,136 @@
|
||||
<template>
|
||||
<!-- 授权用户 -->
|
||||
<el-dialog title="选择用户" :visible.sync="visible" width="800px" top="5vh" append-to-body>
|
||||
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true">
|
||||
<el-form-item label="用户名称" prop="userName">
|
||||
<el-input
|
||||
v-model="queryParams.userName"
|
||||
placeholder="请输入用户名称"
|
||||
clearable
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="手机号码" prop="phonenumber">
|
||||
<el-input
|
||||
v-model="queryParams.phonenumber"
|
||||
placeholder="请输入手机号码"
|
||||
clearable
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-row>
|
||||
<el-table @row-click="clickRow" ref="table" :data="userList" @selection-change="handleSelectionChange" height="260px">
|
||||
<el-table-column type="selection" width="55"></el-table-column>
|
||||
<el-table-column label="用户名称" prop="userName" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="用户昵称" prop="nickName" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="邮箱" prop="email" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="手机" prop="phonenumber" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="状态" align="center" prop="status">
|
||||
<template slot-scope="scope">
|
||||
<dict-tag :options="dict.type.sys_normal_disable" :value="scope.row.status"/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ parseTime(scope.row.createTime) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<pagination
|
||||
v-show="total>0"
|
||||
:total="total"
|
||||
:page.sync="queryParams.pageNum"
|
||||
:limit.sync="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</el-row>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button type="primary" @click="handleSelectUser">确 定</el-button>
|
||||
<el-button @click="visible = false">取 消</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { unallocatedUserList, authUserSelectAll } from "@/api/system/role"
|
||||
export default {
|
||||
dicts: ['sys_normal_disable'],
|
||||
props: {
|
||||
// 角色编号
|
||||
roleId: {
|
||||
type: [Number, String]
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 遮罩层
|
||||
visible: false,
|
||||
// 选中数组值
|
||||
userIds: [],
|
||||
// 总条数
|
||||
total: 0,
|
||||
// 未授权用户数据
|
||||
userList: [],
|
||||
// 查询参数
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
roleId: undefined,
|
||||
userName: undefined,
|
||||
phonenumber: undefined
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 显示弹框
|
||||
show() {
|
||||
this.queryParams.roleId = this.roleId
|
||||
this.getList()
|
||||
this.visible = true
|
||||
},
|
||||
clickRow(row) {
|
||||
this.$refs.table.toggleRowSelection(row)
|
||||
},
|
||||
// 多选框选中数据
|
||||
handleSelectionChange(selection) {
|
||||
this.userIds = selection.map(item => item.userId)
|
||||
},
|
||||
// 查询表数据
|
||||
getList() {
|
||||
unallocatedUserList(this.queryParams).then(res => {
|
||||
this.userList = res.rows
|
||||
this.total = res.total
|
||||
})
|
||||
},
|
||||
/** 搜索按钮操作 */
|
||||
handleQuery() {
|
||||
this.queryParams.pageNum = 1
|
||||
this.getList()
|
||||
},
|
||||
/** 重置按钮操作 */
|
||||
resetQuery() {
|
||||
this.resetForm("queryForm")
|
||||
this.handleQuery()
|
||||
},
|
||||
/** 选择授权用户操作 */
|
||||
handleSelectUser() {
|
||||
const roleId = this.queryParams.roleId
|
||||
const userIds = this.userIds.join(",")
|
||||
if (userIds == "") {
|
||||
this.$modal.msgError("请选择要分配的用户")
|
||||
return
|
||||
}
|
||||
authUserSelectAll({ roleId: roleId, userIds: userIds }).then(res => {
|
||||
this.$modal.msgSuccess(res.msg)
|
||||
this.visible = false
|
||||
this.$emit("ok")
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,152 @@
|
||||
<template>
|
||||
<div class="app-container system-user-auth-role">
|
||||
<h4 class="form-header h4">基本信息</h4>
|
||||
<el-form ref="form" :model="form" label-width="80px">
|
||||
<el-row>
|
||||
<el-col :span="8" :offset="2">
|
||||
<el-form-item label="用户昵称" prop="nickName">
|
||||
<el-input v-model="form.nickName" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8" :offset="2">
|
||||
<el-form-item label="登录账号" prop="userName">
|
||||
<el-input v-model="form.userName" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
|
||||
<h4 class="form-header h4">角色信息</h4>
|
||||
<el-table v-loading="loading" :row-key="getRowKey" @row-click="clickRow" ref="table" @selection-change="handleSelectionChange" :data="roles.slice((pageNum-1)*pageSize,pageNum*pageSize)" :height="tableHeight">
|
||||
<el-table-column label="序号" type="index" align="center">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ (pageNum - 1) * pageSize + scope.$index + 1 }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column type="selection" :reserve-selection="true" :selectable="checkSelectable" width="55" />
|
||||
<el-table-column label="角色编号" align="center" prop="roleId" />
|
||||
<el-table-column label="角色名称" align="center" prop="roleName" />
|
||||
<el-table-column label="权限字符" align="center" prop="roleKey" />
|
||||
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ parseTime(scope.row.createTime) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination v-show="total>0" :total="total" :page.sync="pageNum" :limit.sync="pageSize" />
|
||||
|
||||
<el-form label-width="100px">
|
||||
<el-form-item style="text-align: center;margin-left:-120px;margin-top:30px;">
|
||||
<el-button type="primary" @click="submitForm()">提交</el-button>
|
||||
<el-button @click="close()">返回</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getAuthRole, updateAuthRole } from "@/api/system/user"
|
||||
|
||||
export default {
|
||||
name: "AuthRole",
|
||||
data() {
|
||||
return {
|
||||
// 遮罩层
|
||||
loading: true,
|
||||
// 表格高度
|
||||
tableHeight: window.innerHeight - 240,
|
||||
// 分页信息
|
||||
total: 0,
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
// 选中角色编号
|
||||
roleIds: [],
|
||||
// 角色信息
|
||||
roles: [],
|
||||
// 用户信息
|
||||
form: {}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
const userId = this.$route.params && this.$route.params.userId
|
||||
if (userId) {
|
||||
this.loading = true
|
||||
getAuthRole(userId).then((response) => {
|
||||
this.form = response.user
|
||||
this.roles = response.roles
|
||||
this.total = this.roles.length
|
||||
this.$nextTick(() => {
|
||||
this.roles.forEach((row) => {
|
||||
if (row.flag) {
|
||||
this.$refs.table.toggleRowSelection(row)
|
||||
}
|
||||
})
|
||||
})
|
||||
this.loading = false
|
||||
})
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.getTableHeight()
|
||||
window.addEventListener('resize', this.getTableHeight)
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.removeEventListener('resize', this.getTableHeight)
|
||||
},
|
||||
methods: {
|
||||
/** 获取表格高度 */
|
||||
getTableHeight() {
|
||||
this.tableHeight = window.innerHeight - 240
|
||||
},
|
||||
/** 单击选中行数据 */
|
||||
clickRow(row) {
|
||||
if (this.checkSelectable(row)) {
|
||||
this.$refs.table.toggleRowSelection(row)
|
||||
}
|
||||
},
|
||||
// 多选框选中数据
|
||||
handleSelectionChange(selection) {
|
||||
this.roleIds = selection.map((item) => item.roleId)
|
||||
},
|
||||
// 保存选中的数据编号
|
||||
getRowKey(row) {
|
||||
return row.roleId
|
||||
},
|
||||
// 检查角色状态
|
||||
checkSelectable(row) {
|
||||
return row.status === "0" ? true : false
|
||||
},
|
||||
/** 提交按钮 */
|
||||
submitForm() {
|
||||
const userId = this.form.userId
|
||||
const roleIds = this.roleIds.join(",")
|
||||
updateAuthRole({ userId: userId, roleIds: roleIds }).then(() => {
|
||||
this.$modal.msgSuccess("授权成功")
|
||||
this.close()
|
||||
})
|
||||
},
|
||||
/** 关闭按钮 */
|
||||
close() {
|
||||
const obj = { path: "/system/user" }
|
||||
this.$tab.closeOpenPage(obj)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.system-user-auth-role ::v-deep .el-table ::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
.system-user-auth-role ::v-deep .el-table {
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
.system-user-auth-role ::v-deep .el-table__body-wrapper::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
.system-user-auth-role ::v-deep .el-table__body-wrapper {
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,109 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6" :xs="24">
|
||||
<el-card class="box-card">
|
||||
<div slot="header" class="clearfix">
|
||||
<span>个人信息</span>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-center">
|
||||
<userAvatar />
|
||||
</div>
|
||||
<ul class="list-group list-group-striped">
|
||||
<li class="list-group-item">
|
||||
<svg-icon icon-class="user" />用户名称
|
||||
<div class="pull-right">{{ user.userName }}</div>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<svg-icon icon-class="phone" />手机号码
|
||||
<div class="pull-right">{{ user.phonenumber }}</div>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<svg-icon icon-class="email" />用户邮箱
|
||||
<div class="pull-right">{{ user.email }}</div>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<svg-icon icon-class="tree" />所属部门
|
||||
<div class="pull-right" v-if="user.dept">{{ user.dept.deptName }} / {{ postGroup }}</div>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<svg-icon icon-class="peoples" />所属角色
|
||||
<div class="pull-right">{{ roleGroup }}</div>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<svg-icon icon-class="date" />创建日期
|
||||
<div class="pull-right">{{ user.createTime }}</div>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<svg-icon icon-class="login" />上次登录时间
|
||||
<div class="pull-right">{{ loginStats.LAST_LOGIN_TIME || '首次登录' }}</div>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<svg-icon icon-class="online" />登录次数
|
||||
<div class="pull-right">{{ loginStats.LOGIN_COUNT || 0 }}次</div>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<svg-icon icon-class="ip" />最后登录IP
|
||||
<div class="pull-right">{{ loginStats.LAST_LOGIN_IP || '-' }}</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="18" :xs="24">
|
||||
<el-card>
|
||||
<div slot="header" class="clearfix">
|
||||
<span>基本资料</span>
|
||||
</div>
|
||||
<el-tabs v-model="selectedTab">
|
||||
<el-tab-pane label="基本资料" name="userinfo">
|
||||
<userInfo :user="user" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="修改密码" name="resetPwd">
|
||||
<resetPwd />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import userAvatar from "./userAvatar"
|
||||
import userInfo from "./userInfo"
|
||||
import resetPwd from "./resetPwd"
|
||||
import { getUserProfile } from "@/api/system/user"
|
||||
|
||||
export default {
|
||||
name: "Profile",
|
||||
components: { userAvatar, userInfo, resetPwd },
|
||||
data() {
|
||||
return {
|
||||
user: {},
|
||||
roleGroup: {},
|
||||
postGroup: {},
|
||||
loginStats: {},
|
||||
selectedTab: "userinfo"
|
||||
}
|
||||
},
|
||||
created() {
|
||||
const activeTab = this.$route.params && this.$route.params.activeTab
|
||||
if (activeTab) {
|
||||
this.selectedTab = activeTab
|
||||
}
|
||||
this.getUser()
|
||||
},
|
||||
methods: {
|
||||
getUser() {
|
||||
getUserProfile().then(response => {
|
||||
this.user = response.data
|
||||
this.roleGroup = response.roleGroup
|
||||
this.postGroup = response.postGroup
|
||||
this.loginStats = response.loginStats || {}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,70 @@
|
||||
<template>
|
||||
<el-form ref="form" :model="user" :rules="formRules" label-width="80px">
|
||||
<el-form-item label="旧密码" prop="oldPassword">
|
||||
<el-input v-model="user.oldPassword" placeholder="请输入旧密码" type="password" show-password/>
|
||||
</el-form-item>
|
||||
<el-form-item label="新密码" prop="newPassword" :rules="infoPwdValidator">
|
||||
<el-input v-model="user.newPassword" placeholder="请输入新密码" type="password" show-password/>
|
||||
</el-form-item>
|
||||
<el-form-item label="确认密码" prop="confirmPassword">
|
||||
<el-input v-model="user.confirmPassword" placeholder="请确认新密码" type="password" show-password/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" size="mini" @click="submit">保存</el-button>
|
||||
<el-button size="mini" @click="close">关闭</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { updateUserPwd } from "@/api/system/user"
|
||||
import passwordRule from "@/utils/passwordRule"
|
||||
|
||||
export default {
|
||||
mixins: [passwordRule],
|
||||
data() {
|
||||
return {
|
||||
user: {
|
||||
oldPassword: undefined,
|
||||
newPassword: undefined,
|
||||
confirmPassword: undefined
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
formRules() {
|
||||
return {
|
||||
oldPassword: [
|
||||
{ required: true, message: "旧密码不能为空", trigger: "blur" }
|
||||
],
|
||||
confirmPassword: [
|
||||
{ required: true, message: "确认密码不能为空", trigger: "blur" },
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (this.user.newPassword !== value) {
|
||||
callback(new Error("两次输入的密码不一致"))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}, trigger: "blur"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
submit() {
|
||||
this.$refs["form"].validate(valid => {
|
||||
if (valid) {
|
||||
updateUserPwd(this.user.oldPassword, this.user.newPassword).then(() => {
|
||||
this.$modal.msgSuccess("修改成功")
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
close() {
|
||||
this.$tab.closePage()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,184 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="user-info-head" @click="editCropper()"><img v-bind:src="options.img" title="点击上传头像" class="img-circle img-lg" /></div>
|
||||
<el-dialog :title="title" :visible.sync="open" width="800px" append-to-body @opened="modalOpened" @close="closeDialog">
|
||||
<el-row>
|
||||
<el-col :xs="24" :md="12" :style="{height: '350px'}">
|
||||
<vue-cropper
|
||||
ref="cropper"
|
||||
:img="options.img"
|
||||
:info="true"
|
||||
:autoCrop="options.autoCrop"
|
||||
:autoCropWidth="options.autoCropWidth"
|
||||
:autoCropHeight="options.autoCropHeight"
|
||||
:fixedBox="options.fixedBox"
|
||||
:outputType="options.outputType"
|
||||
@realTime="realTime"
|
||||
v-if="visible"
|
||||
/>
|
||||
</el-col>
|
||||
<el-col :xs="24" :md="12" :style="{height: '350px'}">
|
||||
<div class="avatar-upload-preview">
|
||||
<img :src="previews.url" :style="previews.img" />
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<br />
|
||||
<el-row>
|
||||
<el-col :lg="2" :sm="3" :xs="3">
|
||||
<el-upload action="#" :http-request="requestUpload" :show-file-list="false" :before-upload="beforeUpload">
|
||||
<el-button size="small">
|
||||
选择
|
||||
<i class="el-icon-upload el-icon--right"></i>
|
||||
</el-button>
|
||||
</el-upload>
|
||||
</el-col>
|
||||
<el-col :lg="{span: 1, offset: 2}" :sm="2" :xs="2">
|
||||
<el-button icon="el-icon-plus" size="small" @click="changeScale(1)"></el-button>
|
||||
</el-col>
|
||||
<el-col :lg="{span: 1, offset: 1}" :sm="2" :xs="2">
|
||||
<el-button icon="el-icon-minus" size="small" @click="changeScale(-1)"></el-button>
|
||||
</el-col>
|
||||
<el-col :lg="{span: 1, offset: 1}" :sm="2" :xs="2">
|
||||
<el-button icon="el-icon-refresh-left" size="small" @click="rotateLeft()"></el-button>
|
||||
</el-col>
|
||||
<el-col :lg="{span: 1, offset: 1}" :sm="2" :xs="2">
|
||||
<el-button icon="el-icon-refresh-right" size="small" @click="rotateRight()"></el-button>
|
||||
</el-col>
|
||||
<el-col :lg="{span: 2, offset: 6}" :sm="2" :xs="2">
|
||||
<el-button type="primary" size="small" @click="uploadImg()">提 交</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import store from "@/store"
|
||||
import { VueCropper } from "vue-cropper"
|
||||
import { uploadAvatar } from "@/api/system/user"
|
||||
import { debounce } from '@/utils'
|
||||
|
||||
export default {
|
||||
components: { VueCropper },
|
||||
data() {
|
||||
return {
|
||||
// 是否显示弹出层
|
||||
open: false,
|
||||
// 是否显示cropper
|
||||
visible: false,
|
||||
// 弹出层标题
|
||||
title: "修改头像",
|
||||
options: {
|
||||
img: store.getters.avatar, //裁剪图片的地址
|
||||
autoCrop: true, // 是否默认生成截图框
|
||||
autoCropWidth: 200, // 默认生成截图框宽度
|
||||
autoCropHeight: 200, // 默认生成截图框高度
|
||||
fixedBox: true, // 固定截图框大小 不允许改变
|
||||
outputType:"png", // 默认生成截图为PNG格式
|
||||
filename: 'avatar' // 文件名称
|
||||
},
|
||||
previews: {},
|
||||
resizeHandler: null
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 编辑头像
|
||||
editCropper() {
|
||||
this.open = true
|
||||
},
|
||||
// 打开弹出层结束时的回调
|
||||
modalOpened() {
|
||||
this.visible = true
|
||||
if (!this.resizeHandler) {
|
||||
this.resizeHandler = debounce(() => {
|
||||
this.refresh()
|
||||
}, 100)
|
||||
}
|
||||
window.addEventListener("resize", this.resizeHandler)
|
||||
},
|
||||
// 刷新组件
|
||||
refresh() {
|
||||
this.$refs.cropper.refresh()
|
||||
},
|
||||
// 覆盖默认的上传行为
|
||||
requestUpload() {
|
||||
},
|
||||
// 向左旋转
|
||||
rotateLeft() {
|
||||
this.$refs.cropper.rotateLeft()
|
||||
},
|
||||
// 向右旋转
|
||||
rotateRight() {
|
||||
this.$refs.cropper.rotateRight()
|
||||
},
|
||||
// 图片缩放
|
||||
changeScale(num) {
|
||||
num = num || 1
|
||||
this.$refs.cropper.changeScale(num)
|
||||
},
|
||||
// 上传预处理
|
||||
beforeUpload(file) {
|
||||
if (file.type.indexOf("image/") == -1) {
|
||||
this.$modal.msgError("文件格式错误,请上传图片类型,如:JPG,PNG后缀的文件。")
|
||||
} else {
|
||||
const reader = new FileReader()
|
||||
reader.readAsDataURL(file)
|
||||
reader.onload = () => {
|
||||
this.options.img = reader.result
|
||||
this.options.filename = file.name
|
||||
}
|
||||
}
|
||||
},
|
||||
// 上传图片
|
||||
uploadImg() {
|
||||
this.$refs.cropper.getCropBlob(data => {
|
||||
let formData = new FormData()
|
||||
formData.append("avatarfile", data, this.options.filename)
|
||||
uploadAvatar(formData).then(response => {
|
||||
this.open = false
|
||||
this.options.img = process.env.VUE_APP_BASE_API + response.imgUrl
|
||||
store.commit('SET_AVATAR', this.options.img)
|
||||
this.$modal.msgSuccess("修改成功")
|
||||
this.visible = false
|
||||
})
|
||||
})
|
||||
},
|
||||
// 实时预览
|
||||
realTime(data) {
|
||||
this.previews = data
|
||||
},
|
||||
// 关闭窗口
|
||||
closeDialog() {
|
||||
this.options.img = store.getters.avatar
|
||||
this.visible = false
|
||||
window.removeEventListener("resize", this.resizeHandler)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.user-info-head {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
height: 120px;
|
||||
}
|
||||
|
||||
.user-info-head:hover:after {
|
||||
content: '+';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
color: #eee;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
font-size: 24px;
|
||||
font-style: normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
cursor: pointer;
|
||||
line-height: 110px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,88 @@
|
||||
<template>
|
||||
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
|
||||
<el-form-item label="用户昵称" prop="nickName">
|
||||
<el-input v-model="form.nickName" maxlength="30" />
|
||||
</el-form-item>
|
||||
<el-form-item label="手机号码" prop="phonenumber">
|
||||
<el-input v-model="form.phonenumber" maxlength="11" />
|
||||
</el-form-item>
|
||||
<el-form-item label="邮箱" prop="email">
|
||||
<el-input v-model="form.email" maxlength="50" />
|
||||
</el-form-item>
|
||||
<el-form-item label="性别">
|
||||
<el-radio-group v-model="form.sex">
|
||||
<el-radio label="0">男</el-radio>
|
||||
<el-radio label="1">女</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" size="mini" @click="submit">保存</el-button>
|
||||
<el-button size="mini" @click="close">关闭</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { updateUserProfile } from "@/api/system/user"
|
||||
|
||||
export default {
|
||||
props: {
|
||||
user: {
|
||||
type: Object
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
form: {},
|
||||
// 表单校验
|
||||
rules: {
|
||||
nickName: [
|
||||
{ required: true, message: "用户昵称不能为空", trigger: "blur" }
|
||||
],
|
||||
email: [
|
||||
{ required: true, message: "邮箱地址不能为空", trigger: "blur" },
|
||||
{
|
||||
type: "email",
|
||||
message: "请输入正确的邮箱地址",
|
||||
trigger: ["blur", "change"]
|
||||
}
|
||||
],
|
||||
phonenumber: [
|
||||
{ required: true, message: "手机号码不能为空", trigger: "blur" },
|
||||
{
|
||||
pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/,
|
||||
message: "请输入正确的手机号码",
|
||||
trigger: "blur"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
user: {
|
||||
handler(user) {
|
||||
if (user) {
|
||||
this.form = { nickName: user.nickName, phonenumber: user.phonenumber, email: user.email, sex: user.sex }
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
submit() {
|
||||
this.$refs["form"].validate(valid => {
|
||||
if (valid) {
|
||||
updateUserProfile(this.form).then(() => {
|
||||
this.$modal.msgSuccess("修改成功")
|
||||
this.user.phonenumber = this.form.phonenumber
|
||||
this.user.email = this.form.email
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
close() {
|
||||
this.$tab.closePage()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,177 @@
|
||||
<template>
|
||||
<el-drawer title="用户信息详情" :visible.sync="visible" direction="rtl" size="68%" append-to-body :before-close="handleClose" custom-class="detail-drawer">
|
||||
<div v-loading="loading" class="drawer-content">
|
||||
<!-- 基本信息 -->
|
||||
<h4 class="section-header">基本信息</h4>
|
||||
<el-row :gutter="20" class="mb8">
|
||||
<el-col :span="12">
|
||||
<div class="info-item">
|
||||
<label class="info-label">用户名称:</label>
|
||||
<span class="info-value plaintext">{{ info.nickName }}</span>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<div class="info-item">
|
||||
<label class="info-label">归属部门:</label>
|
||||
<span class="info-value plaintext">{{ (info.dept && info.dept.deptName) }}</span>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20" class="mb8">
|
||||
<el-col :span="12">
|
||||
<div class="info-item">
|
||||
<label class="info-label">手机号码:</label>
|
||||
<span class="info-value plaintext">{{ info.phonenumber }}</span>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<div class="info-item">
|
||||
<label class="info-label">邮箱:</label>
|
||||
<span class="info-value plaintext">{{ info.email }}</span>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20" class="mb8">
|
||||
<el-col :span="12">
|
||||
<div class="info-item">
|
||||
<label class="info-label">登录账号:</label>
|
||||
<span class="info-value plaintext">{{ info.userName }}</span>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<div class="info-item">
|
||||
<label class="info-label">用户状态:</label>
|
||||
<span class="info-value plaintext">
|
||||
<el-tag size="small" :type="info.status === '0' ? 'success' : 'danger'">{{ info.status === '0' ? '正常' : '停用' }}</el-tag>
|
||||
</span>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20" class="mb8">
|
||||
<el-col :span="12">
|
||||
<div class="info-item">
|
||||
<label class="info-label">岗位:</label>
|
||||
<span class="info-value plaintext">{{ postNames || '无岗位' }}</span>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<div class="info-item">
|
||||
<label class="info-label">用户性别:</label>
|
||||
<span class="info-value plaintext">{{ sexLabel }}</span>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20" class="mb8">
|
||||
<el-col :span="24">
|
||||
<div class="info-item full-width">
|
||||
<label class="info-label">角色:</label>
|
||||
<span class="info-value plaintext">{{ roleNames || '无角色' }}</span>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<!-- 其他信息 -->
|
||||
<h4 class="section-header">其他信息</h4>
|
||||
<el-row :gutter="20" class="mb8">
|
||||
<el-col :span="12">
|
||||
<div class="info-item">
|
||||
<label class="info-label">创建者:</label>
|
||||
<span class="info-value plaintext">{{ info.createBy }}</span>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<div class="info-item">
|
||||
<label class="info-label">创建时间:</label>
|
||||
<span class="info-value plaintext">{{ info.createTime }}</span>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20" class="mb8">
|
||||
<el-col :span="12">
|
||||
<div class="info-item">
|
||||
<label class="info-label">更新者:</label>
|
||||
<span class="info-value plaintext">{{ info.updateBy }}</span>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<div class="info-item">
|
||||
<label class="info-label">更新时间:</label>
|
||||
<span class="info-value plaintext">{{ info.updateTime }}</span>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20" class="mb8">
|
||||
<el-col :span="12">
|
||||
<div class="info-item">
|
||||
<label class="info-label">最后登录IP:</label>
|
||||
<span class="info-value plaintext">{{ info.loginIp }}</span>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<div class="info-item">
|
||||
<label class="info-label">最后登录时间:</label>
|
||||
<span class="info-value plaintext">{{ info.loginDate }}</span>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20" class="mb8">
|
||||
<el-col :span="24">
|
||||
<div class="info-item full-width">
|
||||
<label class="info-label">备注:</label>
|
||||
<span class="info-value plaintext">{{ info.remark }}</span>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getUser } from '@/api/system/user'
|
||||
|
||||
export default {
|
||||
name: 'UserViewDrawer',
|
||||
dicts: ['sys_user_sex'],
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
loading: false,
|
||||
info: {},
|
||||
postOptions: [],
|
||||
roleOptions: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
sexLabel() {
|
||||
return this.selectDictLabel(this.dict.type.sys_user_sex, this.info.sex) || '-'
|
||||
},
|
||||
postNames() {
|
||||
if (!this.postOptions.length) return ''
|
||||
const ids = this.info.postIds || []
|
||||
return this.postOptions.filter(p => ids.includes(p.postId)).map(p => p.postName).join('、') || ''
|
||||
},
|
||||
roleNames() {
|
||||
if (!this.roleOptions.length) return ''
|
||||
const ids = this.info.roleIds || []
|
||||
return this.roleOptions.filter(r => ids.includes(r.roleId)).map(r => r.roleName).join('、') || ''
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
open(userId) {
|
||||
this.visible = true
|
||||
this.loading = true
|
||||
getUser(userId).then(res => {
|
||||
this.info = res.data || {}
|
||||
this.postOptions = res.posts || []
|
||||
this.roleOptions = res.roles || []
|
||||
this.info.postIds = res.postIds || []
|
||||
this.info.roleIds = res.roleIds || []
|
||||
}).finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
handleClose() {
|
||||
this.visible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,753 @@
|
||||
<template>
|
||||
<div class="school-calendar-editor">
|
||||
<!-- 标题栏 -->
|
||||
<div class="sce-titlebar">
|
||||
<div class="sce-title-left">
|
||||
<span class="sce-semester-name">{{ semesterName }}</span>
|
||||
</div>
|
||||
<div class="sce-title-center">
|
||||
<span class="sce-date-range">从 {{ dateRangeText }} ,共 {{ totalWeeks }} 周</span>
|
||||
</div>
|
||||
<div class="sce-title-right">
|
||||
<i class="el-icon-close" title="关闭" @click="handleClose" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 设置面板 -->
|
||||
<div class="sce-panel">
|
||||
<div class="sce-panel-row">
|
||||
<span class="sce-label">事件名称:</span>
|
||||
<el-input v-model="toolbar.eventName" size="small" class="sce-event-input" placeholder="请输入校历事件名称" />
|
||||
<el-checkbox v-model="toolbar.bold" class="sce-cb">加粗显示</el-checkbox>
|
||||
<el-checkbox v-model="toolbar.schedulable" class="sce-cb">可排课</el-checkbox>
|
||||
<el-checkbox v-model="toolbar.mainCourse" class="sce-cb">正课</el-checkbox>
|
||||
<el-checkbox v-model="toolbar.remarkShow" class="sce-cb">备注显示</el-checkbox>
|
||||
<span class="sce-label">备注:</span>
|
||||
<el-input
|
||||
v-model="toolbar.remark"
|
||||
size="small"
|
||||
type="textarea"
|
||||
:rows="1"
|
||||
class="sce-remark-input"
|
||||
resize="none"
|
||||
placeholder="备注信息"
|
||||
/>
|
||||
</div>
|
||||
<div class="sce-panel-row">
|
||||
<el-button type="primary" size="small" icon="el-icon-check" @click="applyEvent">设定</el-button>
|
||||
<el-button type="danger" size="small" icon="el-icon-delete" @click="deleteEvent">删除</el-button>
|
||||
<el-button size="small" icon="el-icon-brush" @click="absorbEvent">吸取</el-button>
|
||||
<el-button size="small" icon="el-icon-refresh" @click="refreshGrid">刷新</el-button>
|
||||
<span class="sce-divider" />
|
||||
<el-checkbox v-model="show78" class="sce-cb">显示78节</el-checkbox>
|
||||
<el-checkbox v-model="showNight" class="sce-cb">显示晚上</el-checkbox>
|
||||
<el-checkbox v-model="showLateNight" class="sce-cb">显示夜间</el-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 时间编排区域 -->
|
||||
<div ref="gridWrap" class="sce-grid-wrap" @mousedown="onGridMouseDown">
|
||||
<table class="sce-grid" :class="{ 'is-selecting': selecting }">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="sce-corner sce-th-weekno" :rowspan="2">周次</th>
|
||||
<th class="sce-corner sce-th-weekrange" :rowspan="2">日期段</th>
|
||||
<th v-for="col in visibleColumns" :key="'h1-' + col.colIndex" :colspan="1" class="sce-th-day">
|
||||
{{ weekDayLabel(col.dayIndex) }}
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th v-for="col in visibleColumns" :key="'h2-' + col.colIndex" class="sce-th-slot">
|
||||
{{ col.slotLabel }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(week, wIdx) in weeks" :key="'w-' + wIdx">
|
||||
<td class="sce-week-cell sce-weekno-cell">{{ wIdx + 1 }}</td>
|
||||
<td class="sce-week-cell sce-weekrange-cell">{{ week.rangeText }}</td>
|
||||
<td
|
||||
v-for="col in visibleColumns"
|
||||
:key="'c-' + wIdx + '-' + col.colIndex"
|
||||
class="sce-cell"
|
||||
:class="cellClass(wIdx, col)"
|
||||
:data-key="cellKey(wIdx, col.colIndex)"
|
||||
@dblclick="onCellDblClick(wIdx, col)"
|
||||
>
|
||||
<span v-if="getEvent(wIdx, col.colIndex)" class="sce-event-name" :class="{ 'is-bold': getEvent(wIdx, col.colIndex).bold }">{{ getEvent(wIdx, col.colIndex).name }}</span>
|
||||
<span v-else class="sce-date-num">{{ dateNumberOf(wIdx, col.dayIndex) }}</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="sce-status">
|
||||
已选 {{ selectedKeys.length }} 个时间格
|
||||
<span v-if="absorbPreview" class="sce-absorb-tip">已吸取:{{ absorbPreview }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getSemester } from '@/api/teachBusiness/semester'
|
||||
import { listXqxlb, updateXqxlb } from '@/api/teachBusiness/xqxlb'
|
||||
|
||||
// 节次定义:12/34/56 常显,78/晚上/夜间由开关控制可见
|
||||
const SLOT_DEFS = [
|
||||
{ key: '12', label: '1-2', ctrl: null },
|
||||
{ key: '34', label: '3-4', ctrl: null },
|
||||
{ key: '56', label: '5-6', ctrl: null },
|
||||
{ key: '78', label: '7-8', ctrl: 'show78' },
|
||||
{ key: 'night', label: '晚上', ctrl: 'showNight' },
|
||||
{ key: 'late', label: '夜间', ctrl: 'showLateNight' }
|
||||
]
|
||||
|
||||
// 前端节次 key -> 后端 xqxlb.courseClass 节次范围(如 12 节 -> "1-2"、夜间 -> "11-12")
|
||||
const SLOT_COURSE_MAP = {
|
||||
'12': '1-2',
|
||||
'34': '3-4',
|
||||
'56': '5-6',
|
||||
'78': '7-8',
|
||||
night: '9-10',
|
||||
late: '11-12'
|
||||
}
|
||||
|
||||
const WEEK_DAYS = ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
|
||||
|
||||
function pad2(n) {
|
||||
return n < 10 ? '0' + n : '' + n
|
||||
}
|
||||
|
||||
function fmtDate(d) {
|
||||
return d.getFullYear() + '-' + pad2(d.getMonth() + 1) + '-' + pad2(d.getDate())
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'SchoolCalendarEditor',
|
||||
props: {
|
||||
nd: { type: [String, Number], default: '' },
|
||||
semesterName: { type: String, default: '' }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 学期日期范围
|
||||
startDate: null,
|
||||
endDate: null,
|
||||
totalWeeks: 0,
|
||||
dateRangeText: '',
|
||||
weeks: [],
|
||||
// 显示开关(默认不勾选,每天仅显示 1-2/3-4/5-6 三个节次)
|
||||
show78: false,
|
||||
showNight: false,
|
||||
showLateNight: false,
|
||||
// 工具栏表单
|
||||
toolbar: {
|
||||
eventName: '',
|
||||
bold: false,
|
||||
schedulable: false,
|
||||
mainCourse: false,
|
||||
remarkShow: false,
|
||||
remark: ''
|
||||
},
|
||||
// 事件存储:key = cellKey -> event
|
||||
events: {},
|
||||
// 暂存被隐藏节次列上的事件:key = 周-星期-节次 -> event,列再次显示时恢复
|
||||
hiddenEvents: {},
|
||||
// 选择集(用数组以保证 Vue2 响应式)
|
||||
selectedKeys: [],
|
||||
// 拖拽状态
|
||||
selecting: false,
|
||||
dragStart: null,
|
||||
dragStartSelected: false, // 按下起点在按下前是否已选中
|
||||
moved: false, // 是否发生移动(区分点击与拖拽)
|
||||
dragAddMode: false, // ctrl 拖拽为追加
|
||||
// 窗口控制
|
||||
absorbPreview: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 当前可见列(由开关过滤,默认每天三个节次)
|
||||
visibleColumns() {
|
||||
const cols = []
|
||||
let colIndex = 0
|
||||
WEEK_DAYS.forEach((dayLabel, dayIndex) => {
|
||||
SLOT_DEFS.forEach(slot => {
|
||||
if (slot.ctrl && !this[slot.ctrl]) return
|
||||
cols.push({ colIndex: colIndex, dayIndex: dayIndex, slotKey: slot.key, slotLabel: slot.label, dayLabel: dayLabel })
|
||||
colIndex++
|
||||
})
|
||||
})
|
||||
return cols
|
||||
},
|
||||
// xqxlb 的年度(nd)为年份(如 2026),由 6 位学期代号(如 202601)截取前 4 位得出
|
||||
xqxlbNd() {
|
||||
return Number(String(this.nd).slice(0, 4))
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
nd: {
|
||||
immediate: true,
|
||||
handler(val) {
|
||||
if (val) this.loadSemester()
|
||||
}
|
||||
},
|
||||
visibleColumns(newCols, oldCols) {
|
||||
// 节次列显隐会改变列索引,按「周期-星期-节次」重定位事件,避免事件随时间格列偏移
|
||||
if (oldCols && oldCols.length) {
|
||||
const oldPosOf = index => (oldCols[index] || null)
|
||||
const newIndex = (dayIndex, slotKey) => {
|
||||
const c = newCols.find(x => x.dayIndex === dayIndex && x.slotKey === slotKey)
|
||||
return c ? c.colIndex : -1
|
||||
}
|
||||
const next = {}
|
||||
Object.keys(this.events).forEach(key => {
|
||||
const m = key.match(/^w(\d+)-c(\d+)$/)
|
||||
if (!m) return
|
||||
const wIdx = +m[1]
|
||||
const old = oldPosOf(+m[2])
|
||||
if (!old) return
|
||||
const newC = newIndex(old.dayIndex, old.slotKey)
|
||||
if (newC < 0) {
|
||||
// 该节次列被隐藏:事件暂存,待列再次显示时恢复
|
||||
this.hiddenEvents[this.cellStableKey(wIdx, old.dayIndex, old.slotKey)] = this.events[key]
|
||||
return
|
||||
}
|
||||
next['w' + wIdx + '-c' + newC] = this.events[key]
|
||||
})
|
||||
// 恢复重新显示的隐藏节次事件
|
||||
Object.keys(this.hiddenEvents).forEach(stable => {
|
||||
const p = stable.split('-')
|
||||
const wIdx = +p[0]
|
||||
const dayIndex = +p[1]
|
||||
const slotKey = p[2]
|
||||
const newC = newIndex(dayIndex, slotKey)
|
||||
if (newC >= 0) {
|
||||
next['w' + wIdx + '-c' + newC] = this.hiddenEvents[stable]
|
||||
delete this.hiddenEvents[stable]
|
||||
}
|
||||
})
|
||||
this.events = next
|
||||
}
|
||||
// 列变化后清空无效选择(列索引含义会变)
|
||||
this.selectedKeys = []
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.removeGlobalListeners()
|
||||
},
|
||||
methods: {
|
||||
/* ---------- 数据加载 ---------- */
|
||||
loadSemester() {
|
||||
if (!this.nd) return
|
||||
getSemester(this.nd)
|
||||
.then(res => {
|
||||
const data = res.data || res || {}
|
||||
const kx = (data.kxrq || '').toString().slice(0, 10)
|
||||
const jx = (data.jsrq || '').toString().slice(0, 10)
|
||||
if (kx && jx) {
|
||||
this.startDate = new Date(kx.replace(/-/g, '/'))
|
||||
this.endDate = new Date(jx.replace(/-/g, '/'))
|
||||
this.buildWeeks()
|
||||
}
|
||||
this.loadXqxlbEvents()
|
||||
})
|
||||
.catch(() => {
|
||||
this.$message.warning('学期详情获取失败,请确认学期数据')
|
||||
})
|
||||
},
|
||||
// 从后端拉取本学期校历事件,渲染到对应时间格(年度由 6 位学期代号截取前 4 位)
|
||||
loadXqxlbEvents() {
|
||||
listXqxlb({ nd: this.xqxlbNd }).then(res => {
|
||||
const data = res.data
|
||||
const list = Array.isArray(data) ? data : (data && data.records) || []
|
||||
return list
|
||||
}).then(list => {
|
||||
const loaded = {}
|
||||
list.forEach(item => {
|
||||
const pos = this.locateXqxlb(item)
|
||||
if (!pos) return
|
||||
const ev = {
|
||||
bh: item.bh,
|
||||
name: item.jqmc || '',
|
||||
bold: !!item.jc,
|
||||
schedulable: !!item.kpk,
|
||||
mainCourse: !!item.zdpk,
|
||||
remarkShow: !!item.bzxs,
|
||||
remark: item.bz || ''
|
||||
}
|
||||
const colIndex = this.colIndexOf(pos.dayIndex, pos.slotKey)
|
||||
if (colIndex >= 0) {
|
||||
loaded[this.cellKey(pos.wIdx, colIndex)] = ev
|
||||
} else {
|
||||
// 该节次列当前隐藏:暂存(含 bh),待列显示时由 visibleColumns 监听恢复
|
||||
this.hiddenEvents[this.cellStableKey(pos.wIdx, pos.dayIndex, pos.slotKey)] = ev
|
||||
}
|
||||
})
|
||||
// 合并到现有格子(后端数据覆盖已有状态)
|
||||
this.events = Object.assign({}, this.events, loaded)
|
||||
}).catch(() => {
|
||||
this.$message.warning('校历事件加载失败')
|
||||
})
|
||||
},
|
||||
// 根据假期记录反推时间格位置(jqsj 日期 + courseClass 节次),与列是否可见无关
|
||||
locateXqxlb(item) {
|
||||
if (!this.startDate || !item.jqsj) return null
|
||||
const d = new Date(String(item.jqsj).slice(0, 10).replace(/-/g, '/'))
|
||||
if (isNaN(d.getTime())) return null
|
||||
const offset = Math.round((d - this.startDate) / (24 * 3600 * 1000))
|
||||
if (offset < 0) return null
|
||||
const wIdx = Math.floor(offset / 7)
|
||||
const dayIndex = offset % 7
|
||||
const slotKey = this.courseClassToSlotKey(item.courseClass)
|
||||
if (slotKey === null) return null
|
||||
return { wIdx, dayIndex, slotKey }
|
||||
},
|
||||
// 后端 courseClass 节次范围 -> 前端节次 key
|
||||
courseClassToSlotKey(courseClass) {
|
||||
if (!courseClass) return null
|
||||
const s = String(courseClass).trim()
|
||||
for (const key in SLOT_COURSE_MAP) {
|
||||
if (s === SLOT_COURSE_MAP[key]) return key
|
||||
}
|
||||
// 兼容 "910"/"1112" 等紧凑写法
|
||||
if (s === '910') return 'night'
|
||||
if (s === '1112') return 'late'
|
||||
return null
|
||||
},
|
||||
buildWeeks() {
|
||||
if (!this.startDate || !this.endDate) return
|
||||
const start = new Date(this.startDate)
|
||||
const end = new Date(this.endDate)
|
||||
const days = Math.round((end - start) / (24 * 3600 * 1000)) + 1
|
||||
this.totalWeeks = Math.ceil(days / 7)
|
||||
this.dateRangeText = fmtDate(start) + ' 到 ' + fmtDate(end)
|
||||
const weeks = []
|
||||
for (let w = 0; w < this.totalWeeks; w++) {
|
||||
const wkStart = new Date(start)
|
||||
wkStart.setDate(start.getDate() + w * 7)
|
||||
const wkEnd = new Date(wkStart)
|
||||
wkEnd.setDate(wkStart.getDate() + 6)
|
||||
weeks.push({
|
||||
rangeText: pad2(wkStart.getMonth() + 1) + '-' + pad2(wkStart.getDate()) +
|
||||
'至' + pad2(wkEnd.getMonth() + 1) + '-' + pad2(wkEnd.getDate())
|
||||
})
|
||||
}
|
||||
this.weeks = weeks
|
||||
},
|
||||
colIndexOf(dayIndex, slotKey) {
|
||||
const col = this.visibleColumns.find(c => c.dayIndex === dayIndex && c.slotKey === slotKey)
|
||||
return col ? col.colIndex : -1
|
||||
},
|
||||
/* ---------- 单元格工具 ---------- */
|
||||
cellKey(wIdx, colIndex) {
|
||||
return 'w' + wIdx + '-c' + colIndex
|
||||
},
|
||||
// 稳定的单元格身份:不随列显隐变化的周-星期-节次
|
||||
cellStableKey(wIdx, dayIndex, slotKey) {
|
||||
return wIdx + '-' + dayIndex + '-' + slotKey
|
||||
},
|
||||
getEvent(wIdx, colIndex) {
|
||||
return this.events[this.cellKey(wIdx, colIndex)] || null
|
||||
},
|
||||
weekDayLabel(dayIndex) {
|
||||
return WEEK_DAYS[dayIndex]
|
||||
},
|
||||
// 单元格内显示的日期数字(如 0703)
|
||||
dateNumberOf(wIdx, dayIndex) {
|
||||
if (!this.startDate) return ''
|
||||
const d = new Date(this.startDate)
|
||||
d.setDate(this.startDate.getDate() + wIdx * 7 + dayIndex)
|
||||
return pad2(d.getMonth() + 1) + pad2(d.getDate())
|
||||
},
|
||||
cellClass(wIdx, col) {
|
||||
const classes = []
|
||||
// 星期交替背景(单双日不同底色)
|
||||
if (col.dayIndex % 2 === 0) classes.push('sce-cell-odd')
|
||||
else classes.push('sce-cell-even')
|
||||
if (this.selectedKeys.includes(this.cellKey(wIdx, col.colIndex))) classes.push('is-selected')
|
||||
const ev = this.getEvent(wIdx, col.colIndex)
|
||||
if (ev) {
|
||||
classes.push('has-event')
|
||||
if (!ev.schedulable) classes.push('no-schedule')
|
||||
}
|
||||
return classes
|
||||
},
|
||||
/* ---------- 点击 / 拖拽多选 ---------- */
|
||||
onGridMouseDown(e) {
|
||||
const cell = e.target.closest('.sce-cell')
|
||||
if (!cell) {
|
||||
// 点击空白区(非单元格,含表头、周次列等)取消所有选中
|
||||
if (this.selectedKeys.length) this.selectedKeys = []
|
||||
return
|
||||
}
|
||||
e.preventDefault()
|
||||
const key = cell.getAttribute('data-key')
|
||||
this.dragStart = key
|
||||
this.dragAddMode = e.ctrlKey || e.metaKey
|
||||
// 记录起点格在按下时的选中状态,用于松手时判定"纯点击"的切换行为
|
||||
this.dragStartSelected = this.selectedKeys.includes(key)
|
||||
this.moved = false
|
||||
this.selecting = true
|
||||
// 非 ctrl 模式下,若起点本身已选中(可能准备拖拽扩展),先不清除,避免误清空
|
||||
if (!this.dragAddMode && !this.dragStartSelected) this.selectedKeys = []
|
||||
this.addGlobalListeners()
|
||||
},
|
||||
addGlobalListeners() {
|
||||
document.addEventListener('mousemove', this.onGridMouseMove)
|
||||
document.addEventListener('mouseup', this.onGridMouseUp)
|
||||
},
|
||||
removeGlobalListeners() {
|
||||
document.removeEventListener('mousemove', this.onGridMouseMove)
|
||||
document.removeEventListener('mouseup', this.onGridMouseUp)
|
||||
},
|
||||
onGridMouseMove(e) {
|
||||
if (!this.selecting || !this.dragStart) return
|
||||
if (!this.moved) this.moved = true
|
||||
const el = document.elementFromPoint(e.clientX, e.clientY)
|
||||
const cell = el && el.closest ? el.closest('.sce-cell') : null
|
||||
if (!cell) return
|
||||
const curKey = cell.getAttribute('data-key')
|
||||
this.selectRectangle(this.dragStart, curKey, this.dragAddMode)
|
||||
},
|
||||
onGridMouseUp() {
|
||||
if (!this.selecting) {
|
||||
this.removeGlobalListeners()
|
||||
return
|
||||
}
|
||||
// 纯点击(未移动)时,做单选 / 切换
|
||||
if (!this.moved) {
|
||||
const key = this.dragStart
|
||||
if (this.dragAddMode) {
|
||||
// Ctrl + 单击:切换该格选中
|
||||
if (this.selectedKeys.includes(key)) {
|
||||
this.selectedKeys = this.selectedKeys.filter(k => k !== key)
|
||||
} else {
|
||||
this.selectedKeys.push(key)
|
||||
}
|
||||
} else {
|
||||
// 普通单击:已选中则取消该格,未选中则仅选中该格
|
||||
if (this.dragStartSelected) {
|
||||
this.selectedKeys = this.selectedKeys.filter(k => k !== key)
|
||||
} else {
|
||||
this.selectedKeys = [key]
|
||||
}
|
||||
}
|
||||
}
|
||||
this.selecting = false
|
||||
this.dragStart = null
|
||||
this.dragStartSelected = false
|
||||
this.removeGlobalListeners()
|
||||
},
|
||||
// 根据起点终点 key 选中矩形范围内所有格
|
||||
selectRectangle(startKey, endKey, addMode) {
|
||||
const parse = k => {
|
||||
const m = k.match(/^w(\d+)-c(\d+)$/)
|
||||
return m ? { w: +m[1], c: +m[2] } : null
|
||||
}
|
||||
const s = parse(startKey)
|
||||
const t = parse(endKey)
|
||||
if (!s || !t) return
|
||||
if (!addMode) this.selectedKeys = []
|
||||
const minW = Math.min(s.w, t.w)
|
||||
const maxW = Math.max(s.w, t.w)
|
||||
const minC = Math.min(s.c, t.c)
|
||||
const maxC = Math.max(s.c, t.c)
|
||||
for (let w = minW; w <= maxW; w++) {
|
||||
for (let c = minC; c <= maxC; c++) {
|
||||
const key = this.cellKey(w, c)
|
||||
if (this.selectedKeys.indexOf(key) === -1) this.selectedKeys.push(key)
|
||||
}
|
||||
}
|
||||
},
|
||||
/* ---------- 事件操作 ---------- */
|
||||
applyEvent() {
|
||||
if (!this.selectedKeys.length) {
|
||||
this.$message.warning('请先选择时间格')
|
||||
return
|
||||
}
|
||||
const name = this.toolbar.eventName.trim()
|
||||
if (!name) {
|
||||
this.$message.warning('请输入事件名称')
|
||||
return
|
||||
}
|
||||
const keys = [...this.selectedKeys]
|
||||
keys.forEach(key => {
|
||||
const prev = this.events[key]
|
||||
this.$set(this.events, key, {
|
||||
bh: prev ? prev.bh : undefined,
|
||||
name: name,
|
||||
bold: this.toolbar.bold,
|
||||
schedulable: this.toolbar.schedulable,
|
||||
mainCourse: this.toolbar.mainCourse,
|
||||
remarkShow: this.toolbar.remarkShow,
|
||||
remark: this.toolbar.remark
|
||||
})
|
||||
})
|
||||
this.persistEvents(keys)
|
||||
},
|
||||
deleteEvent() {
|
||||
if (!this.selectedKeys.length) {
|
||||
this.$message.warning('请先选择时间格')
|
||||
return
|
||||
}
|
||||
const keys = [...this.selectedKeys]
|
||||
keys.forEach(key => {
|
||||
this.$delete(this.events, key)
|
||||
})
|
||||
this.$message.success('已删除 ' + keys.length + ' 个时间格事件')
|
||||
},
|
||||
absorbEvent() {
|
||||
// 吸取:取选择中第一个有事件的格
|
||||
let target = null
|
||||
for (const key of this.selectedKeys) {
|
||||
if (this.events[key]) { target = this.events[key]; break }
|
||||
}
|
||||
if (!target) {
|
||||
this.$message.warning('所选时间格中无事件可吸取')
|
||||
return
|
||||
}
|
||||
this.toolbar.eventName = target.name
|
||||
this.toolbar.bold = !!target.bold
|
||||
this.toolbar.schedulable = !!target.schedulable
|
||||
this.toolbar.mainCourse = !!target.mainCourse
|
||||
this.toolbar.remarkShow = !!target.remarkShow
|
||||
this.toolbar.remark = target.remark || ''
|
||||
this.absorbPreview = target.name
|
||||
},
|
||||
onCellDblClick(wIdx, col) {
|
||||
const ev = this.getEvent(wIdx, col.colIndex)
|
||||
if (ev) {
|
||||
this.toolbar.eventName = ev.name
|
||||
this.toolbar.bold = !!ev.bold
|
||||
this.toolbar.schedulable = !!ev.schedulable
|
||||
this.toolbar.mainCourse = !!ev.mainCourse
|
||||
this.toolbar.remarkShow = !!ev.remarkShow
|
||||
this.toolbar.remark = ev.remark || ''
|
||||
this.absorbPreview = ev.name
|
||||
this.$message.info('已吸取事件:' + ev.name)
|
||||
}
|
||||
},
|
||||
refreshGrid() {
|
||||
this.selectedKeys = []
|
||||
this.absorbPreview = ''
|
||||
this.events = {}
|
||||
this.hiddenEvents = {}
|
||||
this.loadXqxlbEvents()
|
||||
this.$message.info('已刷新')
|
||||
},
|
||||
/* ---------- 后端持久化(xqxlb 接口) ---------- */
|
||||
// 批量保存选中格事件,返回各格子保存成功/失败的个数
|
||||
persistEvents(keys) {
|
||||
const tasks = keys
|
||||
.filter(key => this.events[key])
|
||||
.map(key => {
|
||||
const payload = this.buildXqxlbPayload(key, this.events[key])
|
||||
if (!payload) return Promise.resolve(false)
|
||||
return updateXqxlb(payload)
|
||||
.then(() => true)
|
||||
.catch(() => false)
|
||||
})
|
||||
return Promise.all(tasks).then(results => {
|
||||
const ok = results.filter(r => r === true).length
|
||||
const fail = results.length - ok
|
||||
if (fail > 0) {
|
||||
this.$message.error(fail + ' 个事件保存失败,请检查后端接口')
|
||||
} else if (ok > 0) {
|
||||
this.$message.success('已设定 ' + ok + ' 个时间格')
|
||||
}
|
||||
// 保存成功后重新拉取,回写各记录主键,避免重复新增
|
||||
this.loadXqxlbEvents()
|
||||
return { ok: ok, fail: fail }
|
||||
})
|
||||
},
|
||||
// 构造 xqxlb 提交数据
|
||||
buildXqxlbPayload(key, ev) {
|
||||
const pos = this.parseCellKey(key)
|
||||
if (!pos) return null
|
||||
const d = new Date(this.startDate)
|
||||
d.setDate(this.startDate.getDate() + pos.wIdx * 7 + pos.dayIndex)
|
||||
return {
|
||||
delFlag: 0,
|
||||
bh: ev.bh || undefined,
|
||||
nd: this.xqxlbNd,
|
||||
jqsj: fmtDate(d),
|
||||
jqmc: ev.name || '',
|
||||
jc: !!ev.bold,
|
||||
bz: ev.remark || null,
|
||||
kpk: !!ev.schedulable,
|
||||
bzxs: !!ev.remarkShow,
|
||||
zdpk: !!ev.mainCourse,
|
||||
courseClass: SLOT_COURSE_MAP[pos.slotKey] || null
|
||||
}
|
||||
},
|
||||
// 时间格 key -> { wIdx, dayIndex, slotKey }
|
||||
parseCellKey(key) {
|
||||
const m = key.match(/^w(\d+)-c(\d+)$/)
|
||||
if (!m) return null
|
||||
const colIndex = +m[2]
|
||||
const col = this.visibleColumns.find(c => c.colIndex === colIndex)
|
||||
if (!col) return null
|
||||
return { wIdx: +m[1], dayIndex: col.dayIndex, slotKey: col.slotKey }
|
||||
},
|
||||
/* ---------- 返回学期管理 ---------- */
|
||||
handleClose() {
|
||||
this.$emit('close')
|
||||
this.$router.replace({ path: '/teachBusiness/semester' })
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.school-calendar-editor {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
background: #fff;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
|
||||
/* 标题栏 */
|
||||
.sce-titlebar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 44px;
|
||||
padding: 0 14px;
|
||||
background: var(--edu-green-primary, #00875a);
|
||||
color: #fff;
|
||||
|
||||
.sce-title-left { flex: 1; font-size: 15px; font-weight: 600; }
|
||||
.sce-title-center { flex: 1; text-align: center; font-size: 13px; opacity: 0.9; }
|
||||
.sce-title-right { flex: 0 0 auto; text-align: right; }
|
||||
|
||||
.sce-title-right i {
|
||||
margin-left: 14px;
|
||||
cursor: pointer;
|
||||
font-size: 18px;
|
||||
opacity: 0.85;
|
||||
&:hover { opacity: 1; }
|
||||
}
|
||||
}
|
||||
|
||||
/* 设置面板 */
|
||||
.sce-panel {
|
||||
padding: 10px 14px;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
background: #fafafa;
|
||||
|
||||
.sce-panel-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 8px;
|
||||
|
||||
&:last-child { margin-bottom: 0; }
|
||||
}
|
||||
|
||||
.sce-label { font-size: 13px; color: #606266; margin-right: 6px; white-space: nowrap; }
|
||||
.sce-event-input { width: 200px; margin-right: 12px; }
|
||||
.sce-remark-input { width: 240px; margin-left: 6px; }
|
||||
.sce-cb { margin-right: 14px; }
|
||||
.sce-divider { width: 1px; height: 18px; background: #dcdfe6; margin: 0 14px; }
|
||||
}
|
||||
|
||||
/* 时间编排区 */
|
||||
.sce-grid-wrap {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
padding: 8px 10px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.sce-grid {
|
||||
border-collapse: collapse;
|
||||
table-layout: fixed;
|
||||
width: 100%;
|
||||
font-size: 12px;
|
||||
user-select: none;
|
||||
|
||||
th, td {
|
||||
border: 1px solid #e4e7ed;
|
||||
text-align: center;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.sce-corner {
|
||||
background: #f5f7fa;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.sce-th-weekno { width: 52px; }
|
||||
.sce-th-weekrange { width: 110px; }
|
||||
|
||||
.sce-th-day {
|
||||
background: var(--edu-green-primary, #00875a);
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
height: 26px;
|
||||
}
|
||||
|
||||
.sce-th-slot {
|
||||
background: #ecf5f0;
|
||||
color: #00875a;
|
||||
height: 22px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.sce-week-cell {
|
||||
background: #f5f7fa;
|
||||
vertical-align: middle;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.sce-weekno-cell {
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.sce-weekrange-cell {
|
||||
font-size: 11px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.sce-cell {
|
||||
height: 30px;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
background: #fff;
|
||||
transition: background 0.15s;
|
||||
|
||||
.sce-date-num { color: #c0c4cc; }
|
||||
|
||||
// 星期交替底色:周一/三/五/日为浅绿,周二/四/六为白,便于区分星期
|
||||
&.sce-cell-odd { background: #eef6f1; }
|
||||
&.sce-cell-even { background: #ffffff; }
|
||||
&.has-event { background: #e6f4ea; }
|
||||
&.has-event .sce-event-name { color: #00663e; font-weight: 500; }
|
||||
&.has-event .sce-event-name.is-bold { font-weight: 700; }
|
||||
&.no-schedule { background: #fde2e2; }
|
||||
|
||||
&.is-selected {
|
||||
outline: 2px solid var(--edu-green-primary, #00875a);
|
||||
outline-offset: -2px;
|
||||
background: #d4ecdf !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sce-status {
|
||||
padding: 6px 14px;
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
border-top: 1px solid #ebeef5;
|
||||
background: #fafafa;
|
||||
|
||||
.sce-absorb-tip { margin-left: 12px; color: #00875a; }
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,489 @@
|
||||
<template>
|
||||
<div class="teach-calendar app-container">
|
||||
<!-- 顶部标题:学期名称-校历 -->
|
||||
<div class="cal-titlebar">
|
||||
<span class="cal-title">{{ titleText }}</span>
|
||||
</div>
|
||||
|
||||
<!-- 显示控制栏 -->
|
||||
<div class="cal-panel">
|
||||
<el-checkbox v-model="showDate" class="cal-cb">显示日期</el-checkbox>
|
||||
<div class="cal-panel-right">
|
||||
<el-button size="mini" type="primary" icon="el-icon-download" @click="exportExcel">另存为Excel</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 学期校历表格 -->
|
||||
<div class="cal-grid-wrap">
|
||||
<table class="cal-grid">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="cal-corner cal-th-weekno" :rowspan="2">周次</th>
|
||||
<th class="cal-corner cal-th-weekrange" :rowspan="2">日期段</th>
|
||||
<th v-for="day in weekDayHeaders" :key="'h1-' + day.dayIndex" :colspan="day.colCount" class="cal-th-day">
|
||||
{{ day.label }}
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th v-for="col in visibleColumns" :key="'h2-' + col.colIndex" class="cal-th-slot">
|
||||
{{ col.slotLabel }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(week, wIdx) in weeks" :key="'w-' + wIdx">
|
||||
<td class="cal-week-cell cal-weekno-cell">{{ wIdx + 1 }}</td>
|
||||
<td class="cal-week-cell cal-weekrange-cell">{{ week.rangeText }}</td>
|
||||
<td
|
||||
v-for="col in visibleColumns"
|
||||
:key="'c-' + wIdx + '-' + col.colIndex"
|
||||
class="cal-cell"
|
||||
:class="cellClass(wIdx, col)"
|
||||
>
|
||||
<template v-if="getEvent(wIdx, col.colIndex)">
|
||||
<span class="cal-event-name">{{ getEvent(wIdx, col.colIndex).name }}</span>
|
||||
</template>
|
||||
<span v-else-if="showDate" class="cal-date-num">{{ dateNumberOf(wIdx, col.dayIndex) }}</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- 底部状态栏 -->
|
||||
<div class="cal-status">
|
||||
<span v-if="nd">从 {{ dateRangeText }} ,共 {{ totalWeeks }} 周</span>
|
||||
<span v-else>请选择学期</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { saveAs } from 'file-saver'
|
||||
import { listSemester, getSemester } from '@/api/teachBusiness/semester'
|
||||
import { listXqxlb } from '@/api/teachBusiness/xqxlb'
|
||||
|
||||
// 节次定义:全部常显
|
||||
const SLOT_DEFS = [
|
||||
{ key: '12', label: '1-2' },
|
||||
{ key: '34', label: '3-4' },
|
||||
{ key: '56', label: '5-6' },
|
||||
{ key: '78', label: '7-8' },
|
||||
{ key: 'night', label: '晚上' },
|
||||
{ key: 'late', label: '夜间' }
|
||||
]
|
||||
|
||||
// 前端节次 key -> 后端 xqxlb.courseClass 节次范围
|
||||
const SLOT_COURSE_MAP = {
|
||||
'12': '1-2',
|
||||
'34': '3-4',
|
||||
'56': '5-6',
|
||||
'78': '7-8',
|
||||
night: '9-10',
|
||||
late: '11-12'
|
||||
}
|
||||
|
||||
const WEEK_DAYS = ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
|
||||
|
||||
function pad2(n) {
|
||||
return n < 10 ? '0' + n : '' + n
|
||||
}
|
||||
|
||||
function fmtDate(d) {
|
||||
return d.getFullYear() + '-' + pad2(d.getMonth() + 1) + '-' + pad2(d.getDate())
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'SemesterCalendar',
|
||||
data() {
|
||||
return {
|
||||
// 当前学期代号(跟随顶栏切换学期,自动更新为当前学期)
|
||||
nd: '',
|
||||
// 学期日期范围
|
||||
startDate: null,
|
||||
endDate: null,
|
||||
totalWeeks: 0,
|
||||
dateRangeText: '',
|
||||
weeks: [],
|
||||
// 显示控制:默认不勾选显示日期;节次全部展示
|
||||
showDate: false,
|
||||
// 事件存储:key = cellKey -> event
|
||||
events: {},
|
||||
loading: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 页面标题:学期名称-校历
|
||||
titleText() {
|
||||
if (!this.nd) return '学期校历'
|
||||
return this.getSemesterName(this.nd) + '-校历'
|
||||
},
|
||||
// 当前可见列(全部节次常显)
|
||||
visibleColumns() {
|
||||
const cols = []
|
||||
let colIndex = 0
|
||||
WEEK_DAYS.forEach((dayLabel, dayIndex) => {
|
||||
SLOT_DEFS.forEach(slot => {
|
||||
cols.push({ colIndex: colIndex, dayIndex: dayIndex, slotKey: slot.key, slotLabel: slot.label, dayLabel: dayLabel })
|
||||
colIndex++
|
||||
})
|
||||
})
|
||||
return cols
|
||||
},
|
||||
// 表头第一行星期:每个星期合并其下全部节次列
|
||||
weekDayHeaders() {
|
||||
return WEEK_DAYS.map((label, dayIndex) => ({
|
||||
dayIndex: dayIndex,
|
||||
label: label,
|
||||
colCount: SLOT_DEFS.length
|
||||
}))
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 初始加载当前学期
|
||||
this.loadCurrentSemester()
|
||||
// 顶栏切换学期后自动刷新为当前学期的校历
|
||||
this.$root.$on('semester-current-changed', this.loadCurrentSemester)
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.$root.$off('semester-current-changed', this.loadCurrentSemester)
|
||||
},
|
||||
methods: {
|
||||
/* ---------- 加载当前学期 ---------- */
|
||||
loadCurrentSemester() {
|
||||
this.loading = true
|
||||
listSemester({ pageNum: 1, pageSize: 100 }).then(response => {
|
||||
const data = response.data || {}
|
||||
const list = data.records || []
|
||||
// 跟随顶栏当前学期(dqxq=true),无则取第一条
|
||||
const current = list.find(i => i.dqxq) || list[0]
|
||||
this.loading = false
|
||||
if (current) {
|
||||
if (String(this.nd) !== String(current.nd)) {
|
||||
this.nd = current.nd
|
||||
this.loadSemester()
|
||||
}
|
||||
}
|
||||
}).catch(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
/* ---------- 数据加载 ---------- */
|
||||
loadSemester() {
|
||||
if (!this.nd) return
|
||||
this.startDate = null
|
||||
this.endDate = null
|
||||
getSemester(this.nd)
|
||||
.then(res => {
|
||||
const data = res.data || res || {}
|
||||
const kx = (data.kxrq || '').toString().slice(0, 10)
|
||||
const jx = (data.jsrq || '').toString().slice(0, 10)
|
||||
if (kx && jx) {
|
||||
this.startDate = new Date(kx.replace(/-/g, '/'))
|
||||
this.endDate = new Date(jx.replace(/-/g, '/'))
|
||||
this.buildWeeks()
|
||||
}
|
||||
this.loadXqxlbEvents()
|
||||
})
|
||||
.catch(() => {
|
||||
this.$message.warning('学期详情获取失败,请确认学期数据')
|
||||
})
|
||||
},
|
||||
// 从后端拉取本学期校历事件,渲染到对应时间格(年度为 6 位学期代号截取前 4 位)
|
||||
loadXqxlbEvents() {
|
||||
listXqxlb({ nd: String(this.nd).slice(0, 4) }).then(res => {
|
||||
const data = res.data
|
||||
const list = Array.isArray(data) ? data : (data && data.records) || []
|
||||
const loaded = {}
|
||||
list.forEach(item => {
|
||||
const pos = this.locateXqxlb(item)
|
||||
if (pos) {
|
||||
loaded[pos.key] = {
|
||||
bh: item.bh,
|
||||
name: item.jqmc || '',
|
||||
schedulable: !!item.kpk,
|
||||
mainCourse: !!item.zdpk,
|
||||
remarkShow: !!item.bzxs,
|
||||
remark: item.bz || ''
|
||||
}
|
||||
}
|
||||
})
|
||||
this.events = loaded
|
||||
}).catch(() => {
|
||||
this.$message.warning('校历事件加载失败')
|
||||
})
|
||||
},
|
||||
// 根据假期记录反推时间格 key(jqsj 日期 + courseClass 节次)
|
||||
locateXqxlb(item) {
|
||||
if (!this.startDate || !item.jqsj) return null
|
||||
const d = new Date(String(item.jqsj).slice(0, 10).replace(/-/g, '/'))
|
||||
if (isNaN(d.getTime())) return null
|
||||
const offset = Math.round((d - this.startDate) / (24 * 3600 * 1000))
|
||||
if (offset < 0) return null
|
||||
const wIdx = Math.floor(offset / 7)
|
||||
const dayIndex = offset % 7
|
||||
const slotKey = this.courseClassToSlotKey(item.courseClass)
|
||||
if (slotKey === null) return null
|
||||
const colIndex = this.colIndexOf(dayIndex, slotKey)
|
||||
if (colIndex < 0) return null
|
||||
return { key: this.cellKey(wIdx, colIndex) }
|
||||
},
|
||||
// 后端 courseClass 节次范围 -> 前端节次 key
|
||||
courseClassToSlotKey(courseClass) {
|
||||
if (!courseClass) return null
|
||||
const s = String(courseClass).trim()
|
||||
for (const key in SLOT_COURSE_MAP) {
|
||||
if (s === SLOT_COURSE_MAP[key]) return key
|
||||
}
|
||||
// 兼容 "910"/"1112" 等紧凑写法
|
||||
if (s === '910') return 'night'
|
||||
if (s === '1112') return 'late'
|
||||
return null
|
||||
},
|
||||
buildWeeks() {
|
||||
if (!this.startDate || !this.endDate) return
|
||||
const start = new Date(this.startDate)
|
||||
const end = new Date(this.endDate)
|
||||
const days = Math.round((end - start) / (24 * 3600 * 1000)) + 1
|
||||
this.totalWeeks = Math.ceil(days / 7)
|
||||
this.dateRangeText = fmtDate(start) + ' 到 ' + fmtDate(end)
|
||||
const weeks = []
|
||||
for (let w = 0; w < this.totalWeeks; w++) {
|
||||
const wkStart = new Date(start)
|
||||
wkStart.setDate(start.getDate() + w * 7)
|
||||
const wkEnd = new Date(wkStart)
|
||||
wkEnd.setDate(wkStart.getDate() + 6)
|
||||
weeks.push({
|
||||
rangeText: pad2(wkStart.getMonth() + 1) + '-' + pad2(wkStart.getDate()) +
|
||||
'至' + pad2(wkEnd.getMonth() + 1) + '-' + pad2(wkEnd.getDate())
|
||||
})
|
||||
}
|
||||
this.weeks = weeks
|
||||
},
|
||||
colIndexOf(dayIndex, slotKey) {
|
||||
const col = this.visibleColumns.find(c => c.dayIndex === dayIndex && c.slotKey === slotKey)
|
||||
return col ? col.colIndex : -1
|
||||
},
|
||||
/* ---------- 单元格工具 ---------- */
|
||||
cellKey(wIdx, colIndex) {
|
||||
return 'w' + wIdx + '-c' + colIndex
|
||||
},
|
||||
getEvent(wIdx, colIndex) {
|
||||
return this.events[this.cellKey(wIdx, colIndex)] || null
|
||||
},
|
||||
weekDayLabel(dayIndex) {
|
||||
return WEEK_DAYS[dayIndex]
|
||||
},
|
||||
// 单元格内显示的日期数字(如 0703)
|
||||
dateNumberOf(wIdx, dayIndex) {
|
||||
if (!this.startDate) return ''
|
||||
const d = new Date(this.startDate)
|
||||
d.setDate(this.startDate.getDate() + wIdx * 7 + dayIndex)
|
||||
return pad2(d.getMonth() + 1) + pad2(d.getDate())
|
||||
},
|
||||
cellClass(wIdx, col) {
|
||||
const classes = []
|
||||
// 星期交替背景(单双日不同底色)
|
||||
if (col.dayIndex % 2 === 0) classes.push('cal-cell-odd')
|
||||
else classes.push('cal-cell-even')
|
||||
const ev = this.getEvent(wIdx, col.colIndex)
|
||||
if (ev) {
|
||||
classes.push('has-event')
|
||||
if (!ev.schedulable) classes.push('no-schedule')
|
||||
}
|
||||
return classes
|
||||
},
|
||||
/** 学期代号 -> 学期名称(如 202601 -> 2026年春季学期) */
|
||||
getSemesterName(nd) {
|
||||
if (!nd) return ''
|
||||
const s = String(nd)
|
||||
const xn = s.slice(0, 4)
|
||||
const xq = s.slice(4)
|
||||
const map = { '01': '春季学期', '02': '夏季学期', '03': '秋季学期' }
|
||||
return xn + '年' + (map[xq] || '第' + xq + '学期')
|
||||
},
|
||||
/* ---------- 另存为Excel ---------- */
|
||||
exportExcel() {
|
||||
if (!this.weeks.length) {
|
||||
this.$message.warning('暂无校历数据可导出')
|
||||
return
|
||||
}
|
||||
// 按星期分组节次列,用于表头合并
|
||||
const dayCols = []
|
||||
this.visibleColumns.forEach(col => {
|
||||
if (!dayCols[col.dayIndex]) dayCols[col.dayIndex] = []
|
||||
dayCols[col.dayIndex].push(col)
|
||||
})
|
||||
let html = '<html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel">'
|
||||
html += '<head><meta charset="utf-8"></head><body>'
|
||||
html += '<table border="1" cellspacing="0" cellpadding="4" style="border-collapse:collapse;">'
|
||||
// 标题行
|
||||
html += '<tr><td colspan="' + (this.visibleColumns.length + 2) + '" style="font-size:15px;font-weight:bold;text-align:center;">' + this.titleText + '</td></tr>'
|
||||
// 表头第一行:星期(合并同类天)
|
||||
html += '<tr><th rowspan="2">周次</th><th rowspan="2">日期段</th>'
|
||||
WEEK_DAYS.forEach((dayLabel, dayIndex) => {
|
||||
const cols = dayCols[dayIndex] || []
|
||||
if (cols.length) html += '<th colspan="' + cols.length + '">' + dayLabel + '</th>'
|
||||
})
|
||||
html += '</tr>'
|
||||
// 表头第二行:节次
|
||||
html += '<tr>'
|
||||
this.visibleColumns.forEach(col => {
|
||||
html += '<th>' + col.slotLabel + '</th>'
|
||||
})
|
||||
html += '</tr>'
|
||||
// 数据行
|
||||
this.weeks.forEach((week, wIdx) => {
|
||||
html += '<tr>'
|
||||
html += '<td>' + (wIdx + 1) + '</td>'
|
||||
html += '<td>' + week.rangeText + '</td>'
|
||||
this.visibleColumns.forEach(col => {
|
||||
const ev = this.getEvent(wIdx, col.colIndex)
|
||||
if (ev) {
|
||||
const remark = ev.remark && ev.remarkShow ? '(' + ev.remark + ')' : ''
|
||||
html += '<td>' + ev.name + remark + '</td>'
|
||||
} else if (this.showDate) {
|
||||
html += '<td>' + this.dateNumberOf(wIdx, col.dayIndex) + '</td>'
|
||||
} else {
|
||||
html += '<td></td>'
|
||||
}
|
||||
})
|
||||
html += '</tr>'
|
||||
})
|
||||
html += '</table></body></html>'
|
||||
const blob = new Blob(['\ufeff' + html], { type: 'application/vnd.ms-excel;charset=utf-8' })
|
||||
saveAs(blob, (this.titleText || '学期校历') + '.xls')
|
||||
this.$message.success('已导出Excel')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.teach-calendar {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 12px;
|
||||
box-sizing: border-box;
|
||||
background: #fff;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
|
||||
/* 顶部标题栏 */
|
||||
.cal-titlebar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 44px;
|
||||
background: var(--edu-green-primary, #00875a);
|
||||
color: #fff;
|
||||
border-radius: 4px 4px 0 0;
|
||||
|
||||
.cal-title {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
/* 显示控制栏 */
|
||||
.cal-panel {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
padding: 8px 14px;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
background: #fafafa;
|
||||
|
||||
.cal-cb { margin-right: 14px; }
|
||||
.cal-panel-right { margin-left: auto; }
|
||||
}
|
||||
|
||||
/* 表格区域 */
|
||||
.cal-grid-wrap {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
padding: 8px 10px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.cal-grid {
|
||||
border-collapse: collapse;
|
||||
table-layout: fixed;
|
||||
width: 100%;
|
||||
font-size: 12px;
|
||||
|
||||
th, td {
|
||||
border: 1px solid #e4e7ed;
|
||||
text-align: center;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.cal-corner {
|
||||
background: #f5f7fa;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.cal-th-weekno { width: 52px; }
|
||||
.cal-th-weekrange { width: 110px; }
|
||||
|
||||
.cal-th-day {
|
||||
background: var(--edu-green-primary, #00875a);
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
height: 26px;
|
||||
}
|
||||
|
||||
.cal-th-slot {
|
||||
background: #ecf5f0;
|
||||
color: #00875a;
|
||||
height: 22px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.cal-week-cell {
|
||||
background: #f5f7fa;
|
||||
vertical-align: middle;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.cal-weekno-cell {
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.cal-weekrange-cell {
|
||||
font-size: 11px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.cal-cell {
|
||||
height: 30px;
|
||||
position: relative;
|
||||
background: #fff;
|
||||
|
||||
.cal-date-num { color: #c0c4cc; }
|
||||
|
||||
// 星期交替底色:周一/三/五/日为浅绿,周二/四/六为白
|
||||
&.cal-cell-odd { background: #eef6f1; }
|
||||
&.cal-cell-even { background: #ffffff; }
|
||||
&.has-event { background: #e6f4ea; }
|
||||
&.has-event .cal-event-name { color: #00663e; font-weight: 500; }
|
||||
&.no-schedule { background: #fde2e2; }
|
||||
}
|
||||
}
|
||||
|
||||
/* 底部状态栏 */
|
||||
.cal-status {
|
||||
padding: 6px 14px;
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
border-top: 1px solid #ebeef5;
|
||||
background: #fafafa;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,53 @@
|
||||
<template>
|
||||
<div class="teach-calendar app-container">
|
||||
<school-calendar-editor
|
||||
v-if="nd"
|
||||
:nd="nd"
|
||||
:semester-name="semesterName"
|
||||
@close="handleClose"
|
||||
/>
|
||||
<div v-else class="calendar-empty">未指定学期,请从学期管理页面进入。</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SchoolCalendarEditor from '@/views/teachBusiness/calendar/components/SchoolCalendarEditor'
|
||||
|
||||
export default {
|
||||
name: 'SemesterCalendarEdit',
|
||||
components: { SchoolCalendarEditor },
|
||||
data() {
|
||||
return {
|
||||
nd: '',
|
||||
semesterName: ''
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.nd = this.$route.query.nd || ''
|
||||
this.semesterName = this.$route.query.name || ''
|
||||
},
|
||||
methods: {
|
||||
handleClose() {
|
||||
this.$router.replace({ path: '/teachBusiness/semester' })
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.teach-calendar {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 12px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.calendar-empty {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #909399;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,621 @@
|
||||
<template>
|
||||
<div class="app-container teach-semester">
|
||||
<!-- 工具栏 -->
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd">新添</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.8">
|
||||
<el-button type="success" plain icon="el-icon-date" size="mini" :disabled="!currentSemester"
|
||||
@click="handleEditCalendar">
|
||||
编辑校历
|
||||
</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="danger" plain icon="el-icon-delete" size="mini" :disabled="multiple" @click="handleDelete">
|
||||
批量删除
|
||||
</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<el-table v-loading="loading" :data="semesterList" border :height="tableHeight" highlight-current-row
|
||||
@current-change="handleCurrentChange" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" align="center" width="50" />
|
||||
<el-table-column label="学期名称" align="center" width="250">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ getSemesterName(scope.row.nd) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="当前" align="center" width="100">
|
||||
<template slot-scope="scope">
|
||||
<el-tag v-if="scope.row.dqxq" type="success" size="mini">当前</el-tag>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="开学日期" align="center" prop="kxrq" width="160">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ formatDate(scope.row.kxrq) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="结束日期" align="center" prop="jsrq" width="160">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ formatDate(scope.row.jsrq) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="周数" align="center" prop="sdzs" width="100" />
|
||||
<el-table-column label="调课不审批" align="center" width="120">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="scope.row.tkbsp ? 'primary' : 'info'" size="mini">{{ scope.row.tkbsp ? '是' : '否' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="禁止调课" align="center" width="120">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="scope.row.jztk ? 'danger' : 'info'" size="mini">{{ scope.row.jztk ? '是' : '否' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="终结成绩定及格" align="center" width="160">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="scope.row.zjcjdjg ? 'primary' : 'info'" size="mini">{{ scope.row.zjcjdjg ? '是' : '否'
|
||||
}}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="课时系数方案" align="center" width="160">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ getCoefficientLabel(scope.row.ksxsfabh) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="锁定时长" align="center" width="100">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ scope.row.sdjldw || '-' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" fixed="right" class-name="table-action-column">
|
||||
<template slot-scope="scope">
|
||||
<el-button type="text" size="mini" icon="el-icon-edit" @click="handleUpdate(scope.row)">编辑</el-button>
|
||||
<el-button type="text" size="mini" icon="el-icon-delete" class="text-danger"
|
||||
@click="handleDelete(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize"
|
||||
@pagination="getList" />
|
||||
|
||||
<!-- 学期信息弹窗 -->
|
||||
<el-dialog :title="title" :visible.sync="open" width="900px" append-to-body class="semester-dialog">
|
||||
<el-alert v-if="isSemesterNameDuplicated()" class="semester-dup-alert" type="warning" show-icon
|
||||
title="该学期已存在,不能重复添加" :closable="false" />
|
||||
<el-form ref="form" class="semester-dialog-form" :model="form" :rules="rules" label-width="96px"
|
||||
label-position="right">
|
||||
<el-row :gutter="20" class="semester-dialog-body">
|
||||
<!-- 左侧表单区 -->
|
||||
<el-col :span="15" class="semester-form-col">
|
||||
<el-form-item label="学期名称">
|
||||
<el-input :value="getSemesterName(buildNd(form.xn, form.xq))" readonly placeholder="请在下方选择年份和学期类型"
|
||||
prefix-icon="el-icon-office-building" />
|
||||
</el-form-item>
|
||||
<el-row :gutter="14">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="年份" prop="xn">
|
||||
<el-select v-model="form.xn" placeholder="请选择年份" :disabled="!isAdd" style="width: 100%"
|
||||
@change="checkSemesterNameUnique">
|
||||
<el-option v-for="item in yearOptions" :key="item" :label="item + '年'" :value="item" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="学期类型" prop="xq">
|
||||
<el-select v-model="form.xq" placeholder="请选择学期类型" :disabled="!isAdd" style="width: 100%"
|
||||
@change="checkSemesterNameUnique">
|
||||
<el-option v-for="item in semesterOptions" :key="item.value" :label="item.label"
|
||||
:value="item.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="14">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="开学日期" prop="kxrq">
|
||||
<el-date-picker v-model="form.kxrq" type="date" placeholder="开学日期" value-format="yyyy-MM-dd"
|
||||
style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="结束日期" prop="jsrq">
|
||||
<el-date-picker v-model="form.jsrq" type="date" placeholder="结束日期" value-format="yyyy-MM-dd"
|
||||
style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="14">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="学期周数" prop="sdzs">
|
||||
<el-input-number v-model="form.sdzs" :min="1" :max="60" controls-position="right"
|
||||
style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="课时系数方案" prop="ksxsfabh">
|
||||
<el-select v-model="form.ksxsfabh" placeholder="请选择" clearable style="width: 100%">
|
||||
<el-option v-for="item in coefficientOptions" :key="item.value" :label="item.label"
|
||||
:value="item.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="锁定时长">
|
||||
<div class="lock-group">
|
||||
<el-input-number v-model="form.lockValue" :min="0" :max="99" controls-position="right"
|
||||
style="width: 120px" />
|
||||
<el-radio-group v-model="form.lockUnit" class="lock-unit" style="margin-left: 28px">
|
||||
<el-radio label="周">周</el-radio>
|
||||
<el-radio label="月">月</el-radio>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<div class="form-tip">以当前周星期四中午12点为界限,锁定【锁定周数】周的教学实施计划,被锁定的教学实施计划不允许教员自行调整。</div>
|
||||
</el-col>
|
||||
|
||||
<!-- 右侧业务开关区 -->
|
||||
<el-col :span="9" class="semester-switch-col">
|
||||
<div class="switch-panel-title">业务开关</div>
|
||||
<div class="switch-panel">
|
||||
<div class="switch-item">
|
||||
<el-checkbox v-model="form.dqxq">当前学期</el-checkbox>
|
||||
<div class="switch-tip">设定当前学期后,系统启动时会自动将该学期设置为默认学期。</div>
|
||||
</div>
|
||||
|
||||
<div class="switch-item">
|
||||
<el-checkbox v-model="form.jztk">禁止调课</el-checkbox>
|
||||
<div class="switch-tip">设定【禁止调课】后,教学信息系统中就无法对课程信息进行调整。</div>
|
||||
</div>
|
||||
|
||||
<div class="switch-item">
|
||||
<el-checkbox v-model="form.tkbsp">调课不审批</el-checkbox>
|
||||
<div class="switch-tip">
|
||||
设定【调课不审批】后,在教学信息系统中调课时,如果只是更改教员,或更改授课地点,调课申请会立即生效。而对于改变授课地点,调课申请会立即生效。而对于改变授课时间、增加或减少授课教员的调课申请依然不受该设置影响,依然需要逐级审批后才能生效。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="switch-item">
|
||||
<el-checkbox v-model="form.zjcjdjg">终结成绩定及格</el-checkbox>
|
||||
<div class="switch-tip">设定【终结成绩定及格】后,学员终结性成绩不及格的,最终成绩即为不及格。</div>
|
||||
</div>
|
||||
|
||||
<div class="switch-item">
|
||||
<el-checkbox v-model="form.jwldshtk">教务机关审核教学计划调整</el-checkbox>
|
||||
<div class="switch-tip">勾选:教务机关需要从行政入口对每个教学计划调整申请进行审批,同时再由管理员身份教务参谋进行最后的把关;不勾选:仅由管理员身份教务参谋进行最后的把关。</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listSemester, getSemester, addSemester, updateSemester, delSemester } from "@/api/teachBusiness/semester"
|
||||
|
||||
export default {
|
||||
name: "Semester",
|
||||
data() {
|
||||
return {
|
||||
// 遮罩层
|
||||
loading: true,
|
||||
// 表格高度
|
||||
tableHeight: window.innerHeight - 280,
|
||||
// 总条数
|
||||
total: 0,
|
||||
// 学期列表数据
|
||||
semesterList: [],
|
||||
// 选中的学期数组
|
||||
ids: [],
|
||||
// 当前点击选中的学期
|
||||
currentSemester: null,
|
||||
// 非单个禁用
|
||||
single: true,
|
||||
// 非多个禁用
|
||||
multiple: true,
|
||||
// 弹出层标题
|
||||
title: "",
|
||||
// 是否显示弹出层
|
||||
open: false,
|
||||
// 是否新增
|
||||
isAdd: true,
|
||||
// 学年选项(当前年份 ± 10 年)
|
||||
yearOptions: [],
|
||||
// 学期选项:01 春季、02 夏季、03 秋季
|
||||
semesterOptions: [
|
||||
{ value: '01', label: '春季学期' },
|
||||
{ value: '02', label: '夏季学期' },
|
||||
{ value: '03', label: '秋季学期' }
|
||||
],
|
||||
// 课时系数方案选项(引用基础数据表,暂以静态占位)
|
||||
coefficientOptions: [
|
||||
{ value: '1', label: '标准系数方案' },
|
||||
{ value: '2', label: '综合系数方案' },
|
||||
{ value: '3', label: '实训系数方案' }
|
||||
],
|
||||
// 查询参数
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10
|
||||
},
|
||||
// 表单参数
|
||||
form: {},
|
||||
// 表单校验
|
||||
rules: {
|
||||
xn: [
|
||||
{ required: true, message: "请选择年份", trigger: "change" }
|
||||
],
|
||||
xq: [
|
||||
{ required: true, message: "请选择学期类型", trigger: "change" }
|
||||
],
|
||||
kxrq: [
|
||||
{ required: true, message: "请选择开学日期", trigger: "change" }
|
||||
],
|
||||
jsrq: [
|
||||
{ required: true, message: "请选择结束日期", trigger: "change" }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.initYearOptions()
|
||||
this.getList()
|
||||
// 顶栏切换当前学期后,自动刷新本页数据
|
||||
this.$root.$on('semester-current-changed', this.handleCurrentChanged)
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.$root.$off('semester-current-changed', this.handleCurrentChanged)
|
||||
},
|
||||
methods: {
|
||||
/** 顶栏切换当前学期后自动刷新 */
|
||||
handleCurrentChanged() {
|
||||
this.getList()
|
||||
},
|
||||
/** 初始化学年选项 */
|
||||
initYearOptions() {
|
||||
const current = new Date().getFullYear()
|
||||
const list = []
|
||||
for (let i = current - 10; i <= current + 10; i++) {
|
||||
list.push(i)
|
||||
}
|
||||
this.yearOptions = list
|
||||
},
|
||||
/** 学年+学期 -> 学期代号(如 2026 + 01 = 202601) */
|
||||
buildNd(xn, xq) {
|
||||
if (xn === undefined || xn === null || xn === '' || !xq) return ''
|
||||
return String(xn) + String(xq)
|
||||
},
|
||||
/** 学期代号 -> 学期名称(如 202601 -> 2026年春季学期) */
|
||||
getSemesterName(nd) {
|
||||
if (!nd) return ''
|
||||
const s = String(nd)
|
||||
const xn = s.slice(0, 4)
|
||||
const xq = s.slice(4)
|
||||
const map = { '01': '春季学期', '02': '夏季学期', '03': '秋季学期' }
|
||||
return xn + '年' + (map[xq] || '第' + xq + '学期')
|
||||
},
|
||||
/** 学期代号 -> {xn, xq} */
|
||||
parseNd(nd) {
|
||||
if (!nd) return { xn: undefined, xq: undefined }
|
||||
const s = String(nd)
|
||||
return { xn: s.slice(0, 4), xq: s.slice(4) }
|
||||
},
|
||||
/** 新增时校验学期名称(年份+学期类型拼接)是否已存在于表格 */
|
||||
isSemesterNameDuplicated() {
|
||||
if (!this.isAdd) return false
|
||||
const nd = this.buildNd(this.form.xn, this.form.xq)
|
||||
if (!nd) return false
|
||||
return this.semesterList.some(item => String(item.nd) === String(nd))
|
||||
},
|
||||
/** 年份/学期类型切换时实时刷新顶部重复提示 */
|
||||
checkSemesterNameUnique() {
|
||||
this.$forceUpdate()
|
||||
},
|
||||
/** 课时系数方案编号 -> 名称 */
|
||||
getCoefficientLabel(value) {
|
||||
if (!value && value !== 0) return '-'
|
||||
const found = this.coefficientOptions.find(o => String(o.value) === String(value))
|
||||
return found ? found.label : value
|
||||
},
|
||||
/** 格式化后端 LocalDateTime(2026-02-23T00:00:00 -> 2026-02-23) */
|
||||
formatDate(value) {
|
||||
if (!value) return ''
|
||||
return String(value).slice(0, 10)
|
||||
},
|
||||
/** 查询学期列表 */
|
||||
getList() {
|
||||
this.loading = true
|
||||
listSemester(this.queryParams).then(response => {
|
||||
const data = response.data || {}
|
||||
this.semesterList = data.records || []
|
||||
this.total = data.total || 0
|
||||
this.loading = false
|
||||
}).catch(() => {
|
||||
this.semesterList = []
|
||||
this.total = 0
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
/** 多选变化 */
|
||||
handleSelectionChange(selection) {
|
||||
this.ids = selection.map(item => item.nd)
|
||||
this.single = selection.length !== 1
|
||||
this.multiple = !selection.length
|
||||
},
|
||||
/** 单击行选中变化:记录当前选中的学期,用于启用「编辑校历」 */
|
||||
handleCurrentChange(currentRow) {
|
||||
this.currentSemester = currentRow || null
|
||||
},
|
||||
/** 组装提交数据 */
|
||||
buildPayload(form) {
|
||||
const lockValue = form.lockValue !== undefined && form.lockValue !== null ? form.lockValue : ''
|
||||
return {
|
||||
delFlag: 0,
|
||||
nd: this.buildNd(form.xn, form.xq),
|
||||
dqxq: !!form.dqxq,
|
||||
kxrq: form.kxrq ? form.kxrq + 'T00:00:00' : null,
|
||||
jsrq: form.jsrq ? form.jsrq + 'T00:00:00' : null,
|
||||
tkbsp: !!form.tkbsp,
|
||||
jztk: !!form.jztk,
|
||||
zjcjdjg: !!form.zjcjdjg,
|
||||
jwldshtk: !!form.jwldshtk,
|
||||
ksxsfabh: form.ksxsfabh || null,
|
||||
sdzs: form.sdzs,
|
||||
sdjldw: lockValue === '' ? '' : lockValue + form.lockUnit,
|
||||
xh: form.xh,
|
||||
jsonzd: "{'1':1}",
|
||||
kstjsj: null
|
||||
}
|
||||
},
|
||||
/** 新增按钮 */
|
||||
handleAdd() {
|
||||
this.reset()
|
||||
this.isAdd = true
|
||||
this.open = true
|
||||
this.title = "学期信息 - 新增"
|
||||
},
|
||||
/** 编辑按钮 */
|
||||
handleUpdate(row) {
|
||||
const semesterId = row && row.nd ? row.nd : this.ids[0]
|
||||
if (!semesterId) return
|
||||
this.reset()
|
||||
this.isAdd = false
|
||||
getSemester(semesterId).then(response => {
|
||||
const data = response.data || {}
|
||||
const { xn, xq } = this.parseNd(data.nd)
|
||||
// 解析锁定时长(如 "2周" / "1月" / "1")
|
||||
let lockValue = data.sdjldw
|
||||
let lockUnit = '周'
|
||||
if (lockValue) {
|
||||
const s = String(lockValue)
|
||||
if (s.endsWith('周')) { lockValue = s.slice(0, -1); lockUnit = '周' }
|
||||
else if (s.endsWith('月')) { lockValue = s.slice(0, -1); lockUnit = '月' }
|
||||
}
|
||||
this.form = {
|
||||
xn: xn,
|
||||
xq: xq,
|
||||
dqxq: !!data.dqxq,
|
||||
jztk: !!data.jztk,
|
||||
tkbsp: !!data.tkbsp,
|
||||
zjcjdjg: !!data.zjcjdjg,
|
||||
jwldshtk: !!data.jwldshtk,
|
||||
ksxsfabh: data.ksxsfabh,
|
||||
kxrq: this.formatDate(data.kxrq),
|
||||
jsrq: this.formatDate(data.jsrq),
|
||||
sdzs: data.sdzs,
|
||||
lockValue: lockValue,
|
||||
lockUnit: lockUnit,
|
||||
xh: data.xh
|
||||
}
|
||||
this.open = true
|
||||
this.title = "学期信息 - 编辑"
|
||||
}).catch(() => { })
|
||||
},
|
||||
/** 编辑校历按钮:对当前选中的学期编辑校历 */
|
||||
handleEditCalendar() {
|
||||
const row = this.currentSemester
|
||||
if (!row || !row.nd) return
|
||||
this.$router.push({
|
||||
path: '/teachBusiness/semester/semesterCalendar',
|
||||
query: { nd: row.nd, name: this.getSemesterName(row.nd) }
|
||||
})
|
||||
},
|
||||
/** 删除按钮 */
|
||||
handleDelete(row) {
|
||||
const semesterIds = row && row.nd ? [row.nd] : this.ids
|
||||
if (!semesterIds.length) return
|
||||
const names = semesterIds.map(nd => this.getSemesterName(nd)).join('、')
|
||||
this.$modal.confirm('确认删除学期【' + names + '】吗?').then(() => {
|
||||
// 批量删除:逐个调用
|
||||
const delList = semesterIds.map(nd => delSemester(nd))
|
||||
return Promise.all(delList)
|
||||
}).then(() => {
|
||||
this.getList()
|
||||
this.$modal.msgSuccess("删除成功")
|
||||
}).catch(() => { })
|
||||
},
|
||||
/** 提交表单 */
|
||||
submitForm() {
|
||||
if (this.isSemesterNameDuplicated()) {
|
||||
this.$modal.msgWarning("该学期已存在,不能重复添加")
|
||||
return
|
||||
}
|
||||
this.$refs["form"].validate(valid => {
|
||||
if (valid) {
|
||||
const payload = this.buildPayload(this.form)
|
||||
const selfNd = payload.nd
|
||||
// 当前学期全局只能有一个:若本次设为当前,先把其他 dqxq=true 的取消
|
||||
const cancelOthers = payload.dqxq
|
||||
? this.cancelOtherCurrent(selfNd)
|
||||
: Promise.resolve()
|
||||
cancelOthers.then(() => {
|
||||
if (this.isAdd) {
|
||||
return addSemester(payload)
|
||||
}
|
||||
return updateSemester(payload)
|
||||
}).then(() => {
|
||||
this.$modal.msgSuccess(this.isAdd ? "新增成功" : "修改成功")
|
||||
this.open = false
|
||||
this.getList()
|
||||
}).catch(() => { })
|
||||
}
|
||||
})
|
||||
},
|
||||
/** 取消其它“当前学期”,保证全局唯一 */
|
||||
cancelOtherCurrent(selfNd) {
|
||||
return listSemester({ pageNum: 1, pageSize: 100 }).then(response => {
|
||||
const data = response.data || {}
|
||||
const list = data.records || []
|
||||
const others = list.filter(i => i.dqxq && String(i.nd) !== String(selfNd))
|
||||
if (!others.length) return Promise.resolve()
|
||||
return Promise.all(others.map(o => updateSemester({ ...o, dqxq: false })))
|
||||
})
|
||||
},
|
||||
/** 重置表单 */
|
||||
reset() {
|
||||
this.form = {
|
||||
xn: undefined,
|
||||
xq: undefined,
|
||||
dqxq: false,
|
||||
jztk: false,
|
||||
tkbsp: false,
|
||||
zjcjdjg: false,
|
||||
jwldshtk: false,
|
||||
ksxsfabh: undefined,
|
||||
kxrq: undefined,
|
||||
jsrq: undefined,
|
||||
sdzs: 20,
|
||||
lockValue: 1,
|
||||
lockUnit: '周',
|
||||
xh: 1
|
||||
}
|
||||
this.resetForm("form")
|
||||
},
|
||||
/** 取消按钮 */
|
||||
cancel() {
|
||||
this.open = false
|
||||
this.reset()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.teach-semester {
|
||||
.mb8 {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
/* 弹窗内表单:防止 label 文字换行、字段拥挤换行 */
|
||||
::v-deep .el-dialog__body {
|
||||
padding-top: 16px;
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
|
||||
/* 对话框整体两栏布局 */
|
||||
.semester-dialog-body {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* 顶部学期重复提示与表单间距 */
|
||||
.semester-dup-alert {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.semester-form-col {
|
||||
padding-right: 4px;
|
||||
}
|
||||
|
||||
.semester-switch-col {
|
||||
padding-left: 24px;
|
||||
border-left: 1px solid #ebeef5;
|
||||
}
|
||||
|
||||
.semester-dialog-form {
|
||||
::v-deep .el-form-item {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
::v-deep .el-form-item__label {
|
||||
white-space: nowrap;
|
||||
line-height: 32px;
|
||||
padding-right: 10px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
::v-deep .el-form-item__content {
|
||||
line-height: 32px;
|
||||
}
|
||||
|
||||
.el-input,
|
||||
.el-select,
|
||||
.el-date-editor {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.form-tip {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
line-height: 1.5;
|
||||
margin-top: 4px;
|
||||
white-space: normal;
|
||||
padding-left: 2px;
|
||||
}
|
||||
|
||||
.lock-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.switch-panel-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin-bottom: 14px;
|
||||
padding-left: 2px;
|
||||
}
|
||||
|
||||
.switch-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.switch-item {
|
||||
padding: 11px 13px;
|
||||
background: #fafafa;
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 6px;
|
||||
transition: border-color 0.2s, background 0.2s;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--edu-green-primary);
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.el-checkbox {
|
||||
height: 26px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.switch-tip {
|
||||
font-size: 12px;
|
||||
line-height: 1.6;
|
||||
color: #909399;
|
||||
text-align: justify;
|
||||
margin: 2px 0 0 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,296 @@
|
||||
<template>
|
||||
<div class="app-container teaching-task-page">
|
||||
<!-- ==================== 页面标题 ==================== -->
|
||||
<div class="page-title">教学任务列表</div>
|
||||
|
||||
<!-- ==================== 1. 查询条件区域 ==================== -->
|
||||
<el-card shadow="never" class="search-card">
|
||||
<el-form :model="searchForm" label-width="80px" class="search-form">
|
||||
<el-row :gutter="24">
|
||||
<el-col :xs="24" :sm="12" :md="8">
|
||||
<el-form-item label="任务名称">
|
||||
<el-input
|
||||
v-model="searchForm.rwmc"
|
||||
placeholder="请输入任务名称"
|
||||
clearable
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="8">
|
||||
<el-form-item label="年度">
|
||||
<el-select
|
||||
v-model="searchForm.nd"
|
||||
placeholder="请选择年度"
|
||||
clearable
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option v-for="y in yearOptions" :key="y" :label="y" :value="y" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="8">
|
||||
<el-form-item label="状态">
|
||||
<el-select
|
||||
v-model="searchForm.zt"
|
||||
placeholder="请选择状态"
|
||||
clearable
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="opt in statusOptions"
|
||||
:key="opt.value"
|
||||
:label="opt.label"
|
||||
:value="opt.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<div class="search-actions">
|
||||
<el-button type="primary" icon="el-icon-search" @click="handleQuery">查询</el-button>
|
||||
</div>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<!-- ==================== 2. 数据表格 ==================== -->
|
||||
<el-card shadow="never" class="table-card">
|
||||
<el-table v-loading="loading" :data="tableData" border stripe>
|
||||
<template slot="empty">
|
||||
<span>无数据!</span>
|
||||
</template>
|
||||
<el-table-column type="index" label="序号" width="70" align="center" />
|
||||
<el-table-column prop="bh" label="编号" width="100" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="rwmc" label="任务名称" width="250" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="nd" label="年度" width="150" align="center" />
|
||||
<el-table-column label="状态" width="100" align="center">
|
||||
<template slot-scope="{ row }">
|
||||
<el-tag :type="row.zt === '已发布' ? 'success' : 'info'" size="small">
|
||||
{{ row.zt || '-' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="fbsj" label="发布时间" width="200" align="center" :formatter="fmtDateTime" />
|
||||
<el-table-column prop="jssj" label="结束时间" width="200" align="center" :formatter="fmtDateTime" />
|
||||
<el-table-column prop="cjsj" label="创建时间" width="200" align="center" :formatter="fmtDateTime" />
|
||||
<el-table-column prop="jcxqscsj" label="教材需求生成时间" width="200" align="center" :formatter="fmtDateTime" />
|
||||
<el-table-column label="操作" align="center" fixed="right">
|
||||
<template slot-scope="{ row }">
|
||||
<el-button type="text" size="small" @click="handleDetail(row)">详情</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-pagination
|
||||
:current-page="pageNum"
|
||||
:page-size="pageSize"
|
||||
:total="total"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
class="pagination"
|
||||
@current-change="handlePageChange"
|
||||
@size-change="handleSizeChange"
|
||||
/>
|
||||
</el-card>
|
||||
|
||||
<!-- ==================== 3. 详情对话框 ==================== -->
|
||||
<el-dialog
|
||||
:visible="detailVisible"
|
||||
title="教学任务详情"
|
||||
width="620px"
|
||||
:close-on-click-modal="false"
|
||||
@update:visible="val => detailVisible = val"
|
||||
>
|
||||
<el-descriptions v-if="detailData.bh" :column="2" border>
|
||||
<el-descriptions-item label="编号">{{ detailData.bh || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="任务名称">{{ detailData.rwmc || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="年度">{{ fmtVal(detailData.nd) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="状态">{{ detailData.zt || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="发布时间">{{ fmtVal(detailData.fbsj) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="结束时间">{{ fmtVal(detailData.jssj) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">{{ fmtVal(detailData.cjsj) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="教材需求生成时间">{{ fmtVal(detailData.jcxqscsj) }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<div v-else v-loading="detailLoading" class="detail-empty">加载中...</div>
|
||||
<div slot="footer">
|
||||
<el-button @click="detailVisible = false">关闭</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* 教学任务列表(只读查询页)
|
||||
* 对应菜单:teachBusiness/teachingTask(原「分组课列表」页,下载类功能后端无接口已移除)
|
||||
* 仅提供 分页查询 list + 详情 get?bh=,不做增删改发(避免与教学任务计划管理页重复操作同一实体)
|
||||
* 增删改发统一在 src/views/teachOffice/taskPlan/index.vue(教学任务计划管理)完成
|
||||
*/
|
||||
import { listTeachingTask, getTeachingTask } from '@/api/teachBusiness/teachingTask'
|
||||
import { listAllSemester } from '@/api/teachBusiness/semester'
|
||||
|
||||
export default {
|
||||
name: 'TeachingTask',
|
||||
data() {
|
||||
return {
|
||||
// ==================== 1. 查询条件 ====================
|
||||
searchForm: {
|
||||
rwmc: '',
|
||||
nd: undefined,
|
||||
zt: ''
|
||||
},
|
||||
yearOptions: [],
|
||||
statusOptions: [
|
||||
{ label: '未发布', value: '未发布' },
|
||||
{ label: '已发布', value: '已发布' }
|
||||
],
|
||||
|
||||
// ==================== 2. 表格数据 ====================
|
||||
loading: false,
|
||||
tableData: [],
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
|
||||
// ==================== 3. 详情 ====================
|
||||
detailVisible: false,
|
||||
detailLoading: false,
|
||||
detailData: {}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadYearOptions().then(() => this.fetchList())
|
||||
},
|
||||
methods: {
|
||||
/* ---------- 年度下拉(数据来自 /semester/all,禁止硬编码) ---------- */
|
||||
loadYearOptions() {
|
||||
return listAllSemester().then(response => {
|
||||
const list = response.data || []
|
||||
const map = {}
|
||||
list.forEach(item => {
|
||||
if (item.nd !== null && item.nd !== undefined && item.nd !== '') map[item.nd] = item
|
||||
})
|
||||
const arr = Object.keys(map).sort((a, b) => String(b).localeCompare(String(a)))
|
||||
this.yearOptions = arr
|
||||
// 默认年度:优先当前学期(dqxq=true),否则取最新年度
|
||||
const current = list.find(item => item.dqxq === true)
|
||||
const defaultNd = (current && current.nd) || arr[0]
|
||||
if (!this.searchForm.nd) this.searchForm.nd = defaultNd
|
||||
return arr
|
||||
}).catch(() => {
|
||||
this.yearOptions = []
|
||||
return []
|
||||
})
|
||||
},
|
||||
|
||||
/* ---------- 通用格式化 ---------- */
|
||||
fmtDateTime(row, column, cellValue) {
|
||||
if (cellValue === null || cellValue === undefined || cellValue === '') return '-'
|
||||
return String(cellValue).replace('T', ' ').slice(0, 16)
|
||||
},
|
||||
fmtVal(val) {
|
||||
if (val === null || val === undefined || val === '') return '-'
|
||||
return String(val).replace('T', ' ').slice(0, 16)
|
||||
},
|
||||
|
||||
/* ---------- 列表加载 ---------- */
|
||||
fetchList() {
|
||||
this.loading = true
|
||||
const params = { pageNum: this.pageNum, pageSize: this.pageSize }
|
||||
if (this.searchForm.rwmc && this.searchForm.rwmc.trim()) params.rwmc = this.searchForm.rwmc.trim()
|
||||
if (this.searchForm.nd !== undefined && this.searchForm.nd !== null && this.searchForm.nd !== '') {
|
||||
params.nd = this.searchForm.nd
|
||||
}
|
||||
if (this.searchForm.zt && this.searchForm.zt.trim()) params.zt = this.searchForm.zt.trim()
|
||||
listTeachingTask(params).then(res => {
|
||||
const data = (res && res.data) || {}
|
||||
this.tableData = data.records || []
|
||||
this.total = data.total || 0
|
||||
this.loading = false
|
||||
}).catch(() => {
|
||||
this.tableData = []
|
||||
this.total = 0
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
|
||||
handleQuery() {
|
||||
this.pageNum = 1
|
||||
this.fetchList()
|
||||
},
|
||||
|
||||
handlePageChange(page) {
|
||||
this.pageNum = page
|
||||
this.fetchList()
|
||||
},
|
||||
|
||||
handleSizeChange(size) {
|
||||
this.pageSize = size
|
||||
this.pageNum = 1
|
||||
this.fetchList()
|
||||
},
|
||||
|
||||
/* ---------- 详情(只读,走真实 get?bh= 接口) ---------- */
|
||||
handleDetail(row) {
|
||||
this.detailVisible = true
|
||||
this.detailLoading = true
|
||||
this.detailData = {}
|
||||
getTeachingTask(row.bh).then(res => {
|
||||
this.detailData = (res && res.data) || {}
|
||||
this.detailLoading = false
|
||||
}).catch(() => {
|
||||
this.detailLoading = false
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.app-container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.teaching-task-page {
|
||||
.page-title {
|
||||
text-align: center;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
// ==================== 1. 查询条件区域 ====================
|
||||
.search-card {
|
||||
margin-bottom: 16px;
|
||||
|
||||
.search-form {
|
||||
.search-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid #ebeef5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 2. 数据表格 ====================
|
||||
.table-card {
|
||||
.pagination {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 3. 详情 ====================
|
||||
.detail-empty {
|
||||
min-height: 120px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #909399;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,417 @@
|
||||
<template>
|
||||
<div class="app-container conflict-page">
|
||||
<!-- 冲突检查区域 -->
|
||||
<div class="section-card">
|
||||
<div class="section-header">
|
||||
<div>
|
||||
<div class="section-title">教学资源冲突检查</div>
|
||||
<div class="section-desc">当前检查年度:{{ nd ? nd + ' 年' : '未选择' }},可单独检查或执行全部检查</div>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<el-form inline class="toolbar-form" @submit.native.prevent>
|
||||
<el-form-item label="年度" class="year-item">
|
||||
<el-select v-model="nd" placeholder="请选择年度" style="width: 130px" @change="handleNdChange">
|
||||
<el-option v-for="y in yearOptions" :key="y" :label="y + ' 年'" :value="y" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-button size="mini" :disabled="loading" @click="handleReset">重置</el-button>
|
||||
<el-button type="primary" size="mini" :loading="loading" @click="handleCheckAll">执行全部检查</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 检查项卡片网格 -->
|
||||
<div class="check-grid">
|
||||
<div
|
||||
v-for="item in conflictItems"
|
||||
:key="item.key"
|
||||
class="check-item-card"
|
||||
:class="{ active: activeKey === item.key }"
|
||||
@click="selectCard(item)"
|
||||
>
|
||||
<div class="check-item-header">
|
||||
<span class="check-item-name">{{ item.label }}</span>
|
||||
<el-tag :type="getStatus(item).type" size="mini">{{ getStatus(item).text }}</el-tag>
|
||||
</div>
|
||||
<div class="check-item-desc">{{ item.desc }}</div>
|
||||
<div class="check-item-footer">
|
||||
<span class="conflict-count">
|
||||
冲突数:
|
||||
<b :class="item.count > 0 ? 'danger-text' : 'normal-text'">{{ item.count }}</b>
|
||||
</span>
|
||||
<el-button type="primary" plain size="mini" :disabled="loading" @click.stop="handleCheck(item)">检查</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 冲突明细区域 -->
|
||||
<div class="section-card">
|
||||
<div class="section-header detail-header">
|
||||
<div class="section-title">
|
||||
冲突明细
|
||||
<span v-if="activeKey" class="active-label">— {{ activeLabel }}</span>
|
||||
</div>
|
||||
<span v-if="activeKey" class="detail-count">共 {{ detailTotal }} 条记录</span>
|
||||
</div>
|
||||
<template v-if="activeKey">
|
||||
<el-table v-loading="detailLoading" :data="activeDetails" border stripe size="mini" max-height="500">
|
||||
<el-table-column type="index" label="序号" width="55" align="center" />
|
||||
<el-table-column label="冲突号" width="90" align="center" show-overflow-tooltip>
|
||||
<template slot-scope="scope">{{ scope.row.conflictNo || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="courseNames" label="课程" min-width="150" align="center" show-overflow-tooltip />
|
||||
<el-table-column label="日期" width="100" align="center">
|
||||
<template slot-scope="scope">{{ fmtDate(scope.row.rq) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="jc" label="节次" width="70" align="center" />
|
||||
<el-table-column prop="xydNames" label="班次" min-width="140" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="teacherNames" label="教员" min-width="120" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="classroomNames" label="场地" min-width="140" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="responsibleDept" label="责任单位" min-width="110" align="center" show-overflow-tooltip />
|
||||
</el-table>
|
||||
<el-pagination
|
||||
v-if="detailTotal > 0"
|
||||
class="detail-pagination"
|
||||
background
|
||||
layout="total, prev, pager, next"
|
||||
:current-page="detailQuery.pageNum"
|
||||
:page-size="detailQuery.pageSize"
|
||||
:total="detailTotal"
|
||||
@current-change="handlePageChange"
|
||||
/>
|
||||
<el-empty v-if="detailTotal === 0 && !detailLoading" description="暂无冲突明细" />
|
||||
</template>
|
||||
<el-empty v-else v-show="!loading" description="点击上方卡片「检查」按钮,在此查看冲突明细" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { checkConflict, checkAllConflict, resetConflict, getConflictDetails } from '@/api/teachBusiness/timetableConflict'
|
||||
import { listAllSemester } from '@/api/teachBusiness/semester'
|
||||
|
||||
// 四类检查卡片定义(标题/描述与后端 TimetableConflictDimension 枚举一致,作为页面布局常量;
|
||||
// 检查状态与冲突数完全来自后端接口)
|
||||
const CARD_DEFS = [
|
||||
{ key: 'TEAM_CONFLICT', label: '教学班时间冲突检查', desc: '同一教学班次在同一时间段被安排多门课程' },
|
||||
{ key: 'ELECTIVE_REQUIRED_CONFLICT', label: '教学班必修与选修时间冲突检查', desc: '教学班次必修课与选修课时间重叠' },
|
||||
{ key: 'TEACHER_CONFLICT', label: '教员时间冲突检查', desc: '同一教员在同一时间段被安排多门课程' },
|
||||
{ key: 'CLASSROOM_CONFLICT', label: '教室时间冲突检查', desc: '同一教室在同一时间段被多门课程占用' }
|
||||
]
|
||||
|
||||
export default {
|
||||
name: 'TimetableConflict',
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
detailLoading: false,
|
||||
// 年度(数据来自 /semester/all,真实后端数据)
|
||||
nd: undefined,
|
||||
yearOptions: [],
|
||||
conflictItems: CARD_DEFS.map(c => ({ ...c, count: 0, checked: false })),
|
||||
activeKey: '',
|
||||
activeDetails: [],
|
||||
detailTotal: 0,
|
||||
detailQuery: { pageNum: 1, pageSize: 20 }
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
activeLabel() {
|
||||
const item = this.conflictItems.find(i => i.key === this.activeKey)
|
||||
return item ? item.label : ''
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadYearOptions()
|
||||
},
|
||||
methods: {
|
||||
/* ---------- 年度下拉(数据来自 /semester/all) ---------- */
|
||||
loadYearOptions() {
|
||||
return listAllSemester().then(response => {
|
||||
const list = response.data || []
|
||||
// 去重并按年度倒序
|
||||
const map = {}
|
||||
list.forEach(item => {
|
||||
if (item.nd !== null && item.nd !== undefined && item.nd !== '') map[item.nd] = item
|
||||
})
|
||||
const arr = Object.keys(map).sort((a, b) => String(b).localeCompare(String(a)))
|
||||
this.yearOptions = arr
|
||||
// 默认年度:优先当前学期(dqxq=true),否则取最新年度
|
||||
const current = list.find(item => item.dqxq === true)
|
||||
this.nd = (current && current.nd) || arr[0]
|
||||
return arr
|
||||
}).catch(() => {
|
||||
this.yearOptions = []
|
||||
this.nd = undefined
|
||||
return []
|
||||
})
|
||||
},
|
||||
fmtDate(rq) {
|
||||
return (rq || '').substring(0, 10)
|
||||
},
|
||||
getStatus(item) {
|
||||
if (!item.checked) return { type: 'info', text: '未检查' }
|
||||
return item.count > 0
|
||||
? { type: 'danger', text: '存在 ' + item.count + ' 处冲突' }
|
||||
: { type: 'success', text: '无冲突' }
|
||||
},
|
||||
/* 用后端返回的单卡片结果同步对应卡片状态 */
|
||||
applyCard(card) {
|
||||
if (!card) return
|
||||
const item = this.conflictItems.find(i => i.key === card.dimensionCode)
|
||||
if (!item) return
|
||||
item.checked = card.checked
|
||||
item.count = card.conflictCount
|
||||
},
|
||||
applySummary(summary) {
|
||||
if (!summary || !summary.cards) return
|
||||
summary.cards.forEach(card => this.applyCard(card))
|
||||
},
|
||||
/* ---------- 单个检查 ---------- */
|
||||
handleCheck(item) {
|
||||
if (!this.nd) {
|
||||
this.$message.warning('请先选择年度')
|
||||
return
|
||||
}
|
||||
this.loading = true
|
||||
checkConflict({ nd: this.nd, dimensionCode: item.key }).then(response => {
|
||||
this.applyCard(response.data)
|
||||
this.activeKey = item.key
|
||||
this.detailQuery.pageNum = 1
|
||||
this.loadDetails()
|
||||
if (item.count > 0) {
|
||||
this.$message.warning('「' + item.label + '」检查完成,发现 ' + item.count + ' 处冲突')
|
||||
} else {
|
||||
this.$message.success('「' + item.label + '」检查完成,未发现冲突')
|
||||
}
|
||||
}).finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
/* ---------- 全部检查 ---------- */
|
||||
handleCheckAll() {
|
||||
if (!this.nd) {
|
||||
this.$message.warning('请先选择年度')
|
||||
return
|
||||
}
|
||||
this.loading = true
|
||||
checkAllConflict({ nd: this.nd }).then(response => {
|
||||
const summary = response.data
|
||||
this.applySummary(summary)
|
||||
const total = summary ? summary.totalConflictCount : 0
|
||||
this.$message.success('全部检查完成,共发现 ' + total + ' 处冲突')
|
||||
const firstConflict = this.conflictItems.find(i => i.checked && i.count > 0)
|
||||
const firstChecked = this.conflictItems.find(i => i.checked)
|
||||
this.activeKey = firstConflict ? firstConflict.key : (firstChecked ? firstChecked.key : '')
|
||||
this.detailQuery.pageNum = 1
|
||||
if (this.activeKey) {
|
||||
this.loadDetails()
|
||||
} else {
|
||||
this.activeDetails = []
|
||||
this.detailTotal = 0
|
||||
}
|
||||
}).finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
/* ---------- 重置 ---------- */
|
||||
handleReset() {
|
||||
if (!this.nd) {
|
||||
this.$message.warning('请先选择年度')
|
||||
return
|
||||
}
|
||||
resetConflict({ nd: this.nd }).then(response => {
|
||||
this.applySummary(response.data)
|
||||
this.activeKey = ''
|
||||
this.activeDetails = []
|
||||
this.detailTotal = 0
|
||||
this.$message.success('检查结果已重置')
|
||||
})
|
||||
},
|
||||
/* 点击卡片:仅已检查的卡片可查看其明细 */
|
||||
selectCard(item) {
|
||||
if (!item.checked) return
|
||||
this.activeKey = item.key
|
||||
this.detailQuery.pageNum = 1
|
||||
this.loadDetails()
|
||||
},
|
||||
/* ---------- 冲突明细 ---------- */
|
||||
loadDetails() {
|
||||
if (!this.activeKey) {
|
||||
this.activeDetails = []
|
||||
this.detailTotal = 0
|
||||
return
|
||||
}
|
||||
this.detailLoading = true
|
||||
getConflictDetails({
|
||||
nd: this.nd,
|
||||
dimensionCode: this.activeKey,
|
||||
pageNum: this.detailQuery.pageNum,
|
||||
pageSize: this.detailQuery.pageSize
|
||||
}).then(response => {
|
||||
const page = response.data || {}
|
||||
this.activeDetails = page.records || []
|
||||
this.detailTotal = page.total || 0
|
||||
}).catch(() => {
|
||||
this.activeDetails = []
|
||||
this.detailTotal = 0
|
||||
}).finally(() => {
|
||||
this.detailLoading = false
|
||||
})
|
||||
},
|
||||
handlePageChange(page) {
|
||||
this.detailQuery.pageNum = page
|
||||
this.loadDetails()
|
||||
},
|
||||
/* 切换年度:该年度检查状态未知,重置为未检查 */
|
||||
handleNdChange() {
|
||||
this.conflictItems.forEach(i => {
|
||||
i.checked = false
|
||||
i.count = 0
|
||||
})
|
||||
this.activeKey = ''
|
||||
this.activeDetails = []
|
||||
this.detailTotal = 0
|
||||
this.detailQuery.pageNum = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.conflict-page {
|
||||
.section-card {
|
||||
background: #fff;
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 6px;
|
||||
padding: 16px 20px;
|
||||
margin-bottom: 16px;
|
||||
|
||||
.section-title {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
margin-bottom: 16px;
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
|
||||
.toolbar-form {
|
||||
margin-right: 4px;
|
||||
|
||||
.year-item {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.section-desc {
|
||||
margin-top: 8px;
|
||||
font-size: 13px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.detail-header {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 检查项卡片网格 */
|
||||
.check-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.check-item-card {
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 6px;
|
||||
padding: 14px 16px;
|
||||
transition: box-shadow 0.2s ease;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
&.active {
|
||||
border-color: var(--edu-green-primary, #00875a);
|
||||
box-shadow: 0 0 0 2px rgba(0, 135, 90, 0.15);
|
||||
}
|
||||
|
||||
.check-item-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
|
||||
.check-item-name {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
}
|
||||
|
||||
.check-item-desc {
|
||||
margin-top: 8px;
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.check-item-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-top: 12px;
|
||||
|
||||
.conflict-count {
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
|
||||
b {
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.danger-text {
|
||||
color: #f56c6c;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.normal-text {
|
||||
color: #67c23a;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.active-label {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
color: var(--edu-green-primary, #00875a);
|
||||
}
|
||||
|
||||
.detail-count {
|
||||
font-size: 13px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.detail-pagination {
|
||||
margin-top: 12px;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,676 @@
|
||||
<template>
|
||||
<div class="app-container training-plan-page">
|
||||
<!-- ==================== 1. 查询条件区域 ==================== -->
|
||||
<el-card shadow="never" class="search-card">
|
||||
<el-form :model="searchForm" label-width="100px" class="search-form" @submit.native.prevent>
|
||||
<el-row :gutter="24">
|
||||
<el-col :xs="24" :md="8">
|
||||
<el-form-item label="专业名称">
|
||||
<el-input v-model="searchForm.zymc" placeholder="请输入专业名称" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :md="8">
|
||||
<el-form-item label="专业代码">
|
||||
<el-input v-model="searchForm.zydm" placeholder="请输入专业代码" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :md="8">
|
||||
<el-form-item label="培训类型">
|
||||
<el-select
|
||||
v-model="searchForm.pxlx"
|
||||
placeholder="请选择培训类型"
|
||||
clearable
|
||||
filterable
|
||||
class="w-full"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in trainingTypeOptions"
|
||||
:key="item.dictCode || item.dictValue"
|
||||
:label="item.dictLabel"
|
||||
:value="item.dictLabel"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :md="8">
|
||||
<el-form-item label="培训层次">
|
||||
<el-select
|
||||
v-model="searchForm.pxcc"
|
||||
placeholder="请选择培训层次"
|
||||
clearable
|
||||
filterable
|
||||
class="w-full"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in trainingLevelOptions"
|
||||
:key="item.dictCode || item.dictValue"
|
||||
:label="item.dictLabel"
|
||||
:value="item.dictLabel"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :md="8">
|
||||
<div class="search-actions">
|
||||
<el-button type="primary" icon="el-icon-search" @click="handleQuery">查询</el-button>
|
||||
<el-button icon="el-icon-refresh" @click="handleReset">重置</el-button>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<!-- ==================== 2. 操作与上传区域 ==================== -->
|
||||
<el-card shadow="never" class="action-card">
|
||||
<div class="action-row">
|
||||
<el-button type="primary" icon="el-icon-plus" @click="handleAdd">新建</el-button>
|
||||
<el-button icon="el-icon-download" @click="handleDownload">下载</el-button>
|
||||
</div>
|
||||
<div class="action-row">
|
||||
<el-button icon="el-icon-download" @click="handleTemplateDownload">【人才培养方案目录模板】下载</el-button>
|
||||
</div>
|
||||
<div class="upload-row">
|
||||
<div class="upload-left">
|
||||
<el-button icon="el-icon-folder-opened" @click="handleChooseFile">选择文件</el-button>
|
||||
<span class="file-name" :class="{ 'has-file': selectedFile }">
|
||||
<template v-if="selectedFile">{{ fileName }}</template>
|
||||
<template v-else>请选择要上传的文件</template>
|
||||
</span>
|
||||
<input ref="fileInputRef" type="file" style="display: none" @change="handleFileChange" />
|
||||
</div>
|
||||
<div class="upload-right">
|
||||
<el-button type="primary" icon="el-icon-upload2" :disabled="!selectedFile" @click="handleUpload">上传数据</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- ==================== 3. 数据表格区域 ==================== -->
|
||||
<el-card shadow="never" class="table-card">
|
||||
<div class="list-header">
|
||||
<div class="list-title">人才培养方案列表</div>
|
||||
</div>
|
||||
<el-table v-loading="loading" :data="tableData" border stripe highlight-current-row>
|
||||
<el-table-column type="index" label="序号" width="60" align="center" />
|
||||
<el-table-column prop="zydh" label="专业代号" width="120" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="zymc" label="专业名称" width="140" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="zyfx" label="专业方向" width="120" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="zydm" label="专业代码" width="110" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="pxlx" label="培训类型" width="110" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="pxcc" label="培训层次" width="120" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="pxlx2" label="培训类型2" width="110" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="xylb" label="学员类别" width="100" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="xnz" label="学年制" width="80" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="xqs" label="学期数" width="80" align="center" />
|
||||
<el-table-column prop="zybb" label="专业版本" width="100" align="center" show-overflow-tooltip />
|
||||
<el-table-column label="主干专业" width="90" align="center">
|
||||
<template slot-scope="scope">{{ fmtYesNo(scope.row.zgzy) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" width="80" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="isTrue(scope.row.ty) ? 'danger' : 'success'" size="mini">
|
||||
{{ isTrue(scope.row.ty) ? '停用' : '启用' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="启用时间" width="150" align="center">
|
||||
<template slot-scope="scope">{{ fmtDateTime(scope.row.qysj) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="停用时间" width="150" align="center">
|
||||
<template slot-scope="scope">{{ fmtDateTime(scope.row.tysj) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="240" align="center" fixed="right">
|
||||
<template slot-scope="scope">
|
||||
<el-button type="text" size="mini" icon="el-icon-view" @click="handleDetail(scope.row)">详情</el-button>
|
||||
<el-button type="text" size="mini" icon="el-icon-edit" @click="handleEdit(scope.row)">编辑</el-button>
|
||||
<el-button type="text" size="mini" icon="el-icon-circle-close" class="danger-text-btn"
|
||||
:disabled="isTrue(scope.row.ty)" @click="handleDisable(scope.row)">停用</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-pagination
|
||||
class="pagination"
|
||||
background
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:current-page="pageNum"
|
||||
:page-size="pageSize"
|
||||
:total="total"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handlePageChange"
|
||||
/>
|
||||
</el-card>
|
||||
|
||||
<!-- 新增/编辑弹窗 -->
|
||||
<el-dialog :title="dialog.title" :visible.sync="dialog.visible" width="860px" append-to-body
|
||||
:close-on-click-modal="false">
|
||||
<el-form ref="trainingForm" :model="dialog.form" :rules="rules" label-width="130px">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="专业代号" prop="zydh">
|
||||
<el-input v-model="dialog.form.zydh" placeholder="请输入专业代号" :disabled="dialog.isEdit" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="专业名称" prop="zymc">
|
||||
<el-input v-model="dialog.form.zymc" placeholder="请输入专业名称" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="专业代码" prop="zydm">
|
||||
<el-input v-model="dialog.form.zydm" placeholder="请输入专业代码" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="专业方向">
|
||||
<el-input v-model="dialog.form.zyfx" placeholder="请输入专业方向" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="培训层次" prop="pxcc">
|
||||
<el-select
|
||||
v-model="dialog.form.pxcc"
|
||||
placeholder="请选择培训层次"
|
||||
filterable
|
||||
class="training-dict-select"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in trainingLevelOptions"
|
||||
:key="item.dictCode || item.dictValue"
|
||||
:label="item.dictLabel"
|
||||
:value="item.dictLabel"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="培训类型" prop="pxlx">
|
||||
<el-select
|
||||
v-model="dialog.form.pxlx"
|
||||
placeholder="请选择培训类型"
|
||||
filterable
|
||||
class="training-dict-select"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in trainingTypeOptions"
|
||||
:key="item.dictCode || item.dictValue"
|
||||
:label="item.dictLabel"
|
||||
:value="item.dictLabel"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="培训类型2">
|
||||
<el-input v-model="dialog.form.pxlx2" placeholder="请输入培训类型2" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="学员类别">
|
||||
<el-input v-model="dialog.form.xylb" placeholder="请输入学员类别" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="学年制">
|
||||
<el-input v-model="dialog.form.xnz" placeholder="请输入学年制" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="学期数">
|
||||
<el-input-number v-model="dialog.form.xqs" :min="1" :max="20" controls-position="right" class="w-full" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="专业版本">
|
||||
<el-input v-model="dialog.form.zybb" placeholder="请输入专业版本" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="自定义分类">
|
||||
<el-input v-model="dialog.form.zdyfl" placeholder="请输入自定义分类" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="教学管理机构编号">
|
||||
<el-input v-model="dialog.form.jxgljgbh" placeholder="请输入教学管理机构编号" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="规范名称">
|
||||
<el-input v-model="dialog.form.gfmc" placeholder="请输入规范名称" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="专业规范">
|
||||
<el-input v-model="dialog.form.zygf" placeholder="请输入专业规范" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="简称">
|
||||
<el-input v-model="dialog.form.jc" placeholder="请输入简称" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="主干专业">
|
||||
<el-radio-group v-model="dialog.form.zgzy">
|
||||
<el-radio :label="true">是</el-radio>
|
||||
<el-radio :label="false">否</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="专业备注">
|
||||
<el-input v-model="dialog.form.zybz" placeholder="请输入专业备注" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="培养目标">
|
||||
<el-input v-model="dialog.form.pymb" type="textarea" :rows="3" placeholder="请输入培养目标" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="dialog.visible = false">取 消</el-button>
|
||||
<el-button type="primary" :loading="dialog.submitting" @click="submitDialog">确 定</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 详情弹窗 -->
|
||||
<el-dialog title="人才培养方案详情" :visible.sync="detail.visible" width="760px" append-to-body
|
||||
:close-on-click-modal="false">
|
||||
<div v-loading="detail.loading" class="detail-body">
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="专业代号">{{ fmtVal(detail.data.zydh) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="专业名称">{{ fmtVal(detail.data.zymc) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="专业代码">{{ fmtVal(detail.data.zydm) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="专业方向">{{ fmtVal(detail.data.zyfx) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="培训类型">{{ fmtVal(detail.data.pxlx) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="培训层次">{{ fmtVal(detail.data.pxcc) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="培训类型2">{{ fmtVal(detail.data.pxlx2) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="学员类别">{{ fmtVal(detail.data.xylb) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="学年制">{{ fmtVal(detail.data.xnz) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="学期数">{{ fmtVal(detail.data.xqs) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="专业版本">{{ fmtVal(detail.data.zybb) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="主干专业">{{ fmtYesNo(detail.data.zgzy) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="自定义分类">{{ fmtVal(detail.data.zdyfl) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="教学管理机构编号">{{ fmtVal(detail.data.jxgljgbh) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="规范名称">{{ fmtVal(detail.data.gfmc) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="专业规范">{{ fmtVal(detail.data.zygf) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="简称">{{ fmtVal(detail.data.jc) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="专业标识号">{{ fmtVal(detail.data.zybsh) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="学科专业信息标识号">{{ fmtVal(detail.data.xkzyxxbsh) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="教学大纲编号">{{ fmtVal(detail.data.jxdgbh) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="人培编号">{{ fmtVal(detail.data.rpbh) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="节次类别">{{ fmtVal(detail.data.jclb) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="系统模式">{{ fmtVal(detail.data.xtms) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="启用时间">{{ fmtDateTime(detail.data.qysj) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="停用时间">{{ fmtDateTime(detail.data.tysj) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="专业备注" :span="2">{{ fmtVal(detail.data.zybz) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="培养目标" :span="2">{{ fmtVal(detail.data.pymb) }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="detail.visible = false">关 闭</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
addTraining,
|
||||
disableTraining,
|
||||
updateTraining,
|
||||
getTraining,
|
||||
listTraining
|
||||
} from '@/api/teachBusiness/training'
|
||||
import { getDicts } from '@/api/system/dict/data'
|
||||
|
||||
const TRAINING_TYPE_DICT_CODE = 'train_type'
|
||||
const TRAINING_LEVEL_DICT_CODE = 'train_level'
|
||||
|
||||
export default {
|
||||
name: 'TrainingPlan',
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
// 查询条件(仅传后端 ZYBMapper 支持的字段)
|
||||
searchForm: {
|
||||
zymc: '',
|
||||
zydm: '',
|
||||
pxlx: '',
|
||||
pxcc: ''
|
||||
},
|
||||
trainingTypeOptions: [],
|
||||
trainingLevelOptions: [],
|
||||
// 列表
|
||||
tableData: [],
|
||||
total: 0,
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
// 上传
|
||||
selectedFile: null,
|
||||
fileName: '',
|
||||
// 新增/编辑弹窗
|
||||
dialog: {
|
||||
visible: false,
|
||||
title: '',
|
||||
isEdit: false,
|
||||
submitting: false,
|
||||
form: this.createEmptyForm()
|
||||
},
|
||||
// 详情弹窗
|
||||
detail: {
|
||||
visible: false,
|
||||
loading: false,
|
||||
data: {}
|
||||
},
|
||||
rules: {
|
||||
zydh: [{ required: true, message: '请输入专业代号', trigger: 'blur' }],
|
||||
zymc: [{ required: true, message: '请输入专业名称', trigger: 'blur' }],
|
||||
zydm: [{ required: true, message: '请输入专业代码', trigger: 'blur' }],
|
||||
pxcc: [{ required: true, message: '请选择培训层次', trigger: 'change' }],
|
||||
pxlx: [{ required: true, message: '请选择培训类型', trigger: 'change' }]
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadTrainingDictionaries()
|
||||
this.fetchList()
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 从系统字典加载培训类型和培训层次,提交标签以匹配现有业务数据。
|
||||
*/
|
||||
loadTrainingDictionaries() {
|
||||
return Promise.all([
|
||||
getDicts(TRAINING_TYPE_DICT_CODE),
|
||||
getDicts(TRAINING_LEVEL_DICT_CODE)
|
||||
]).then(([typeResponse, levelResponse]) => {
|
||||
this.trainingTypeOptions = typeResponse.data || []
|
||||
this.trainingLevelOptions = levelResponse.data || []
|
||||
}).catch(() => {
|
||||
this.trainingTypeOptions = []
|
||||
this.trainingLevelOptions = []
|
||||
})
|
||||
},
|
||||
|
||||
/* ---------- 列表加载 ---------- */
|
||||
fetchList() {
|
||||
this.loading = true
|
||||
const params = {
|
||||
pageNum: this.pageNum,
|
||||
pageSize: this.pageSize
|
||||
}
|
||||
Object.keys(this.searchForm).forEach(key => {
|
||||
const value = this.searchForm[key]
|
||||
if (value !== '' && value !== null && value !== undefined) {
|
||||
params[key] = value
|
||||
}
|
||||
})
|
||||
listTraining(params).then(response => {
|
||||
const data = response.data || {}
|
||||
this.tableData = data.records || []
|
||||
this.total = data.total || 0
|
||||
}).catch(() => {
|
||||
this.tableData = []
|
||||
this.total = 0
|
||||
}).finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
|
||||
/* ---------- 查询 / 重置 ---------- */
|
||||
handleQuery() {
|
||||
this.pageNum = 1
|
||||
this.fetchList()
|
||||
},
|
||||
handleReset() {
|
||||
this.searchForm = { zymc: '', zydm: '', pxlx: '', pxcc: '' }
|
||||
this.pageNum = 1
|
||||
this.fetchList()
|
||||
},
|
||||
|
||||
/* ---------- 分页 ---------- */
|
||||
handleSizeChange(size) {
|
||||
this.pageSize = size
|
||||
this.pageNum = 1
|
||||
this.fetchList()
|
||||
},
|
||||
handlePageChange(page) {
|
||||
this.pageNum = page
|
||||
this.fetchList()
|
||||
},
|
||||
|
||||
/* ---------- 新增 / 编辑 ---------- */
|
||||
handleAdd() {
|
||||
this.dialog.title = '新建人才培养方案'
|
||||
this.dialog.isEdit = false
|
||||
this.dialog.form = this.createEmptyForm()
|
||||
this.dialog.visible = true
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.trainingForm) this.$refs.trainingForm.clearValidate()
|
||||
})
|
||||
},
|
||||
handleEdit(row) {
|
||||
this.dialog.title = '编辑人才培养方案'
|
||||
this.dialog.isEdit = true
|
||||
this.dialog.form = Object.assign({}, this.createEmptyForm(), row)
|
||||
this.dialog.visible = true
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.trainingForm) this.$refs.trainingForm.clearValidate()
|
||||
})
|
||||
},
|
||||
submitDialog() {
|
||||
this.$refs.trainingForm.validate(valid => {
|
||||
if (!valid) return
|
||||
this.dialog.submitting = true
|
||||
const payload = this.cleanPayload(this.dialog.form)
|
||||
const isEdit = this.dialog.isEdit
|
||||
const req = isEdit ? updateTraining(payload) : addTraining(payload)
|
||||
req.then(() => {
|
||||
this.$message.success(isEdit ? '修改成功' : '新增成功')
|
||||
this.dialog.visible = false
|
||||
this.fetchList()
|
||||
}).catch(() => {}).finally(() => {
|
||||
this.dialog.submitting = false
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
/* ---------- 详情 ---------- */
|
||||
handleDetail(row) {
|
||||
this.detail.visible = true
|
||||
this.detail.loading = true
|
||||
this.detail.data = {}
|
||||
getTraining(row.zydh).then(response => {
|
||||
this.detail.data = response.data || {}
|
||||
}).catch(() => {
|
||||
this.detail.data = {}
|
||||
}).finally(() => {
|
||||
this.detail.loading = false
|
||||
})
|
||||
},
|
||||
|
||||
/* ---------- 停用(逻辑删除) ---------- */
|
||||
handleDisable(row) {
|
||||
this.$confirm('确定停用人才培养方案「' + (row.zymc || row.zydh) + '」吗?停用后列表不再展示。', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
return disableTraining(row.zydh)
|
||||
}).then(() => {
|
||||
this.$message.success('停用成功')
|
||||
this.fetchList()
|
||||
}).catch(() => {})
|
||||
},
|
||||
|
||||
/* ---------- 下载 / 模板下载 / 上传(后端未提供接口) ---------- */
|
||||
handleDownload() {
|
||||
this.$message.warning('后端暂未提供该接口')
|
||||
},
|
||||
handleTemplateDownload() {
|
||||
this.$message.warning('后端暂未提供该接口')
|
||||
},
|
||||
handleChooseFile() {
|
||||
this.$refs.fileInputRef && this.$refs.fileInputRef.click()
|
||||
},
|
||||
handleFileChange(e) {
|
||||
const file = e.target.files && e.target.files[0]
|
||||
this.selectedFile = file || null
|
||||
this.fileName = file ? file.name : ''
|
||||
},
|
||||
handleUpload() {
|
||||
this.$message.warning('后端暂未提供该接口')
|
||||
},
|
||||
|
||||
/* ---------- 工具 ---------- */
|
||||
createEmptyForm() {
|
||||
return {
|
||||
zydh: '',
|
||||
zymc: '',
|
||||
zyfx: '',
|
||||
zydm: '',
|
||||
pxcc: '',
|
||||
pxlx: '',
|
||||
pxlx2: '',
|
||||
xylb: '',
|
||||
xnz: '',
|
||||
xqs: null,
|
||||
zybb: '',
|
||||
zdyfl: '',
|
||||
jxgljgbh: '',
|
||||
gfmc: '',
|
||||
zygf: '',
|
||||
jc: '',
|
||||
zgzy: false,
|
||||
zybz: '',
|
||||
pymb: ''
|
||||
}
|
||||
},
|
||||
/** 移除空值(''/null/undefined),保留 0/false 等有效值 */
|
||||
cleanPayload(obj) {
|
||||
const payload = {}
|
||||
Object.keys(obj).forEach(key => {
|
||||
const value = obj[key]
|
||||
if (value !== '' && value !== null && value !== undefined) {
|
||||
payload[key] = value
|
||||
}
|
||||
})
|
||||
return payload
|
||||
},
|
||||
isTrue(val) {
|
||||
return val === true || val === 1 || val === '1' || val === 'true'
|
||||
},
|
||||
fmtYesNo(val) {
|
||||
return this.isTrue(val) ? '是' : '否'
|
||||
},
|
||||
fmtDateTime(val) {
|
||||
const s = (val || '').substring(0, 10)
|
||||
return s || '-'
|
||||
},
|
||||
fmtVal(val) {
|
||||
return val === '' || val === null || val === undefined ? '-' : val
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.app-container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.training-plan-page {
|
||||
.search-card {
|
||||
margin-bottom: 16px;
|
||||
|
||||
.search-form {
|
||||
.w-full {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.search-actions {
|
||||
padding-top: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.action-card {
|
||||
margin-bottom: 16px;
|
||||
|
||||
.action-row {
|
||||
margin-bottom: 12px;
|
||||
|
||||
&:last-of-type {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.upload-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
padding: 12px;
|
||||
background: #fafafa;
|
||||
border: 1px dashed #dcdfe6;
|
||||
border-radius: 4px;
|
||||
|
||||
.upload-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
|
||||
.file-name {
|
||||
font-size: 13px;
|
||||
color: #909399;
|
||||
|
||||
&.has-file {
|
||||
color: #409eff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.table-card {
|
||||
.list-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16px;
|
||||
|
||||
.list-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
}
|
||||
|
||||
.pagination {
|
||||
margin-top: 16px;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
.detail-body {
|
||||
min-height: 60px;
|
||||
}
|
||||
}
|
||||
|
||||
.danger-text-btn {
|
||||
color: #f56c6c;
|
||||
|
||||
&:hover {
|
||||
color: #f78989;
|
||||
}
|
||||
}
|
||||
|
||||
.training-dict-select {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,437 @@
|
||||
<template>
|
||||
<div class="app-container teach-notice">
|
||||
<!-- 查询条件区域 -->
|
||||
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" label-width="68px">
|
||||
<el-form-item label="标题" prop="title">
|
||||
<el-input
|
||||
v-model="queryParams.title"
|
||||
placeholder="请输入标题"
|
||||
clearable
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="公告类别">
|
||||
<el-select
|
||||
v-model="queryParams.category"
|
||||
placeholder="请选择"
|
||||
clearable
|
||||
style="width: 160px"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in categoryOptions"
|
||||
:key="item"
|
||||
:label="item"
|
||||
:value="item"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="发布日期">
|
||||
<el-date-picker
|
||||
v-model="queryParams.publishDateFrom"
|
||||
type="date"
|
||||
placeholder="从"
|
||||
value-format="yyyy-MM-dd"
|
||||
style="width: 140px"
|
||||
/>
|
||||
<span class="range-sep">-</span>
|
||||
<el-date-picker
|
||||
v-model="queryParams.publishDateTo"
|
||||
type="date"
|
||||
placeholder="到"
|
||||
value-format="yyyy-MM-dd"
|
||||
style="width: 140px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">查询</el-button>
|
||||
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- 数据列表区域 -->
|
||||
<div class="list-heading">
|
||||
<div class="list-title">公告列表</div>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-plus"
|
||||
size="mini"
|
||||
@click="handleAdd"
|
||||
>新增</el-button>
|
||||
</div>
|
||||
<el-table v-loading="loading" :data="noticeList" border :height="tableHeight">
|
||||
<el-table-column label="序号" type="index" align="center" width="70" />
|
||||
<el-table-column label="发布时间" align="center" width="165">
|
||||
<template slot-scope="scope">{{ formatTime(scope.row.publishTime) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="标题" align="left" prop="title" min-width="300" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="公告状态" align="center" width="100">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="statusTagType(scope.row.status)">{{ scope.row.status }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="公告类别" align="center" prop="category" width="110" />
|
||||
<el-table-column label="优先级" align="center" prop="priority" width="80" />
|
||||
<el-table-column label="阅读次数" align="center" prop="readCount" width="90" />
|
||||
<el-table-column label="接收/已读" align="center" width="110">
|
||||
<template slot-scope="scope">{{ scope.row.recipientCount }}/{{ scope.row.readerCount }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="操作"
|
||||
align="center"
|
||||
width="300"
|
||||
fixed="right"
|
||||
class-name="small-padding fixed-width action-column"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<el-button
|
||||
type="text"
|
||||
size="mini"
|
||||
icon="el-icon-view"
|
||||
@click="handleView(scope.row)"
|
||||
>详情</el-button>
|
||||
<el-button
|
||||
type="text"
|
||||
size="mini"
|
||||
icon="el-icon-edit"
|
||||
@click="handleUpdate(scope.row)"
|
||||
>修改</el-button>
|
||||
<el-button
|
||||
v-if="scope.row.status !== '已下架'"
|
||||
type="text"
|
||||
size="mini"
|
||||
icon="el-icon-download"
|
||||
@click="handleOffline(scope.row)"
|
||||
>下架</el-button>
|
||||
<el-button
|
||||
type="text"
|
||||
size="mini"
|
||||
icon="el-icon-delete"
|
||||
style="color: #f56c6c"
|
||||
@click="handleDelete(scope.row)"
|
||||
>删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination
|
||||
v-show="total > 0"
|
||||
:total="total"
|
||||
:page.sync="queryParams.pageNum"
|
||||
:limit.sync="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
|
||||
<!-- 新增/修改 对话框 -->
|
||||
<el-dialog
|
||||
:title="dialogTitle"
|
||||
:visible="dialogVisible"
|
||||
:close-on-click-modal="false"
|
||||
width="600px"
|
||||
@update:visible="handleDialogVisible"
|
||||
>
|
||||
<el-form ref="noticeForm" :model="form" :rules="rules" label-width="80px">
|
||||
<el-form-item label="公告标题" prop="title">
|
||||
<el-input v-model="form.title" placeholder="请输入公告标题" />
|
||||
</el-form-item>
|
||||
<el-form-item label="公告类别" prop="category">
|
||||
<el-select v-model="form.category" placeholder="请选择公告类别" clearable>
|
||||
<el-option v-for="item in categoryOptions" :key="item" :label="item" :value="item" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="公告正文" prop="content">
|
||||
<el-input type="textarea" v-model="form.content" rows="8" placeholder="请输入公告正文" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button type="primary" size="mini" @click="submitForm">确 定</el-button>
|
||||
<el-button size="mini" @click="handleDialogVisible(false)">取 消</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 详情 对话框 -->
|
||||
<el-dialog
|
||||
title="公告详情"
|
||||
:visible="viewVisible"
|
||||
:close-on-click-modal="false"
|
||||
width="600px"
|
||||
@update:visible="handleViewVisible"
|
||||
>
|
||||
<div class="detail-body">
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">公告标题</span>
|
||||
<span class="detail-value">{{ viewForm.title }}</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">公告类别</span>
|
||||
<span class="detail-value">{{ viewForm.category }}</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">公告状态</span>
|
||||
<span class="detail-value">{{ viewForm.status }}</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">优先级</span>
|
||||
<span class="detail-value">{{ viewForm.priority }}</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">发布时间</span>
|
||||
<span class="detail-value">{{ viewForm.publishTime }}</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">阅读次数</span>
|
||||
<span class="detail-value">{{ viewForm.readCount }}</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">接收/已读</span>
|
||||
<span class="detail-value">{{ viewForm.recipientCount }}/{{ viewForm.readerCount }}</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">发布范围</span>
|
||||
<span class="detail-value">{{ viewForm.scopeName }}</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">公告正文</span>
|
||||
<span class="detail-value detail-content">{{ viewForm.content }}</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">备注</span>
|
||||
<span class="detail-value">{{ viewForm.remark }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button size="mini" @click="handleViewVisible(false)">关 闭</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listTeachNotice, getTeachNotice, addTeachNotice, updateTeachNotice, delTeachNotice } from "@/api/teachNotice"
|
||||
|
||||
export default {
|
||||
name: "TeachNotice",
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
tableHeight: window.innerHeight - 320,
|
||||
total: 0,
|
||||
noticeList: [],
|
||||
categoryOptions: ['教学公告', '考试通知', '调课通知'],
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
title: undefined,
|
||||
category: undefined,
|
||||
publishDateFrom: undefined,
|
||||
publishDateTo: undefined
|
||||
},
|
||||
// 新增/修改 对话框
|
||||
dialogVisible: false,
|
||||
dialogTitle: '',
|
||||
form: {},
|
||||
rules: {
|
||||
title: [{ required: true, message: "公告标题不能为空", trigger: "blur" }],
|
||||
content: [{ required: true, message: "公告正文不能为空", trigger: "blur" }],
|
||||
category: [{ required: true, message: "公告类别不能为空", trigger: "change" }]
|
||||
},
|
||||
// 详情 对话框
|
||||
viewVisible: false,
|
||||
viewForm: {}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getList()
|
||||
},
|
||||
methods: {
|
||||
/** 查询列表 */
|
||||
getList() {
|
||||
this.loading = true
|
||||
listTeachNotice(this.queryParams).then(response => {
|
||||
const data = response.data || {}
|
||||
this.noticeList = data.records || []
|
||||
this.total = data.total || 0
|
||||
this.loading = false
|
||||
}).catch(() => {
|
||||
this.noticeList = []
|
||||
this.total = 0
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
/** 时间格式化:yyyy-MM-dd HH:mm:ss */
|
||||
formatTime(time) {
|
||||
if (!time) return ''
|
||||
const date = new Date(time)
|
||||
if (isNaN(date.getTime())) return time
|
||||
const pad = n => String(n).padStart(2, '0')
|
||||
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`
|
||||
},
|
||||
/** 状态标签颜色 */
|
||||
statusTagType(status) {
|
||||
if (status === '已发布') return 'success'
|
||||
if (status === '已下架') return 'info'
|
||||
return 'warning'
|
||||
},
|
||||
/** 查询按钮 */
|
||||
handleQuery() {
|
||||
this.queryParams.pageNum = 1
|
||||
this.getList()
|
||||
},
|
||||
/** 重置按钮 */
|
||||
resetQuery() {
|
||||
this.queryParams = {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
title: undefined,
|
||||
category: undefined,
|
||||
publishDateFrom: undefined,
|
||||
publishDateTo: undefined
|
||||
}
|
||||
this.handleQuery()
|
||||
},
|
||||
/** 新增 */
|
||||
handleAdd() {
|
||||
this.dialogTitle = '新增公告'
|
||||
this.form = { title: undefined, content: undefined, category: undefined }
|
||||
this.dialogVisible = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.noticeForm && this.$refs.noticeForm.clearValidate()
|
||||
})
|
||||
},
|
||||
/** 修改 */
|
||||
handleUpdate(row) {
|
||||
getTeachNotice(row.id).then(response => {
|
||||
const data = response.data || {}
|
||||
this.form = {
|
||||
id: data.id,
|
||||
title: data.title,
|
||||
content: data.content,
|
||||
category: data.category
|
||||
}
|
||||
}).catch(() => {})
|
||||
this.dialogTitle = '修改公告'
|
||||
this.dialogVisible = true
|
||||
},
|
||||
/** 提交新增/修改 */
|
||||
submitForm() {
|
||||
this.$refs.noticeForm.validate(valid => {
|
||||
if (!valid) return
|
||||
const isAdd = !this.form.id
|
||||
if (isAdd) {
|
||||
addTeachNotice(this.form).then(() => {
|
||||
this.$message.success("新增成功")
|
||||
this.dialogVisible = false
|
||||
this.getList()
|
||||
}).catch(() => {})
|
||||
} else {
|
||||
updateTeachNotice(this.form).then(() => {
|
||||
this.$message.success("修改成功")
|
||||
this.dialogVisible = false
|
||||
this.getList()
|
||||
}).catch(() => {})
|
||||
}
|
||||
})
|
||||
},
|
||||
/** 下架 */
|
||||
handleOffline(row) {
|
||||
this.$confirm("确认下架该公告吗?", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
}).then(() => {
|
||||
updateTeachNotice({ id: row.id, status: '已下架' }).then(() => {
|
||||
this.$message.success("下架成功")
|
||||
this.getList()
|
||||
}).catch(() => {})
|
||||
}).catch(() => {})
|
||||
},
|
||||
/** 删除(级联删除:仅拟制/已下架可删,后端校验) */
|
||||
handleDelete(row) {
|
||||
this.$confirm("确认删除该公告吗?", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
}).then(() => {
|
||||
delTeachNotice(row.id).then(() => {
|
||||
this.$message.success("删除成功")
|
||||
this.getList()
|
||||
}).catch(() => {})
|
||||
}).catch(() => {})
|
||||
},
|
||||
/** 详情 */
|
||||
handleView(row) {
|
||||
getTeachNotice(row.id).then(response => {
|
||||
this.viewForm = response.data || {}
|
||||
this.viewVisible = true
|
||||
}).catch(() => {})
|
||||
},
|
||||
/** 对话框关闭事件(Vue2 兼容) */
|
||||
handleDialogVisible(val) {
|
||||
this.dialogVisible = val
|
||||
},
|
||||
handleViewVisible(val) {
|
||||
this.viewVisible = val
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.teach-notice {
|
||||
.range-sep {
|
||||
margin: 0 8px;
|
||||
color: #909399;
|
||||
}
|
||||
.list-heading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.list-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
padding-left: 10px;
|
||||
border-left: 4px solid var(--edu-green-primary);
|
||||
}
|
||||
::v-deep .action-column .cell {
|
||||
white-space: nowrap;
|
||||
}
|
||||
.detail-body {
|
||||
padding: 0 8px;
|
||||
.detail-row {
|
||||
display: flex;
|
||||
padding: 6px 0;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
.detail-label {
|
||||
flex-shrink: 0;
|
||||
width: 90px;
|
||||
color: #909399;
|
||||
text-align: right;
|
||||
margin-right: 12px;
|
||||
}
|
||||
.detail-value {
|
||||
flex: 1;
|
||||
color: #303133;
|
||||
word-break: break-all;
|
||||
}
|
||||
.detail-content {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.list-heading {
|
||||
align-items: stretch;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.list-heading .el-button {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,550 @@
|
||||
<template>
|
||||
<div class="app-container management-page elective-page">
|
||||
<!-- ==================== 2. 查询条件区域 ==================== -->
|
||||
<div class="section-card query-card">
|
||||
<div class="section-heading">
|
||||
<div>
|
||||
<h2 class="section-title">筛选条件</h2>
|
||||
<p class="section-description">按课程、班次、教员及状态筛选选修课</p>
|
||||
</div>
|
||||
<el-button type="primary" icon="el-icon-search" @click="handleQuery">查询</el-button>
|
||||
</div>
|
||||
<el-form>
|
||||
<el-row :gutter="32">
|
||||
<!-- 左栏 -->
|
||||
<el-col :xs="24" :md="12">
|
||||
<div class="query-field">
|
||||
<span class="q-label no-check">授课教员</span>
|
||||
<el-input v-model="searchForm.js" placeholder="请输入授课教员" clearable class="q-control" />
|
||||
</div>
|
||||
<div class="query-field">
|
||||
<span class="q-label no-check">可选班次年级</span>
|
||||
<el-input v-model="searchForm.kbcnj" placeholder="请输入可选班次年级" clearable class="q-control" />
|
||||
</div>
|
||||
<div class="query-field">
|
||||
<span class="q-label no-check">选修课状态</span>
|
||||
<el-select
|
||||
v-model="searchForm.xxkzt"
|
||||
placeholder="请选择选修课状态"
|
||||
clearable
|
||||
class="q-control"
|
||||
>
|
||||
<el-option v-for="item in statusOptions" :key="item" :label="item" :value="item" />
|
||||
</el-select>
|
||||
</div>
|
||||
</el-col>
|
||||
|
||||
<!-- 右栏 -->
|
||||
<el-col :xs="24" :md="12">
|
||||
<div class="query-field">
|
||||
<span class="q-label no-check">课程科目名称</span>
|
||||
<el-input v-model="searchForm.kckmmc" placeholder="请输入课程科目名称" clearable class="q-control" />
|
||||
</div>
|
||||
<div class="query-field">
|
||||
<span class="q-label no-check">选修班名称</span>
|
||||
<el-input v-model="searchForm.xxbmc" placeholder="请输入选修班名称" clearable class="q-control" />
|
||||
</div>
|
||||
<div class="query-field">
|
||||
<span class="q-label no-check">培训层次</span>
|
||||
<el-select v-model="searchForm.pxcc" placeholder="请选择培训层次" clearable class="q-control">
|
||||
<el-option v-for="item in pxccOptions" :key="item" :label="item" :value="item" />
|
||||
</el-select>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- ==================== 3. 导出按钮区域 ==================== -->
|
||||
<div class="section-card export-card">
|
||||
<div class="section-heading section-heading--compact">
|
||||
<div>
|
||||
<h2 class="section-title">数据导出</h2>
|
||||
<p class="section-description">下载选修班及学员名单</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="export-bar">
|
||||
<el-button icon="el-icon-download" @click="handleDownloadList">下载选修班列表</el-button>
|
||||
<el-button icon="el-icon-download" @click="handleDownloadWord">下载选修班学员名单Word</el-button>
|
||||
<el-button icon="el-icon-download" @click="handleDownloadExcel">下载选修班学员名单Excel</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ==================== 4. 操作按钮区域 ==================== -->
|
||||
<div class="section-card action-card">
|
||||
<div class="section-heading section-heading--compact">
|
||||
<div>
|
||||
<h2 class="section-title">选修课操作</h2>
|
||||
<p class="section-description">新建选修课,或对已选数据执行批量操作</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="action-row">
|
||||
<el-button type="primary" icon="el-icon-plus" @click="handleOpenNew">新建选修课</el-button>
|
||||
<el-button type="primary" plain icon="el-icon-circle-check" @click="handleOpenEarly">
|
||||
所选批量开放报名
|
||||
</el-button>
|
||||
<el-button type="warning" plain icon="el-icon-remove-outline" @click="handleCancelEarly">
|
||||
所选批量取消开放报名
|
||||
</el-button>
|
||||
<el-button type="danger" plain icon="el-icon-circle-close" @click="handleStopSignup">
|
||||
批量停止报名
|
||||
</el-button>
|
||||
<el-button icon="el-icon-refresh" @click="handleReverseRange">
|
||||
所选批量根据学员反向确定班次范围
|
||||
</el-button>
|
||||
</div>
|
||||
<div class="action-row">
|
||||
<el-button icon="el-icon-download" @click="handleDownloadTemplate">【选修课数据文件模板】下载</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ==================== 5. 文件上传区域 ==================== -->
|
||||
<div class="section-card upload-card">
|
||||
<div class="section-heading section-heading--compact">
|
||||
<div>
|
||||
<h2 class="section-title">数据导入</h2>
|
||||
<p class="section-description">选择填写完成的模板文件并上传</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="upload-row">
|
||||
<div class="upload-left">
|
||||
<el-button icon="el-icon-folder-opened" @click="handleSelectFile">选择文件</el-button>
|
||||
<span class="file-name" :class="{ 'has-file': selectedFile }">{{ fileName }}</span>
|
||||
<input ref="fileInputRef" type="file" style="display: none" @change="handleFileChange" />
|
||||
</div>
|
||||
<div class="upload-right">
|
||||
<el-button type="primary" icon="el-icon-upload2" @click="handleUpload">上传数据</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ==================== 6. 数据表格区域 ==================== -->
|
||||
<div class="section-card table-card">
|
||||
<div class="section-heading section-heading--compact table-heading">
|
||||
<div>
|
||||
<h2 class="section-title">选修课列表</h2>
|
||||
<p class="section-description">共 {{ total }} 条数据</p>
|
||||
</div>
|
||||
</div>
|
||||
<el-table v-loading="loading" :data="tableData" border stripe class="main-table" @selection-change="handleSelectionChange">
|
||||
<template slot="empty">
|
||||
<span>无数据!</span>
|
||||
</template>
|
||||
<el-table-column type="selection" width="40" align="center" />
|
||||
<el-table-column type="index" label="序号" width="60" align="center" :index="tableIndex" />
|
||||
<el-table-column prop="kmc" label="课程" min-width="110" show-overflow-tooltip />
|
||||
<el-table-column prop="jcmc" label="教材" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column prop="jyxm" label="实施教员" width="90" align="center" />
|
||||
<el-table-column prop="jsmc" label="教学场地" width="100" show-overflow-tooltip />
|
||||
<el-table-column prop="kxdlbc" label="显示可选队别班次" min-width="150" align="center" show-overflow-tooltip />
|
||||
<el-table-column label="计划学时/运行学时/学分" width="130" align="center">
|
||||
<template slot="header">
|
||||
<div class="triple-header">
|
||||
<span>计划学时</span>
|
||||
<span>运行学时</span>
|
||||
<span>学分</span>
|
||||
</div>
|
||||
</template>
|
||||
<template slot-scope="{ row }">
|
||||
<div class="triple-cell">
|
||||
<span>{{ row.jhyxxsxf }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="开课结课日期" min-width="150" align="center">
|
||||
<template slot-scope="{ row }">
|
||||
{{ row.kkjkrq }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="报名日期" min-width="140" align="center">
|
||||
<template slot-scope="{ row }">
|
||||
{{ row.bmrq }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="jhrs" label="计划人数" width="90" align="center" />
|
||||
<el-table-column prop="yqsm" label="要求说明" min-width="120" show-overflow-tooltip />
|
||||
</el-table>
|
||||
|
||||
<div class="pagination-wrap">
|
||||
<el-pagination
|
||||
background
|
||||
layout="total, prev, pager, next, jumper"
|
||||
:total="total"
|
||||
:page-size="pageSize"
|
||||
:current-page="currentPage"
|
||||
@current-change="handlePageChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 新建选修课弹窗 -->
|
||||
<el-dialog
|
||||
:visible="dialogVisible"
|
||||
title="新建选修课"
|
||||
width="560px"
|
||||
@update:visible="val => (dialogVisible = val)"
|
||||
>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="110px">
|
||||
<el-form-item label="课程名称" prop="kcmc">
|
||||
<el-input v-model="form.kcmc" placeholder="请输入课程名称" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="实施教员" prop="js">
|
||||
<el-input v-model="form.js" placeholder="请输入实施教员" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="教材">
|
||||
<el-input v-model="form.jc" placeholder="请输入教材" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="教学场地">
|
||||
<el-input v-model="form.jxcd" placeholder="请输入教学场地" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="计划学时">
|
||||
<el-input-number v-model="form.jhsxs" :min="0" :precision="0" class="w-full" />
|
||||
</el-form-item>
|
||||
<el-form-item label="运行学时">
|
||||
<el-input-number v-model="form.yxsxs" :min="0" :precision="0" class="w-full" />
|
||||
</el-form-item>
|
||||
<el-form-item label="学分">
|
||||
<el-input-number v-model="form.xf" :min="0" :precision="1" class="w-full" />
|
||||
</el-form-item>
|
||||
<el-form-item label="计划人数">
|
||||
<el-input-number v-model="form.jh" :min="0" :precision="0" class="w-full" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer">
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="handleNewConfirm">确定</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
listElective,
|
||||
batchOpenElective,
|
||||
batchCancelOpenElective,
|
||||
batchStopElective,
|
||||
exportElectiveList,
|
||||
exportElectiveStudentsExcel,
|
||||
exportElectiveStudentsWord
|
||||
} from '@/api/teachOffice/elective'
|
||||
import { saveAs } from 'file-saver'
|
||||
|
||||
export default {
|
||||
name: 'Elective',
|
||||
data() {
|
||||
return {
|
||||
// ==================== 下拉选项 ====================
|
||||
statusOptions: ['拟制', '开放报名', '停止报名', '已开课'],
|
||||
pxccOptions: ['无', '本科', '硕士', '博士'],
|
||||
|
||||
// ==================== 查询条件 ====================
|
||||
searchForm: {
|
||||
js: '',
|
||||
kbcnj: '',
|
||||
xxkzt: '',
|
||||
kckmmc: '',
|
||||
xxbmc: '',
|
||||
pxcc: ''
|
||||
},
|
||||
|
||||
// ==================== 数据表格 ====================
|
||||
tableData: [],
|
||||
total: 0,
|
||||
loading: false,
|
||||
selectedRows: [],
|
||||
|
||||
// ==================== 文件上传 ====================
|
||||
selectedFile: null,
|
||||
|
||||
// ==================== 新建选修课弹窗 ====================
|
||||
dialogVisible: false,
|
||||
form: {
|
||||
kcmc: '',
|
||||
js: '',
|
||||
jc: '',
|
||||
jxcd: '',
|
||||
jhsxs: 0,
|
||||
yxsxs: 0,
|
||||
xf: 0,
|
||||
jh: 0
|
||||
},
|
||||
rules: {
|
||||
kcmc: [{ required: true, message: '请输入课程名称', trigger: 'blur' }],
|
||||
js: [{ required: true, message: '请输入实施教员', trigger: 'blur' }]
|
||||
},
|
||||
|
||||
// ==================== 分页 ====================
|
||||
currentPage: 1,
|
||||
pageSize: 20
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
fileName() {
|
||||
return (this.selectedFile && this.selectedFile.name) || '未选择任何文件'
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadList()
|
||||
},
|
||||
methods: {
|
||||
// 组装查询过滤条件(列表与导出共用,导出不含分页参数)
|
||||
buildFilter() {
|
||||
const q = {}
|
||||
if (this.searchForm.js) q.jyxm = this.searchForm.js
|
||||
if (this.searchForm.kbcnj) q.kxbcnj = this.searchForm.kbcnj
|
||||
if (this.searchForm.kckmmc) q.kmc = this.searchForm.kckmmc
|
||||
if (this.searchForm.xxbmc) q.xxbmc = this.searchForm.xxbmc
|
||||
if (this.searchForm.xxkzt) q.xkzt = this.searchForm.xxkzt
|
||||
if (this.searchForm.pxcc) q.pxcc = this.searchForm.pxcc
|
||||
return q
|
||||
},
|
||||
|
||||
// ==================== 查询 ====================
|
||||
handleQuery() {
|
||||
this.currentPage = 1
|
||||
this.loadList()
|
||||
},
|
||||
loadList() {
|
||||
this.loading = true
|
||||
const params = Object.assign({ pageNum: this.currentPage, pageSize: this.pageSize }, this.buildFilter())
|
||||
listElective(params)
|
||||
.then(res => {
|
||||
const data = (res && res.data) || {}
|
||||
this.tableData = data.records || []
|
||||
this.total = data.total || 0
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
|
||||
// ==================== 导出 ====================
|
||||
handleDownloadList() {
|
||||
exportElectiveList(this.buildFilter())
|
||||
.then(blob => {
|
||||
saveAs(blob, '选修班列表.xlsx')
|
||||
this.$message.success('选修班列表已下载')
|
||||
})
|
||||
.catch(() => {})
|
||||
},
|
||||
handleDownloadWord() {
|
||||
const bhList = this.selectedRows.map(row => row.bh)
|
||||
if (bhList.length === 0) {
|
||||
this.$message.warning('请先选择选修班')
|
||||
return
|
||||
}
|
||||
exportElectiveStudentsWord(bhList)
|
||||
.then(blob => {
|
||||
saveAs(blob, '选修班学员名单.docx')
|
||||
this.$message.success('选修班学员名单Word已下载')
|
||||
})
|
||||
.catch(() => {})
|
||||
},
|
||||
handleDownloadExcel() {
|
||||
const bhList = this.selectedRows.map(row => row.bh)
|
||||
if (bhList.length === 0) {
|
||||
this.$message.warning('请先选择选修班')
|
||||
return
|
||||
}
|
||||
exportElectiveStudentsExcel(bhList)
|
||||
.then(blob => {
|
||||
saveAs(blob, '选修班学员名单.xlsx')
|
||||
this.$message.success('选修班学员名单Excel已下载')
|
||||
})
|
||||
.catch(() => {})
|
||||
},
|
||||
|
||||
// ==================== 操作按钮 ====================
|
||||
handleOpenEarly() {
|
||||
const bhList = this.selectedRows.map(row => row.bh)
|
||||
if (bhList.length === 0) {
|
||||
this.$message.warning('请先选择选修班')
|
||||
return
|
||||
}
|
||||
batchOpenElective(bhList)
|
||||
.then(() => {
|
||||
this.$message.success('所选班已开放报名')
|
||||
this.loadList()
|
||||
})
|
||||
.catch(() => {})
|
||||
},
|
||||
handleCancelEarly() {
|
||||
const bhList = this.selectedRows.map(row => row.bh)
|
||||
if (bhList.length === 0) {
|
||||
this.$message.warning('请先选择选修班')
|
||||
return
|
||||
}
|
||||
batchCancelOpenElective(bhList)
|
||||
.then(() => {
|
||||
this.$message.success('所选班已取消开放报名')
|
||||
this.loadList()
|
||||
})
|
||||
.catch(() => {})
|
||||
},
|
||||
handleStopSignup() {
|
||||
const bhList = this.selectedRows.map(row => row.bh)
|
||||
if (bhList.length === 0) {
|
||||
this.$message.warning('请先选择选修班')
|
||||
return
|
||||
}
|
||||
batchStopElective(bhList)
|
||||
.then(() => {
|
||||
this.$message.success('已批量停止报名')
|
||||
this.loadList()
|
||||
})
|
||||
.catch(() => {})
|
||||
},
|
||||
handleReverseRange() {
|
||||
this.$message.warning('后端暂未提供该接口')
|
||||
},
|
||||
handleDownloadTemplate() {
|
||||
this.$message.warning('后端暂未提供该接口')
|
||||
},
|
||||
|
||||
// ==================== 文件上传 ====================
|
||||
handleSelectFile() {
|
||||
this.$refs.fileInputRef && this.$refs.fileInputRef.click()
|
||||
},
|
||||
handleFileChange(e) {
|
||||
this.selectedFile = e.target.files[0] || null
|
||||
},
|
||||
handleUpload() {
|
||||
if (!this.selectedFile) {
|
||||
this.$message.warning('请先选择文件')
|
||||
return
|
||||
}
|
||||
this.$message.warning('后端暂未提供该接口')
|
||||
},
|
||||
|
||||
// ==================== 表格多选 ====================
|
||||
handleSelectionChange(rows) {
|
||||
this.selectedRows = rows
|
||||
},
|
||||
|
||||
// ==================== 新建选修课弹窗 ====================
|
||||
handleOpenNew() {
|
||||
this.$message.warning('后端暂未提供该接口')
|
||||
},
|
||||
handleNewConfirm() {
|
||||
if (!this.$refs.formRef) return
|
||||
this.$refs.formRef.validate((valid) => {
|
||||
if (!valid) return
|
||||
console.log('新建选修课:', JSON.parse(JSON.stringify(this.form)))
|
||||
this.$message.success('新建选修课成功(前端模拟)')
|
||||
this.dialogVisible = false
|
||||
})
|
||||
},
|
||||
|
||||
// ==================== 分页 ====================
|
||||
handlePageChange(page) {
|
||||
this.currentPage = page
|
||||
this.loadList()
|
||||
},
|
||||
|
||||
// 表格序号(跨页连续)
|
||||
tableIndex(index) {
|
||||
return (this.currentPage - 1) * this.pageSize + index + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
// ==================== 2. 查询条件区域 ====================
|
||||
.query-card {
|
||||
.query-field {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 14px;
|
||||
min-height: 32px;
|
||||
|
||||
.q-label {
|
||||
width: 120px;
|
||||
flex-shrink: 0;
|
||||
font-size: 13px;
|
||||
color: #303133;
|
||||
text-align: right;
|
||||
margin-right: 10px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.q-control {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.export-bar {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
// ==================== 4. 操作按钮区域 ====================
|
||||
.action-card {
|
||||
.action-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
margin-bottom: 12px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 5. 文件上传区域 ====================
|
||||
.upload-card {
|
||||
.upload-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
|
||||
.upload-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
|
||||
.file-name {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
|
||||
&.has-file {
|
||||
color: #303133;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.upload-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 6. 数据表格区域 ====================
|
||||
.table-card {
|
||||
.main-table {
|
||||
::v-deep .cell {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.triple-header,
|
||||
.triple-cell {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.pagination-wrap {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 16px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,752 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
:visible="dialogVisible"
|
||||
:title="`班历管理——${semesterTitle}`"
|
||||
width="1200px"
|
||||
top="6vh"
|
||||
:close-on-click-modal="false"
|
||||
class="class-calendar-dialog"
|
||||
@update:visible="val => dialogVisible = val"
|
||||
>
|
||||
<div v-loading="loading" class="class-calendar">
|
||||
<!-- ==================== 1. 学期与班次信息 ==================== -->
|
||||
<div class="info-panel">
|
||||
<div class="info-title-row">
|
||||
<span class="semester-name">{{ semesterTitle }}</span>
|
||||
<span class="class-name">{{ className }}</span>
|
||||
</div>
|
||||
<div class="date-range-row">
|
||||
<div class="range-item">学期日期:{{ fmtDate(safeSemester.kxrq) }} 至 {{ fmtDate(safeSemester.jsrq) }},共 {{ semesterWeeks }} 周</div>
|
||||
<div class="range-item">班历记录:{{ records.length }} 条,总学时 {{ summary.totalXs }},总学分 {{ summary.totalXf }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ==================== 2. 操作按钮区域 ==================== -->
|
||||
<div class="settings-panel">
|
||||
<div class="action-buttons-row">
|
||||
<el-button type="primary" size="small" icon="el-icon-plus" @click="handleAdd">新增记录</el-button>
|
||||
<el-button size="small" @click="handleAutoGenerate">自动生成必修课程</el-button>
|
||||
<el-button size="small" @click="handlePresetMerge">预设合班</el-button>
|
||||
<el-button size="small" @click="handlePresetSplit">预设拆班</el-button>
|
||||
<el-button size="small" @click="handleApplyRegion">选定区域应用于其它班次</el-button>
|
||||
<el-button size="small" @click="handleApplyWhole">整个班历应用于其它班次</el-button>
|
||||
<el-button size="small" icon="el-icon-refresh" @click="handleRefresh">刷新</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ==================== 3. 班历记录表格 ==================== -->
|
||||
<div class="table-area">
|
||||
<el-table
|
||||
ref="tableRef"
|
||||
:data="records"
|
||||
border
|
||||
stripe
|
||||
size="small"
|
||||
max-height="480"
|
||||
class="calendar-table"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<el-table-column type="selection" width="40" align="center" />
|
||||
<el-table-column type="index" label="序号" width="55" align="center" />
|
||||
<el-table-column prop="kcxh" label="课次序号" width="80" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="jhkcxh" label="计划课次" width="80" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="jc" label="简称" width="100" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="kbh" label="课编号" width="120" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="klx" label="课类型" width="80" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="jybh" label="教员编号" width="100" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="jsbh" label="教室编号" width="100" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="zks" label="周课时" width="70" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="xs" label="学时" width="60" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="llxs" label="理论学时" width="80" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="sjxs" label="实践学时" width="80" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="xf" label="学分" width="60" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="rs" label="人数" width="60" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="ksbh" label="考试编号" width="100" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="ksdd" label="考试地点" width="110" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="bz" label="备注" min-width="120" align="left" header-align="center" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="120" align="center" fixed="right">
|
||||
<template slot-scope="{ row }">
|
||||
<el-button type="text" size="small" @click="handleEdit(row)">编辑</el-button>
|
||||
<el-button type="text" size="small" class="text-danger" @click="handleDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="select-tip">
|
||||
提示:勾选多条记录后可进行 预设合班 / 预设拆班 / 选定区域应用于其它班次 操作;删除为物理删除,请谨慎操作。
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div slot="footer">
|
||||
<el-button @click="dialogVisible = false">关闭</el-button>
|
||||
</div>
|
||||
|
||||
<!-- ==================== 班历记录新增/编辑弹窗 ==================== -->
|
||||
<el-dialog
|
||||
:visible="editVisible"
|
||||
:title="editTitle"
|
||||
width="760px"
|
||||
:close-on-click-modal="false"
|
||||
append-to-body
|
||||
@update:visible="val => editVisible = val"
|
||||
>
|
||||
<el-form ref="editFormRef" :model="editForm" :rules="editRules" label-width="120px" size="small" class="edit-form">
|
||||
<div class="form-section-title">基本信息</div>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="简称" prop="jc">
|
||||
<el-input v-model="editForm.jc" placeholder="课程简称" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="课编号">
|
||||
<el-input v-model="editForm.kbh" placeholder="课程编号" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="课类型">
|
||||
<el-input v-model="editForm.klx" placeholder="如 必修/选修/实践" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="课次序号">
|
||||
<el-input-number v-model="editForm.kcxh" :min="1" controls-position="right" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="计划课次序号">
|
||||
<el-input-number v-model="editForm.jhkcxh" :min="1" controls-position="right" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="序号标识">
|
||||
<el-input-number v-model="editForm.xhbs" :min="1" controls-position="right" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="教员编号">
|
||||
<el-input v-model="editForm.jybh" placeholder="教员编号" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="教室编号">
|
||||
<el-input v-model="editForm.jsbh" placeholder="教室编号" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="人数">
|
||||
<el-input-number v-model="editForm.rs" :min="1" controls-position="right" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="周课时">
|
||||
<el-input-number v-model="editForm.zks" :min="1" controls-position="right" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<div class="form-section-title">学时学分</div>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="学时">
|
||||
<el-input-number v-model="editForm.xs" :min="1" controls-position="right" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="理论学时">
|
||||
<el-input-number v-model="editForm.llxs" :min="0" controls-position="right" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="实践学时">
|
||||
<el-input-number v-model="editForm.sjxs" :min="0" controls-position="right" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="学分">
|
||||
<el-input-number v-model="editForm.xf" :min="0" :precision="1" :step="0.5" controls-position="right" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<div class="form-section-title">考试信息</div>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="考试课时">
|
||||
<el-input-number v-model="editForm.ksks" :min="0" controls-position="right" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="考试编号">
|
||||
<el-input v-model="editForm.ksbh" placeholder="考试编号" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="考试地点">
|
||||
<el-input v-model="editForm.ksdd" placeholder="考试地点" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="成绩分制">
|
||||
<el-input v-model="editForm.cjfz" placeholder="如 百分制/等级制" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="不计入平均分">
|
||||
<el-select v-model="editForm.bjrxypjf" clearable style="width: 100%">
|
||||
<el-option label="是" :value="1" />
|
||||
<el-option label="否" :value="0" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="考试课时不显示">
|
||||
<el-select v-model="editForm.ksksbxs" clearable style="width: 100%">
|
||||
<el-option label="是" :value="1" />
|
||||
<el-option label="否" :value="0" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<div class="form-section-title">教研室计划</div>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="教研室代号">
|
||||
<el-input v-model="editForm.jysdh" placeholder="教研室代号" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="计划教员编号">
|
||||
<el-input v-model="editForm.jysjhjybh" placeholder="教研室计划教员编号" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="计划备注">
|
||||
<el-input v-model="editForm.jysjhbz" type="textarea" :rows="2" placeholder="教研室计划备注" clearable />
|
||||
</el-form-item>
|
||||
<div class="form-section-title">配档设置</div>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="配档序号">
|
||||
<el-input-number v-model="editForm.pdxh" :min="1" controls-position="right" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="配档起始周">
|
||||
<el-input-number v-model="editForm.pdqsz" :min="1" controls-position="right" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="配档按周">
|
||||
<el-input-number v-model="editForm.pdaz" :min="1" controls-position="right" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="占正课时间">
|
||||
<el-input-number v-model="editForm.pdzzksj" :min="0" controls-position="right" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="配档编组">
|
||||
<el-input-number v-model="editForm.pdbz" :min="1" controls-position="right" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="同班异课选修编组">
|
||||
<el-select v-model="editForm.pdtbykxxbz" clearable style="width: 100%">
|
||||
<el-option label="是" :value="1" />
|
||||
<el-option label="否" :value="0" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="启用自定义学时">
|
||||
<el-select v-model="editForm.pdqyzdyxs" clearable style="width: 100%">
|
||||
<el-option label="是" :value="1" />
|
||||
<el-option label="否" :value="0" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="编组">
|
||||
<el-input-number v-model="editForm.bz2" :min="0" controls-position="right" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="自定义学时分布">
|
||||
<el-input v-model="editForm.pdzdyxsfbsj" type="textarea" :rows="2" placeholder="配档自定义学时分布数据" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="editForm.bz" type="textarea" :rows="2" placeholder="备注" clearable />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer">
|
||||
<el-button size="small" @click="editVisible = false">取消</el-button>
|
||||
<el-button size="small" type="primary" :loading="saving" @click="handleEditSubmit">确定</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- ==================== 目标班次学期选取弹窗 ==================== -->
|
||||
<ClassTeamSelectDialog
|
||||
:visible="teamSelectVisible"
|
||||
:semester-list="semesterOptions"
|
||||
:exclude-bh="safeSemester.bh"
|
||||
:exclude-note="copyNote"
|
||||
@update:visible="val => teamSelectVisible = val"
|
||||
@confirm="handleTeamConfirm"
|
||||
/>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ClassTeamSelectDialog from './ClassTeamSelectDialog.vue'
|
||||
import {
|
||||
listClassCalendar,
|
||||
updateClassCalendar,
|
||||
batchCopyCalendar,
|
||||
batchCopyCalendarBySemester,
|
||||
addTeamTask,
|
||||
delTeamTask,
|
||||
batchPresetMerge,
|
||||
batchPresetSplit,
|
||||
autoGenerateRequiredCourses
|
||||
} from '@/api/studentRecords/classCalendar'
|
||||
|
||||
export default {
|
||||
name: 'ClassCalendarDialog',
|
||||
components: { ClassTeamSelectDialog },
|
||||
props: {
|
||||
visible: { type: Boolean, default: false },
|
||||
/** 选中的班次学期记录(含 bh/xydbh/xqdc/nd/xydmc/kxrq/jsrq) */
|
||||
semester: { type: Object, default: null },
|
||||
/** 可选的目标班次学期列表(已含班次名称的完整列表) */
|
||||
semesterOptions: { type: Array, default: () => [] }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
saving: false,
|
||||
records: [],
|
||||
selection: [],
|
||||
|
||||
// ==================== 新增/编辑 ====================
|
||||
editVisible: false,
|
||||
editMode: 'add',
|
||||
editForm: this.createEmptyEditForm(),
|
||||
editRules: {
|
||||
jc: [{ required: true, message: '请输入课程简称', trigger: 'blur' }]
|
||||
},
|
||||
|
||||
// ==================== 复制到其它班次 ====================
|
||||
teamSelectVisible: false,
|
||||
applyMode: 'region',
|
||||
copyNote: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
dialogVisible: {
|
||||
get() {
|
||||
return this.visible
|
||||
},
|
||||
set(val) {
|
||||
this.$emit('update:visible', val)
|
||||
}
|
||||
},
|
||||
safeSemester() {
|
||||
return this.semester || {}
|
||||
},
|
||||
className() {
|
||||
const s = this.safeSemester
|
||||
return s.xydmc || s.xydbh || ''
|
||||
},
|
||||
semesterTitle() {
|
||||
const s = this.safeSemester
|
||||
const nd = s.nd !== undefined && s.nd !== null && s.nd !== '' ? `${s.nd}年` : ''
|
||||
const xq = this.xqdcLabel(s.xqdc)
|
||||
return `${nd}${xq}`
|
||||
},
|
||||
semesterWeeks() {
|
||||
const s = this.safeSemester
|
||||
if (!s.kxrq || !s.jsrq) return '-'
|
||||
const start = new Date(String(s.kxrq).slice(0, 10))
|
||||
const end = new Date(String(s.jsrq).slice(0, 10))
|
||||
const diff = Math.round((end - start) / (1000 * 60 * 60 * 24)) + 1
|
||||
return diff > 0 ? Math.ceil(diff / 7) : '-'
|
||||
},
|
||||
/** 班历统计:课程数 / 总学时 / 总学分 */
|
||||
summary() {
|
||||
const totalXs = this.records.reduce((sum, r) => sum + (Number(r.xs) || 0), 0)
|
||||
const totalXf = this.records.reduce((sum, r) => sum + (Number(r.xf) || 0), 0)
|
||||
return { totalXs, totalXf }
|
||||
},
|
||||
editTitle() {
|
||||
return this.editMode === 'edit' ? '编辑班历记录' : '新增班历记录'
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
visible(val) {
|
||||
if (val) {
|
||||
this.loadData()
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/* ---------- 格式化 ---------- */
|
||||
fmtDate(val) {
|
||||
if (!val) return '-'
|
||||
return String(val).slice(0, 10)
|
||||
},
|
||||
xqdcLabel(val) {
|
||||
if (val === 1 || val === '1') return '第1学期'
|
||||
if (val === 2 || val === '2') return '第2学期'
|
||||
if (val === 3 || val === '3') return '第3学期'
|
||||
return val !== undefined && val !== null && val !== '' ? `第${val}学期` : '-'
|
||||
},
|
||||
cleanPayload(obj) {
|
||||
const payload = {}
|
||||
Object.keys(obj).forEach(key => {
|
||||
const value = obj[key]
|
||||
if (value !== '' && value !== null && value !== undefined) {
|
||||
payload[key] = value
|
||||
}
|
||||
})
|
||||
return payload
|
||||
},
|
||||
|
||||
/* ---------- 列表加载 ---------- */
|
||||
loadData() {
|
||||
const s = this.semester || {}
|
||||
if (!s.bh) return
|
||||
this.loading = true
|
||||
listClassCalendar(s.bh).then(res => {
|
||||
this.records = (res && res.data) || []
|
||||
this.loading = false
|
||||
}).catch(() => {
|
||||
this.records = []
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
|
||||
handleSelectionChange(rows) {
|
||||
this.selection = rows
|
||||
},
|
||||
|
||||
/* ---------- 新增 / 编辑 ---------- */
|
||||
createEmptyEditForm() {
|
||||
const s = this.semester || {}
|
||||
return {
|
||||
bh: '',
|
||||
nd: s.nd,
|
||||
xydbh: s.xydbh || '',
|
||||
xqdc: s.xqdc,
|
||||
xydxqbh: s.bh || '',
|
||||
jc: '',
|
||||
kbh: '',
|
||||
klx: '',
|
||||
kcxh: undefined,
|
||||
jhkcxh: undefined,
|
||||
xhbs: undefined,
|
||||
jybh: '',
|
||||
jsbh: '',
|
||||
rs: undefined,
|
||||
zks: undefined,
|
||||
xs: undefined,
|
||||
llxs: undefined,
|
||||
sjxs: undefined,
|
||||
xf: undefined,
|
||||
ksks: undefined,
|
||||
ksbh: '',
|
||||
ksdd: '',
|
||||
cjfz: '',
|
||||
bjrxypjf: undefined,
|
||||
ksksbxs: undefined,
|
||||
jysdh: '',
|
||||
jysjhjybh: '',
|
||||
jysjhbz: '',
|
||||
pdxh: undefined,
|
||||
pdqsz: undefined,
|
||||
pdaz: undefined,
|
||||
pdzzksj: undefined,
|
||||
pdbz: undefined,
|
||||
pdtbykxxbz: undefined,
|
||||
pdqyzdyxs: undefined,
|
||||
pdzdyxsfbsj: '',
|
||||
bz2: undefined,
|
||||
bz: ''
|
||||
}
|
||||
},
|
||||
|
||||
handleAdd() {
|
||||
this.editMode = 'add'
|
||||
this.editForm = this.createEmptyEditForm()
|
||||
this.editVisible = true
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.editFormRef) this.$refs.editFormRef.clearValidate()
|
||||
})
|
||||
},
|
||||
|
||||
handleEdit(row) {
|
||||
this.editMode = 'edit'
|
||||
const s = this.semester || {}
|
||||
const form = this.createEmptyEditForm()
|
||||
Object.keys(form).forEach(key => {
|
||||
if (row[key] !== undefined && row[key] !== null) form[key] = row[key]
|
||||
})
|
||||
form.xydxqbh = s.bh || row.xydxqbh
|
||||
this.editForm = form
|
||||
this.editVisible = true
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.editFormRef) this.$refs.editFormRef.clearValidate()
|
||||
})
|
||||
},
|
||||
|
||||
handleEditSubmit() {
|
||||
this.$refs.editFormRef.validate(valid => {
|
||||
if (!valid) return
|
||||
const payload = this.cleanPayload({ ...this.editForm })
|
||||
const req = this.editMode === 'edit'
|
||||
? updateClassCalendar(payload)
|
||||
: addTeamTask(payload)
|
||||
this.saving = true
|
||||
req.then(() => {
|
||||
this.saving = false
|
||||
this.editVisible = false
|
||||
this.$message.success(this.editMode === 'edit' ? '班历记录已更新' : '班历记录已新增')
|
||||
this.loadData()
|
||||
}).catch(() => {
|
||||
this.saving = false
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
/* ---------- 删除 ---------- */
|
||||
handleDelete(row) {
|
||||
this.$confirm(`确定删除该班历记录(${row.jc || row.bh})吗?`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
delTeamTask(row.bh).then(() => {
|
||||
this.$message.success('已删除')
|
||||
this.loadData()
|
||||
})
|
||||
}).catch(() => {})
|
||||
},
|
||||
|
||||
/* ---------- 自动生成必修课程 ---------- */
|
||||
handleAutoGenerate() {
|
||||
const s = this.semester || {}
|
||||
if (!s.xydbh || !s.xqdc) {
|
||||
this.$message.warning('当前班次学期缺少学员队编号或学期第次,无法自动生成')
|
||||
return
|
||||
}
|
||||
this.$confirm('将按专业教学计划自动生成该班次的必修课程(已存在的课程不会重复添加),是否继续?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
autoGenerateRequiredCourses(s.xydbh, s.xqdc).then(res => {
|
||||
const count = (res && res.data) || 0
|
||||
this.$message.success(`已自动生成 ${count} 门必修课程`)
|
||||
this.loadData()
|
||||
})
|
||||
}).catch(() => {})
|
||||
},
|
||||
|
||||
/* ---------- 预设合班 / 拆班 ---------- */
|
||||
handlePresetMerge() {
|
||||
const bhs = this.selection.map(r => r.bh)
|
||||
if (!bhs.length) {
|
||||
this.$message.warning('请先勾选要合班的班历记录')
|
||||
return
|
||||
}
|
||||
this.$confirm(`将按年度为选中的 ${bhs.length} 条记录设置统一的课次序号,是否继续?`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
batchPresetMerge(bhs).then(res => {
|
||||
const count = (res && res.data) || 0
|
||||
this.$message.success(`已完成 ${count} 条记录的预设合班`)
|
||||
this.loadData()
|
||||
})
|
||||
}).catch(() => {})
|
||||
},
|
||||
|
||||
handlePresetSplit() {
|
||||
const bhs = this.selection.map(r => r.bh)
|
||||
if (!bhs.length) {
|
||||
this.$message.warning('请先勾选要拆班的班历记录')
|
||||
return
|
||||
}
|
||||
this.$confirm(`将清空选中的 ${bhs.length} 条记录的课次序号,是否继续?`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
batchPresetSplit(bhs).then(res => {
|
||||
const count = (res && res.data) || 0
|
||||
this.$message.success(`已完成 ${count} 条记录的预设拆班`)
|
||||
this.loadData()
|
||||
})
|
||||
}).catch(() => {})
|
||||
},
|
||||
|
||||
/* ---------- 应用于其它班次 ---------- */
|
||||
handleApplyRegion() {
|
||||
const bhs = this.selection.map(r => r.bh)
|
||||
if (!bhs.length) {
|
||||
this.$message.warning('请先勾选要应用的班历记录(选定区域)')
|
||||
return
|
||||
}
|
||||
this.applyMode = 'region'
|
||||
this.copyNote = `将选中的 ${bhs.length} 条班历记录复制到所选班次`
|
||||
this.teamSelectVisible = true
|
||||
},
|
||||
|
||||
handleApplyWhole() {
|
||||
this.applyMode = 'whole'
|
||||
this.copyNote = '将整个班历复制到所选班次'
|
||||
this.teamSelectVisible = true
|
||||
},
|
||||
|
||||
handleTeamConfirm(targets) {
|
||||
if (!targets || !targets.length) return
|
||||
const targetBhList = targets.map(t => t.bh)
|
||||
if (this.applyMode === 'whole') {
|
||||
batchCopyCalendarBySemester({
|
||||
sourceXydxqbh: this.semester.bh,
|
||||
targetBhList: targetBhList
|
||||
}).then(res => {
|
||||
const data = (res && res.data) || {}
|
||||
this.$message.success(`已复制整个班历,共新增 ${data.count || 0} 条记录`)
|
||||
this.loadData()
|
||||
})
|
||||
} else {
|
||||
batchCopyCalendar({
|
||||
sourceBhList: this.selection.map(r => r.bh),
|
||||
targetBhList: targetBhList
|
||||
}).then(res => {
|
||||
const data = (res && res.data) || {}
|
||||
this.$message.success(`已复制选定区域,共新增 ${data.count || 0} 条记录`)
|
||||
this.loadData()
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
/* ---------- 刷新 ---------- */
|
||||
handleRefresh() {
|
||||
this.loadData()
|
||||
this.$message.success('已刷新班历')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.class-calendar {
|
||||
// ========== 1. 学期与班次信息 ==========
|
||||
.info-panel {
|
||||
background: #fff;
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 4px;
|
||||
padding: 12px 20px;
|
||||
margin-bottom: 12px;
|
||||
|
||||
.info-title-row {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 20px;
|
||||
margin-bottom: 8px;
|
||||
|
||||
.semester-name,
|
||||
.class-name {
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
color: #1e6fff;
|
||||
}
|
||||
}
|
||||
|
||||
.date-range-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
|
||||
.range-item {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #1e6fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 2. 操作按钮区域 ==========
|
||||
.settings-panel {
|
||||
background: #fff;
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 4px;
|
||||
padding: 12px 20px;
|
||||
margin-bottom: 12px;
|
||||
|
||||
.action-buttons-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 3. 表格区域 ==========
|
||||
.table-area {
|
||||
background: #fff;
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
|
||||
.select-tip {
|
||||
padding: 6px 12px;
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
border-top: 1px solid #ebeef5;
|
||||
background: #fafafa;
|
||||
}
|
||||
}
|
||||
|
||||
.text-danger {
|
||||
color: #f56c6c;
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 弹窗内表单分区标题 ==========
|
||||
.edit-form {
|
||||
.form-section-title {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin: 6px 0 10px;
|
||||
padding: 6px 10px;
|
||||
background: #f0f7ff;
|
||||
border-left: 3px solid #1e6fff;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,223 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
:visible="dialogVisible"
|
||||
:title="isEdit ? '编辑班次学期信息' : '教学班次学期信息明细'"
|
||||
width="620px"
|
||||
class="class-detail-dialog"
|
||||
:close-on-click-modal="false"
|
||||
@update:visible="val => dialogVisible = val"
|
||||
>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="110px" class="detail-form">
|
||||
<!-- 班次信息(只读展示) -->
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="班次">
|
||||
<el-input :value="form.xydmc || form.xydbh" readonly />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="班次编号">
|
||||
<el-input v-model="form.xydbh" :readonly="isEdit" :disabled="isEdit" placeholder="编辑模式不可修改" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="年度" prop="nd">
|
||||
<el-select v-model="form.nd" placeholder="请选择年度" style="width: 100%">
|
||||
<el-option v-for="y in yearOptions" :key="y" :label="`${y}年`" :value="y" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="学期第次" prop="xqdc">
|
||||
<el-select v-model="form.xqdc" placeholder="请选择学期" style="width: 100%">
|
||||
<el-option v-for="opt in levelOptions" :key="opt.value" :label="opt.label" :value="opt.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="开学日期" prop="kxrq">
|
||||
<el-date-picker v-model="form.kxrq" type="date" value-format="yyyy-MM-dd" placeholder="请选择" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="结束日期" prop="jsrq">
|
||||
<el-date-picker v-model="form.jsrq" type="date" value-format="yyyy-MM-dd" placeholder="请选择" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="专用教室">
|
||||
<el-input v-model="form.zyjsbh" placeholder="请输入专用教室编号" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="教学任务">
|
||||
<el-select v-model="form.jxrwbh" clearable filterable placeholder="请选择教学任务" style="width: 100%">
|
||||
<el-option v-for="opt in taskOptions" :key="opt.bh" :label="opt.rwmc" :value="opt.bh" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="开放教员排课">
|
||||
<el-checkbox v-model="form.kfjypk">开放教员排课</el-checkbox>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="form.bz" type="textarea" :rows="3" placeholder="请输入备注" clearable />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<div slot="footer">
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="saving" @click="handleSubmit">确定</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ClassSemesterDetailDialog',
|
||||
props: {
|
||||
visible: { type: Boolean, default: false },
|
||||
/** 编辑模式:edit / add */
|
||||
mode: { type: String, default: 'add' },
|
||||
/** 编辑时的班次学期记录 */
|
||||
semester: { type: Object, default: null },
|
||||
/** 新增时选中的班次(学员队)信息 */
|
||||
teamInfo: { type: Object, default: null },
|
||||
/** 年度下拉 */
|
||||
yearOptions: { type: Array, default: () => [] },
|
||||
/** 教学任务下拉({ bh, rwmc }) */
|
||||
taskOptions: { type: Array, default: () => [] }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
form: this.createEmptyForm(),
|
||||
levelOptions: [
|
||||
{ label: '第1学期', value: 1 },
|
||||
{ label: '第2学期', value: 2 },
|
||||
{ label: '第3学期', value: 3 }
|
||||
],
|
||||
saving: false,
|
||||
rules: {
|
||||
nd: [{ required: true, message: '请选择年度', trigger: 'change' }],
|
||||
xqdc: [{ required: true, message: '请选择学期第次', trigger: 'change' }],
|
||||
kxrq: [{ required: true, message: '请选择开学日期', trigger: 'change' }],
|
||||
jsrq: [{ required: true, message: '请选择结束日期', trigger: 'change' }]
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
dialogVisible: {
|
||||
get() {
|
||||
return this.visible
|
||||
},
|
||||
set(val) {
|
||||
this.$emit('update:visible', val)
|
||||
}
|
||||
},
|
||||
isEdit() {
|
||||
return this.mode === 'edit'
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
visible(val) {
|
||||
if (!val) return
|
||||
this.initForm()
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.formRef) this.$refs.formRef.clearValidate()
|
||||
})
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
createEmptyForm() {
|
||||
return {
|
||||
bh: '',
|
||||
xydbh: '',
|
||||
xydmc: '',
|
||||
nd: undefined,
|
||||
xqdc: undefined,
|
||||
kxrq: '',
|
||||
jsrq: '',
|
||||
zyjsbh: '',
|
||||
jxrwbh: undefined,
|
||||
kfjypk: false,
|
||||
bz: ''
|
||||
}
|
||||
},
|
||||
/** 打开时按编辑/新增模式初始化表单 */
|
||||
initForm() {
|
||||
const form = this.createEmptyForm()
|
||||
if (this.isEdit && this.semester) {
|
||||
const row = this.semester
|
||||
form.bh = row.bh || ''
|
||||
form.xydbh = row.xydbh || ''
|
||||
form.xydmc = row.xydmc || ''
|
||||
form.nd = row.nd
|
||||
form.xqdc = row.xqdc
|
||||
form.kxrq = row.kxrq ? String(row.kxrq).slice(0, 10) : ''
|
||||
form.jsrq = row.jsrq ? String(row.jsrq).slice(0, 10) : ''
|
||||
form.zyjsbh = row.zyjsbh || ''
|
||||
form.jxrwbh = row.jxrwbh || undefined
|
||||
form.kfjypk = Number(row.kfjypk) === 1
|
||||
form.bz = row.bz || ''
|
||||
} else if (this.teamInfo) {
|
||||
form.xydbh = this.teamInfo.xydbh || ''
|
||||
form.xydmc = this.teamInfo.xydmc || ''
|
||||
// 默认取当前选中年度的第一学期,日期可后续修改
|
||||
form.nd = this.yearOptions[0]
|
||||
form.xqdc = 1
|
||||
}
|
||||
this.form = form
|
||||
},
|
||||
/** 移除空值(保留 0 等有效值) */
|
||||
cleanPayload(obj) {
|
||||
const payload = {}
|
||||
Object.keys(obj).forEach(key => {
|
||||
const value = obj[key]
|
||||
if (value !== '' && value !== null && value !== undefined) {
|
||||
payload[key] = value
|
||||
}
|
||||
})
|
||||
return payload
|
||||
},
|
||||
handleSubmit() {
|
||||
this.$refs.formRef.validate(valid => {
|
||||
if (!valid) return
|
||||
if (!this.form.xydbh) {
|
||||
this.$message.warning('缺少班次编号,无法保存')
|
||||
return
|
||||
}
|
||||
const payload = this.cleanPayload({
|
||||
bh: this.form.bh,
|
||||
xydbh: this.form.xydbh,
|
||||
nd: this.form.nd,
|
||||
xqdc: this.form.xqdc,
|
||||
kxrq: this.form.kxrq,
|
||||
jsrq: this.form.jsrq,
|
||||
zyjsbh: this.form.zyjsbh,
|
||||
jxrwbh: this.form.jxrwbh,
|
||||
kfjypk: this.form.kfjypk ? 1 : 0,
|
||||
bz: this.form.bz
|
||||
})
|
||||
this.$emit('submit', payload)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.class-detail-dialog {
|
||||
.detail-form {
|
||||
// 只读输入框灰色底
|
||||
::v-deep .el-input__inner[readonly] {
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,224 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
:visible="dialogVisible"
|
||||
:title="title"
|
||||
width="900px"
|
||||
top="8vh"
|
||||
:close-on-click-modal="false"
|
||||
class="team-select-dialog"
|
||||
@update:visible="val => dialogVisible = val"
|
||||
>
|
||||
<div class="team-select">
|
||||
<!-- ==================== 操作说明 ==================== -->
|
||||
<div v-if="excludeNote" class="note-bar">
|
||||
<i class="el-icon-info"></i>
|
||||
<span>{{ excludeNote }}</span>
|
||||
</div>
|
||||
|
||||
<!-- ==================== 查询条件区域 ==================== -->
|
||||
<div class="query-panel">
|
||||
<el-form :inline="true" class="query-form" @submit.native.prevent>
|
||||
<el-form-item label="班次编号">
|
||||
<el-input
|
||||
v-model="queryForm.xydbh"
|
||||
placeholder="请输入班次编号(模糊)"
|
||||
clearable
|
||||
style="width: 220px"
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" size="small" icon="el-icon-search" @click="handleQuery">查询</el-button>
|
||||
<el-button size="small" @click="handleResetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<!-- ==================== 目标班次学期表格 ==================== -->
|
||||
<div class="table-panel">
|
||||
<el-table
|
||||
ref="tableRef"
|
||||
v-loading="loading"
|
||||
:data="filteredList"
|
||||
border
|
||||
stripe
|
||||
size="small"
|
||||
max-height="360"
|
||||
class="team-table"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<el-table-column type="selection" width="44" align="center" reserve-selection />
|
||||
<el-table-column type="index" label="序号" width="55" align="center" />
|
||||
<el-table-column prop="nd" label="年度" width="80" align="center" show-overflow-tooltip />
|
||||
<el-table-column label="学期第次" width="90" align="center">
|
||||
<template slot-scope="{ row }">{{ xqdcLabel(row.xqdc) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="xydbh" label="班次编号" min-width="150" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="kxrq" label="开学日期" width="110" align="center" :formatter="fmtDate" />
|
||||
<el-table-column prop="jsrq" label="结束日期" width="110" align="center" :formatter="fmtDate" />
|
||||
<el-table-column prop="bz" label="备注" min-width="120" align="left" header-align="center" show-overflow-tooltip />
|
||||
</el-table>
|
||||
<el-empty v-if="!filteredList.length" description="暂无可选的目标班次学期" :image-size="60" />
|
||||
</div>
|
||||
|
||||
<!-- ==================== 底部按钮区域 ==================== -->
|
||||
<div class="footer-actions">
|
||||
<span class="selected-count">已选择 {{ selection.length }} 个班次学期</span>
|
||||
<el-button size="small" @click="dialogVisible = false">取消</el-button>
|
||||
<el-button size="small" type="primary" :disabled="!selection.length" @click="handleConfirm">确定</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ClassTeamSelectDialog',
|
||||
props: {
|
||||
visible: { type: Boolean, default: false },
|
||||
/** 标题(可选) */
|
||||
title: { type: String, default: '目标班次学期选取' },
|
||||
/** 可选的目标班次学期列表(含 bh/nd/xqdc/xydbh/kxrq/jsrq 等) */
|
||||
semesterList: { type: Array, default: () => [] },
|
||||
/** 需要排除的班次学期编号(当前班次学期,不可复制给自己) */
|
||||
excludeBh: { type: String, default: '' },
|
||||
/** 顶部操作说明 */
|
||||
excludeNote: { type: String, default: '' }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
queryForm: { xydbh: '' },
|
||||
selection: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
dialogVisible: {
|
||||
get() {
|
||||
return this.visible
|
||||
},
|
||||
set(val) {
|
||||
this.$emit('update:visible', val)
|
||||
}
|
||||
},
|
||||
/** 排除当前班次学期后的可选列表 */
|
||||
availableList() {
|
||||
const excludeBh = this.excludeBh || ''
|
||||
return (this.semesterList || []).filter(item => item.bh && String(item.bh) !== String(excludeBh))
|
||||
},
|
||||
/** 本地过滤后的列表 */
|
||||
filteredList() {
|
||||
const kw = (this.queryForm.xydbh || '').trim()
|
||||
if (!kw) return this.availableList
|
||||
return this.availableList.filter(item => item.xydbh && String(item.xydbh).includes(kw))
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
visible(val) {
|
||||
if (val) {
|
||||
this.selection = []
|
||||
this.queryForm.xydbh = ''
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.tableRef) this.$refs.tableRef.clearSelection()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
fmtDate(val) {
|
||||
if (!val) return '-'
|
||||
return String(val).slice(0, 10)
|
||||
},
|
||||
xqdcLabel(val) {
|
||||
if (val === 1 || val === '1') return '第1学期'
|
||||
if (val === 2 || val === '2') return '第2学期'
|
||||
if (val === 3 || val === '3') return '第3学期'
|
||||
return val !== undefined && val !== null && val !== '' ? `第${val}学期` : '-'
|
||||
},
|
||||
handleSelectionChange(rows) {
|
||||
this.selection = rows
|
||||
},
|
||||
handleQuery() {},
|
||||
handleResetQuery() {
|
||||
this.queryForm.xydbh = ''
|
||||
},
|
||||
handleConfirm() {
|
||||
if (!this.selection.length) {
|
||||
this.$message.warning('请先勾选目标班次学期')
|
||||
return
|
||||
}
|
||||
this.$emit('confirm', this.selection.slice())
|
||||
this.dialogVisible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.team-select {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
|
||||
// ========== 操作说明 ==========
|
||||
.note-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 8px 12px;
|
||||
font-size: 13px;
|
||||
color: #1e6fff;
|
||||
background: #ecf5ff;
|
||||
border: 1px solid #d9ecff;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
// ========== 查询条件区域 ==========
|
||||
.query-panel {
|
||||
background: #fff;
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 4px;
|
||||
padding: 10px 12px 2px;
|
||||
|
||||
.query-form {
|
||||
::v-deep .el-form-item {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 表格区域 ==========
|
||||
.table-panel {
|
||||
position: relative;
|
||||
background: #fff;
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 4px;
|
||||
padding: 10px;
|
||||
overflow: hidden;
|
||||
|
||||
.team-table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
::v-deep .el-empty {
|
||||
padding: 12px 0;
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 底部按钮区域 ==========
|
||||
.footer-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid #ebeef5;
|
||||
|
||||
.selected-count {
|
||||
flex: 1;
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,649 @@
|
||||
<template>
|
||||
<div class="app-container shift-semester-page">
|
||||
<!-- ==================== 页面标题 ==================== -->
|
||||
<div class="page-title">班次学期信息管理</div>
|
||||
|
||||
<!-- ==================== 1. 查询条件区域 ==================== -->
|
||||
<el-card shadow="never" class="search-card">
|
||||
<el-form :inline="true" :model="searchForm" class="search-form">
|
||||
<el-form-item label="班次编号">
|
||||
<el-input
|
||||
v-model="searchForm.xydbh"
|
||||
placeholder="请输入班次编号(模糊)"
|
||||
clearable
|
||||
style="width: 220px"
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="年度">
|
||||
<el-select v-model="filterYear" placeholder="全部年度" clearable style="width: 140px" @change="handleYearChange">
|
||||
<el-option v-for="y in yearFilterOptions" :key="y" :label="`${y}年`" :value="y" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" @click="handleQuery">查询</el-button>
|
||||
<el-button icon="el-icon-refresh" @click="handleReset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<!-- ==================== 2. 工具栏按钮区 ==================== -->
|
||||
<div class="list-toolbar">
|
||||
<div class="left-group">
|
||||
<el-button type="primary" icon="el-icon-plus" @click="handleBatchAdd">批量创建班次学期</el-button>
|
||||
<el-button type="primary" plain icon="el-icon-document-add" @click="handleAdd">新增</el-button>
|
||||
<el-button type="primary" plain icon="el-icon-edit" :disabled="!currentRow" @click="handleEdit">编辑</el-button>
|
||||
<el-button type="danger" plain icon="el-icon-delete" :disabled="!selection.length" @click="handleDelete">删除所选</el-button>
|
||||
<el-button type="primary" plain icon="el-icon-date" :disabled="!selection.length" @click="handleQuickSetDates">快速设定学期日期</el-button>
|
||||
<el-button type="primary" plain icon="el-icon-notebook-2" :disabled="!selection.length" @click="handleSetJxrw">批量设置教学任务</el-button>
|
||||
</div>
|
||||
<div class="right-group">
|
||||
<el-button type="success" plain icon="el-icon-calendar" :disabled="!currentRow" @click="handleEditCalendar">编辑班历</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ==================== 3. 班次学期信息列表 ==================== -->
|
||||
<el-card shadow="never" class="table-card">
|
||||
<el-table
|
||||
ref="tableRef"
|
||||
v-loading="loading"
|
||||
:data="displayData"
|
||||
border
|
||||
stripe
|
||||
highlight-current-row
|
||||
size="small"
|
||||
max-height="480"
|
||||
class="class-table"
|
||||
@selection-change="handleSelectionChange"
|
||||
@current-change="handleCurrentChange"
|
||||
>
|
||||
<template slot="empty">
|
||||
<span>无数据!</span>
|
||||
</template>
|
||||
<el-table-column type="selection" width="44" align="center" />
|
||||
<el-table-column type="index" label="序号" width="55" align="center" />
|
||||
<el-table-column prop="xydbh" label="班次编号" min-width="150" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="nd" label="年度" width="80" align="center" show-overflow-tooltip />
|
||||
<el-table-column label="学期第次" width="90" align="center">
|
||||
<template slot-scope="{ row }">{{ xqdcLabel(row.xqdc) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="kxrq" label="开学日期" width="110" align="center" :formatter="fmtDate" />
|
||||
<el-table-column prop="jsrq" label="结束日期" width="110" align="center" :formatter="fmtDate" />
|
||||
<el-table-column prop="zyjsbh" label="专用教室" width="100" align="center" show-overflow-tooltip />
|
||||
<el-table-column label="教学任务" min-width="160" align="center" show-overflow-tooltip>
|
||||
<template slot-scope="{ row }">{{ taskName(row.jxrwbh) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="开放教员排课" width="110" align="center">
|
||||
<template slot-scope="{ row }">
|
||||
<el-tag v-if="Number(row.kfjypk) === 1" type="success" size="small">开放</el-tag>
|
||||
<el-tag v-else type="info" size="small">关闭</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="bz" label="备注" min-width="120" align="left" header-align="center" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="200" align="center" fixed="right">
|
||||
<template slot-scope="{ row }">
|
||||
<el-button type="text" size="small" @click="handleEdit(row)">编辑</el-button>
|
||||
<el-button type="text" size="small" @click="handleEditCalendar(row)">班历</el-button>
|
||||
<el-button type="text" size="small" class="text-danger" @click="handleDeleteRow(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-pagination
|
||||
class="list-pagination"
|
||||
:current-page="pageNum"
|
||||
:page-size="pageSize"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
:total="total"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handlePageChange"
|
||||
/>
|
||||
</el-card>
|
||||
|
||||
<!-- ==================== 班次学期新增/编辑弹窗 ==================== -->
|
||||
<ClassSemesterDetailDialog
|
||||
:visible="detailVisible"
|
||||
:mode="detailMode"
|
||||
:semester="detailSemester"
|
||||
:team-info="detailTeamInfo"
|
||||
:year-options="yearOptions"
|
||||
:task-options="taskOptions"
|
||||
@update:visible="val => detailVisible = val"
|
||||
@submit="handleDetailSubmit"
|
||||
/>
|
||||
|
||||
<!-- ==================== 班历管理弹窗 ==================== -->
|
||||
<ClassCalendarDialog
|
||||
:visible="calendarVisible"
|
||||
:semester="calendarSemester"
|
||||
:semester-options="semesterOptions"
|
||||
@update:visible="val => calendarVisible = val"
|
||||
/>
|
||||
|
||||
<!-- ==================== 批量创建班次学期弹窗 ==================== -->
|
||||
<el-dialog
|
||||
:visible="batchAddVisible"
|
||||
title="批量创建班次学期"
|
||||
width="520px"
|
||||
:close-on-click-modal="false"
|
||||
@update:visible="val => batchAddVisible = val"
|
||||
>
|
||||
<el-form ref="batchAddFormRef" :model="batchAddForm" :rules="batchAddRules" label-width="100px">
|
||||
<el-form-item label="年度" prop="nd">
|
||||
<el-select v-model="batchAddForm.nd" placeholder="请选择年度" style="width: 100%">
|
||||
<el-option v-for="y in yearOptions" :key="y" :label="`${y}年`" :value="y" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="学期" prop="xqdc">
|
||||
<el-select v-model="batchAddForm.xqdc" placeholder="请选择学期" style="width: 100%">
|
||||
<el-option label="春季学期" value="春季学期" />
|
||||
<el-option label="夏季学期" value="夏季学期" />
|
||||
<el-option label="秋季学期" value="秋季学期" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="开始日期" prop="startTime">
|
||||
<el-date-picker v-model="batchAddForm.startTime" type="date" value-format="yyyy-MM-dd" placeholder="请选择开始日期" style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="结束日期" prop="endTime">
|
||||
<el-date-picker v-model="batchAddForm.endTime" type="date" value-format="yyyy-MM-dd" placeholder="请选择结束日期" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer">
|
||||
<el-button @click="batchAddVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="saving" @click="handleBatchAddSubmit">确定</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- ==================== 快速设定学期日期弹窗 ==================== -->
|
||||
<el-dialog
|
||||
:visible="dateRangeVisible"
|
||||
title="快速设定学期日期"
|
||||
width="480px"
|
||||
:close-on-click-modal="false"
|
||||
@update:visible="val => dateRangeVisible = val"
|
||||
>
|
||||
<el-alert
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
:title="`将对选中的 ${selection.length} 条班次学期统一设定开学/结束日期`"
|
||||
style="margin-bottom: 16px"
|
||||
/>
|
||||
<el-form ref="dateRangeFormRef" :model="dateRangeForm" :rules="dateRangeRules" label-width="100px">
|
||||
<el-form-item label="开学日期" prop="kxrq">
|
||||
<el-date-picker v-model="dateRangeForm.kxrq" type="date" value-format="yyyy-MM-dd" placeholder="请选择开学日期" style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="结束日期" prop="jsrq">
|
||||
<el-date-picker v-model="dateRangeForm.jsrq" type="date" value-format="yyyy-MM-dd" placeholder="请选择结束日期" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer">
|
||||
<el-button @click="dateRangeVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="saving" @click="handleDateRangeSubmit">确定</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- ==================== 批量设置教学任务弹窗 ==================== -->
|
||||
<el-dialog
|
||||
:visible="jxrwVisible"
|
||||
title="批量设置教学任务"
|
||||
width="480px"
|
||||
:close-on-click-modal="false"
|
||||
@update:visible="val => jxrwVisible = val"
|
||||
>
|
||||
<el-alert
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
:title="`将对选中的 ${selection.length} 条班次学期统一设置教学任务`"
|
||||
style="margin-bottom: 16px"
|
||||
/>
|
||||
<el-form ref="jxrwFormRef" :model="jxrwForm" :rules="jxrwRules" label-width="100px">
|
||||
<el-form-item label="教学任务" prop="jxrwbh">
|
||||
<el-select v-model="jxrwForm.jxrwbh" placeholder="请选择教学任务" filterable style="width: 100%">
|
||||
<el-option v-for="opt in taskOptions" :key="opt.bh" :label="opt.rwmc" :value="opt.bh" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer">
|
||||
<el-button @click="jxrwVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="saving" @click="handleJxrwSubmit">确定</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ClassSemesterDetailDialog from './ClassSemesterDetailDialog.vue'
|
||||
import ClassCalendarDialog from './ClassCalendarDialog.vue'
|
||||
import { listSemester, allSemester, addSemester, updateSemester, delSemester, batchDeleteSemester, batchUpdateDateRange, batchUpdateJxrwbh, batchAddFromElective } from '@/api/studentRecords/semester'
|
||||
import { listAllSemester } from '@/api/teachBusiness/semester'
|
||||
import { listTeachingTask } from '@/api/teachBusiness/teachingTask'
|
||||
|
||||
export default {
|
||||
name: 'ShiftSemester',
|
||||
components: {
|
||||
ClassSemesterDetailDialog,
|
||||
ClassCalendarDialog
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
saving: false,
|
||||
|
||||
// ==================== 查询条件 ====================
|
||||
searchForm: { xydbh: '' },
|
||||
filterYear: undefined,
|
||||
yearFilterOptions: [],
|
||||
|
||||
// ==================== 列表与分页 ====================
|
||||
tableData: [],
|
||||
selection: [],
|
||||
currentRow: null,
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
|
||||
// ==================== 下拉数据 ====================
|
||||
/** 年度下拉(来自 /semester/all) */
|
||||
yearOptions: [],
|
||||
/** 教学任务下拉({ bh, rwmc }) */
|
||||
taskOptions: [],
|
||||
/** 全部班次学期(用于班历复制目标选择) */
|
||||
semesterOptions: [],
|
||||
|
||||
// ==================== 班次学期新增/编辑 ====================
|
||||
detailVisible: false,
|
||||
detailMode: 'add',
|
||||
detailSemester: null,
|
||||
detailTeamInfo: null,
|
||||
|
||||
// ==================== 班历管理 ====================
|
||||
calendarVisible: false,
|
||||
calendarSemester: null,
|
||||
|
||||
// ==================== 批量创建班次学期 ====================
|
||||
batchAddVisible: false,
|
||||
batchAddForm: { nd: undefined, xqdc: '', startTime: '', endTime: '' },
|
||||
batchAddRules: {
|
||||
nd: [{ required: true, message: '请选择年度', trigger: 'change' }],
|
||||
xqdc: [{ required: true, message: '请选择学期', trigger: 'change' }],
|
||||
startTime: [{ required: true, message: '请选择开始日期', trigger: 'change' }],
|
||||
endTime: [{ required: true, message: '请选择结束日期', trigger: 'change' }]
|
||||
},
|
||||
|
||||
// ==================== 快速设定学期日期 ====================
|
||||
dateRangeVisible: false,
|
||||
dateRangeForm: { kxrq: '', jsrq: '' },
|
||||
dateRangeRules: {
|
||||
kxrq: [{ required: true, message: '请选择开学日期', trigger: 'change' }],
|
||||
jsrq: [{ required: true, message: '请选择结束日期', trigger: 'change' }]
|
||||
},
|
||||
|
||||
// ==================== 批量设置教学任务 ====================
|
||||
jxrwVisible: false,
|
||||
jxrwForm: { jxrwbh: undefined },
|
||||
jxrwRules: {
|
||||
jxrwbh: [{ required: true, message: '请选择教学任务', trigger: 'change' }]
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
/** 本地按年度过滤(后端 /semester/list 仅支持 xydbh 模糊筛选) */
|
||||
displayData() {
|
||||
if (!this.filterYear) return this.tableData
|
||||
return this.tableData.filter(item => String(item.nd) === String(this.filterYear))
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadYearOptions()
|
||||
this.loadTaskOptions()
|
||||
this.loadSemesterOptions()
|
||||
this.loadList()
|
||||
},
|
||||
methods: {
|
||||
/* ==================== 格式化 ==================== */
|
||||
fmtDate(val) {
|
||||
if (!val) return '-'
|
||||
return String(val).slice(0, 10)
|
||||
},
|
||||
xqdcLabel(val) {
|
||||
if (val === 1 || val === '1') return '第1学期'
|
||||
if (val === 2 || val === '2') return '第2学期'
|
||||
if (val === 3 || val === '3') return '第3学期'
|
||||
return val !== undefined && val !== null && val !== '' ? `第${val}学期` : '-'
|
||||
},
|
||||
/** 教学任务编号 → 名称 */
|
||||
taskName(bh) {
|
||||
if (!bh) return '-'
|
||||
const opt = this.taskOptions.find(o => o.bh === bh)
|
||||
return opt ? opt.rwmc : bh
|
||||
},
|
||||
cleanPayload(obj) {
|
||||
const payload = {}
|
||||
Object.keys(obj).forEach(key => {
|
||||
const value = obj[key]
|
||||
if (value !== '' && value !== null && value !== undefined) {
|
||||
payload[key] = value
|
||||
}
|
||||
})
|
||||
return payload
|
||||
},
|
||||
|
||||
/* ==================== 下拉数据加载 ==================== */
|
||||
loadYearOptions() {
|
||||
listAllSemester().then(res => {
|
||||
const list = (res && res.data) || []
|
||||
const years = list.map(x => x.nd).filter(v => v !== undefined && v !== null && v !== '')
|
||||
this.yearOptions = [...new Set(years)].sort((a, b) => Number(a) - Number(b))
|
||||
}).catch(() => {
|
||||
this.yearOptions = []
|
||||
})
|
||||
},
|
||||
loadTaskOptions() {
|
||||
listTeachingTask({ pageNum: 1, pageSize: 1000 }).then(res => {
|
||||
const list = (res && res.data && res.data.records) || []
|
||||
this.taskOptions = list.filter(o => o.bh)
|
||||
}).catch(() => {
|
||||
this.taskOptions = []
|
||||
})
|
||||
},
|
||||
loadSemesterOptions() {
|
||||
allSemester().then(res => {
|
||||
this.semesterOptions = (res && res.data) || []
|
||||
}).catch(() => {
|
||||
this.semesterOptions = []
|
||||
})
|
||||
},
|
||||
|
||||
/* ==================== 列表加载 ==================== */
|
||||
loadList() {
|
||||
this.loading = true
|
||||
const query = this.cleanPayload({
|
||||
pageNum: this.pageNum,
|
||||
pageSize: this.pageSize,
|
||||
xydbh: this.searchForm.xydbh
|
||||
})
|
||||
listSemester(query).then(res => {
|
||||
const data = (res && res.data) || {}
|
||||
this.tableData = data.records || []
|
||||
this.total = data.total || 0
|
||||
this.buildYearFilterOptions()
|
||||
this.loading = false
|
||||
}).catch(() => {
|
||||
this.tableData = []
|
||||
this.total = 0
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
/** 本地年度过滤下拉(从当前数据动态生成,避免硬编码) */
|
||||
buildYearFilterOptions() {
|
||||
const years = this.tableData.map(r => r.nd).filter(v => v !== undefined && v !== null && v !== '')
|
||||
this.yearFilterOptions = [...new Set(years)].sort((a, b) => Number(a) - Number(b))
|
||||
},
|
||||
handleYearChange() {
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.tableRef) this.$refs.tableRef.setCurrentRow(null)
|
||||
this.currentRow = null
|
||||
})
|
||||
},
|
||||
handleSizeChange(size) {
|
||||
this.pageSize = size
|
||||
this.loadList()
|
||||
},
|
||||
handlePageChange(page) {
|
||||
this.pageNum = page
|
||||
this.loadList()
|
||||
},
|
||||
handleSelectionChange(rows) {
|
||||
this.selection = rows
|
||||
},
|
||||
handleCurrentChange(row) {
|
||||
this.currentRow = row
|
||||
},
|
||||
|
||||
/* ==================== 查询 / 重置 ==================== */
|
||||
handleQuery() {
|
||||
this.pageNum = 1
|
||||
this.loadList()
|
||||
},
|
||||
handleReset() {
|
||||
this.searchForm.xydbh = ''
|
||||
this.filterYear = undefined
|
||||
this.pageNum = 1
|
||||
this.loadList()
|
||||
},
|
||||
|
||||
/* ==================== 新增 / 编辑 ==================== */
|
||||
handleAdd() {
|
||||
this.detailMode = 'add'
|
||||
this.detailSemester = null
|
||||
this.detailTeamInfo = null
|
||||
this.detailVisible = true
|
||||
},
|
||||
handleEdit(row) {
|
||||
const target = row || this.currentRow
|
||||
if (!target) {
|
||||
this.$message.warning('请先选择一条班次学期信息')
|
||||
return
|
||||
}
|
||||
this.detailMode = 'edit'
|
||||
this.detailSemester = target
|
||||
this.detailTeamInfo = null
|
||||
this.detailVisible = true
|
||||
},
|
||||
handleDetailSubmit(payload) {
|
||||
this.saving = true
|
||||
const req = this.detailMode === 'edit'
|
||||
? updateSemester(payload)
|
||||
: addSemester(payload)
|
||||
req.then(() => {
|
||||
this.saving = false
|
||||
this.detailVisible = false
|
||||
this.$message.success(this.detailMode === 'edit' ? '班次学期已更新' : '班次学期已新增')
|
||||
this.loadList()
|
||||
this.loadSemesterOptions()
|
||||
}).catch(() => {
|
||||
this.saving = false
|
||||
})
|
||||
},
|
||||
|
||||
/* ==================== 删除 ==================== */
|
||||
handleDelete() {
|
||||
if (!this.selection.length) {
|
||||
this.$message.warning('请先勾选要删除的班次学期')
|
||||
return
|
||||
}
|
||||
this.$confirm(`确定删除选中的 ${this.selection.length} 条班次学期吗?删除后其下班历记录将一并删除。`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
const bhList = this.selection.map(r => r.bh)
|
||||
batchDeleteSemester(bhList).then(() => {
|
||||
this.$message.success('已删除')
|
||||
this.loadList()
|
||||
this.loadSemesterOptions()
|
||||
})
|
||||
}).catch(() => {})
|
||||
},
|
||||
handleDeleteRow(row) {
|
||||
this.$confirm(`确定删除班次学期「${row.xydbh || row.bh}」吗?删除后其下班历记录将一并删除。`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
delSemester(row.bh).then(() => {
|
||||
this.$message.success('已删除')
|
||||
this.loadList()
|
||||
this.loadSemesterOptions()
|
||||
})
|
||||
}).catch(() => {})
|
||||
},
|
||||
|
||||
/* ==================== 编辑班历 ==================== */
|
||||
handleEditCalendar(row) {
|
||||
const target = row || this.currentRow
|
||||
if (!target) {
|
||||
this.$message.warning('请先在列表中选择一条班次学期信息')
|
||||
return
|
||||
}
|
||||
this.calendarSemester = target
|
||||
this.calendarVisible = true
|
||||
},
|
||||
|
||||
/* ==================== 批量创建班次学期 ==================== */
|
||||
handleBatchAdd() {
|
||||
this.batchAddForm = { nd: this.yearOptions[0] || undefined, xqdc: '', startTime: '', endTime: '' }
|
||||
this.batchAddVisible = true
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.batchAddFormRef) this.$refs.batchAddFormRef.clearValidate()
|
||||
})
|
||||
},
|
||||
handleBatchAddSubmit() {
|
||||
this.$refs.batchAddFormRef.validate(valid => {
|
||||
if (!valid) return
|
||||
const f = this.batchAddForm
|
||||
this.saving = true
|
||||
batchAddFromElective({
|
||||
nd: f.nd,
|
||||
xqdc: f.xqdc,
|
||||
startTime: f.startTime,
|
||||
endTime: f.endTime
|
||||
}).then(res => {
|
||||
this.saving = false
|
||||
this.batchAddVisible = false
|
||||
const count = (res && res.data) || 0
|
||||
this.$message.success(`已创建 ${count} 个班次学期`)
|
||||
this.loadList()
|
||||
this.loadSemesterOptions()
|
||||
}).catch(() => {
|
||||
this.saving = false
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
/* ==================== 快速设定学期日期 ==================== */
|
||||
handleQuickSetDates() {
|
||||
if (!this.selection.length) {
|
||||
this.$message.warning('请先勾选要设定日期的班次学期')
|
||||
return
|
||||
}
|
||||
this.dateRangeForm = { kxrq: '', jsrq: '' }
|
||||
this.dateRangeVisible = true
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.dateRangeFormRef) this.$refs.dateRangeFormRef.clearValidate()
|
||||
})
|
||||
},
|
||||
handleDateRangeSubmit() {
|
||||
this.$refs.dateRangeFormRef.validate(valid => {
|
||||
if (!valid) return
|
||||
const f = this.dateRangeForm
|
||||
this.saving = true
|
||||
batchUpdateDateRange({
|
||||
bhList: this.selection.map(r => r.bh),
|
||||
xqkssj: f.kxrq,
|
||||
xqjssj: f.jsrq
|
||||
}).then(res => {
|
||||
this.saving = false
|
||||
this.dateRangeVisible = false
|
||||
const count = (res && res.data) || 0
|
||||
this.$message.success(`已更新 ${count} 条班次学期的日期`)
|
||||
this.loadList()
|
||||
}).catch(() => {
|
||||
this.saving = false
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
/* ==================== 批量设置教学任务 ==================== */
|
||||
handleSetJxrw() {
|
||||
if (!this.selection.length) {
|
||||
this.$message.warning('请先勾选要设置教学任务的班次学期')
|
||||
return
|
||||
}
|
||||
this.jxrwForm = { jxrwbh: undefined }
|
||||
this.jxrwVisible = true
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.jxrwFormRef) this.$refs.jxrwFormRef.clearValidate()
|
||||
})
|
||||
},
|
||||
handleJxrwSubmit() {
|
||||
this.$refs.jxrwFormRef.validate(valid => {
|
||||
if (!valid) return
|
||||
this.saving = true
|
||||
batchUpdateJxrwbh({
|
||||
bhList: this.selection.map(r => r.bh),
|
||||
jxrwbh: this.jxrwForm.jxrwbh
|
||||
}).then(res => {
|
||||
this.saving = false
|
||||
this.jxrwVisible = false
|
||||
const count = (res && res.data) || 0
|
||||
this.$message.success(`已更新 ${count} 条班次学期的教学任务`)
|
||||
this.loadList()
|
||||
}).catch(() => {
|
||||
this.saving = false
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.app-container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.shift-semester-page {
|
||||
// ========== 页面标题 ==========
|
||||
.page-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin-bottom: 12px;
|
||||
padding-left: 10px;
|
||||
border-left: 4px solid var(--edu-green-primary);
|
||||
}
|
||||
|
||||
// ========== 1. 查询条件区域 ==========
|
||||
.search-card {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
// ========== 2. 工具栏按钮区 ==========
|
||||
.list-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
margin-bottom: 16px;
|
||||
|
||||
.left-group,
|
||||
.right-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 3. 数据表格区 ==========
|
||||
.table-card {
|
||||
.class-table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.text-danger {
|
||||
color: #f56c6c;
|
||||
}
|
||||
|
||||
.list-pagination {
|
||||
margin-top: 12px;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,744 @@
|
||||
<template>
|
||||
<div class="app-container management-page subject-page">
|
||||
|
||||
<!-- ==================== 2. 查询条件区域 ==================== -->
|
||||
<el-card shadow="never" class="search-card">
|
||||
<div class="section-heading">
|
||||
<div>
|
||||
<h2 class="section-title">筛选条件</h2>
|
||||
<p class="section-description">按课程名称、代码和培训属性筛选课程科目</p>
|
||||
</div>
|
||||
<el-button type="primary" icon="el-icon-search" @click="handleQuery">查询</el-button>
|
||||
</div>
|
||||
<el-form :model="searchForm">
|
||||
<el-row :gutter="32">
|
||||
<!-- 左栏 -->
|
||||
<el-col :xs="24" :md="12">
|
||||
<div class="query-field">
|
||||
<span class="q-label no-check">课名称</span>
|
||||
<el-input v-model="searchForm.kmc" placeholder="请输入课名称" clearable class="q-control" />
|
||||
</div>
|
||||
<div class="query-field">
|
||||
<span class="q-label no-check">科目代码</span>
|
||||
<el-input v-model="searchForm.kmdm" placeholder="请输入科目代码" clearable class="q-control" />
|
||||
</div>
|
||||
<div class="query-field">
|
||||
<span class="q-label no-check">培训层次</span>
|
||||
<el-input v-model="searchForm.pxcc" placeholder="请输入培训层次" clearable class="q-control" />
|
||||
</div>
|
||||
<div class="query-field">
|
||||
<span class="q-label no-check">课程类型</span>
|
||||
<el-input v-model="searchForm.kclx" placeholder="请输入课程类型" clearable class="q-control" />
|
||||
</div>
|
||||
</el-col>
|
||||
|
||||
<!-- 右栏 -->
|
||||
<el-col :xs="24" :md="12">
|
||||
<div class="query-field">
|
||||
<span class="q-label no-check">简称</span>
|
||||
<el-input v-model="searchForm.jc" placeholder="请输入简称" clearable class="q-control" />
|
||||
</div>
|
||||
<div class="query-field">
|
||||
<span class="q-label no-check">教研室代号</span>
|
||||
<el-input v-model="searchForm.jysdh" placeholder="请输入教研室代号" clearable class="q-control" />
|
||||
</div>
|
||||
<div class="query-field">
|
||||
<span class="q-label no-check">培训类型</span>
|
||||
<el-input v-model="searchForm.pxlx" placeholder="请输入培训类型" clearable class="q-control" />
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
|
||||
</el-card>
|
||||
|
||||
<!-- ==================== 3. 操作按钮区域 ==================== -->
|
||||
<el-card shadow="never" class="action-card">
|
||||
<div class="section-heading section-heading--compact">
|
||||
<div>
|
||||
<h2 class="section-title">课程科目操作</h2>
|
||||
<p class="section-description">维护课程科目,或下载业务数据与导入模板</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="action-row">
|
||||
<el-button type="primary" icon="el-icon-plus" @click="handleOpenDialog">新建课程科目</el-button>
|
||||
<el-button type="danger" plain icon="el-icon-delete" @click="handleBatchDelete">删除所选</el-button>
|
||||
<el-button icon="el-icon-download" @click="handleDownloadCourse">下载课程科目基本信息</el-button>
|
||||
<el-button icon="el-icon-download" @click="handleDownloadTextbook">下载课程教材基本信息</el-button>
|
||||
</div>
|
||||
<div class="action-row">
|
||||
<el-button icon="el-icon-download" @click="handleDownloadTemplate">【课程科目数据文件模板】下载</el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- ==================== 4. 文件上传区域 ==================== -->
|
||||
<el-card shadow="never" class="upload-card">
|
||||
<div class="section-heading section-heading--compact">
|
||||
<div>
|
||||
<h2 class="section-title">数据导入</h2>
|
||||
<p class="section-description">选择填写完成的课程科目模板并上传</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="upload-row">
|
||||
<div class="upload-left">
|
||||
<el-button icon="el-icon-folder-opened" @click="handleSelectFile">选择文件</el-button>
|
||||
<span class="file-name" :class="{ 'has-file': selectedFile }">{{ fileName }}</span>
|
||||
<input ref="fileInputRef" type="file" style="display: none" @change="handleFileChange" />
|
||||
</div>
|
||||
<div class="upload-right">
|
||||
<el-checkbox v-model="coverExist">覆盖已存在课程科目</el-checkbox>
|
||||
<el-button type="primary" icon="el-icon-upload2" @click="handleUpload">上传数据</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- ==================== 6. 数据表格区域 ==================== -->
|
||||
<el-card shadow="never" class="table-card">
|
||||
<div class="section-heading section-heading--compact table-heading">
|
||||
<div>
|
||||
<h2 class="section-title">课程科目列表</h2>
|
||||
<p class="section-description">共 {{ total }} 条数据</p>
|
||||
</div>
|
||||
</div>
|
||||
<el-table v-loading="loading" :data="tableData" stripe border highlight-current-row
|
||||
@selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="40" align="center" />
|
||||
<el-table-column prop="kbh" label="课编号" width="130" show-overflow-tooltip />
|
||||
<el-table-column prop="kmc" label="课名称" width="200" show-overflow-tooltip />
|
||||
<el-table-column prop="jc" label="简称" width="130" show-overflow-tooltip />
|
||||
<el-table-column prop="jysdh" label="教研室代号" width="150" show-overflow-tooltip />
|
||||
<el-table-column prop="kclx" label="课程类型" width="150" align="center" />
|
||||
<el-table-column prop="pxlx" label="培训类型" width="150" align="center" />
|
||||
<el-table-column prop="pxcc" label="培训层次" width="200" align="center" />
|
||||
<el-table-column prop="xf" label="学分" align="center" />
|
||||
<el-table-column prop="xs" label="学时" width="160" align="center" />
|
||||
<el-table-column label="操作" width="150" align="center" fixed="right">
|
||||
<template slot-scope="{ row }">
|
||||
<el-button type="text" size="small" @click="handleEdit(row)">编辑</el-button>
|
||||
<el-button type="text" size="small" class="text-danger" @click="handleDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-pagination
|
||||
:current-page="pageNum"
|
||||
:page-size="pageSize"
|
||||
:total="total"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
class="pagination-wrapper"
|
||||
@current-change="handlePageChange"
|
||||
@size-change="handleSizeChange"
|
||||
/>
|
||||
</el-card>
|
||||
|
||||
<!-- 新增/编辑课程科目弹窗 -->
|
||||
<el-dialog
|
||||
:visible="dialogVisible"
|
||||
:title="dialogTitle"
|
||||
width="1000px"
|
||||
:close-on-click-modal="false"
|
||||
class="add-dialog"
|
||||
@update:visible="val => dialogVisible = val"
|
||||
>
|
||||
<el-form ref="addFormRef" :model="addForm" :rules="rules" label-width="120px" class="add-form" :status-icon="false">
|
||||
<el-row :gutter="24">
|
||||
<el-col :xs="24" :sm="12" :md="8">
|
||||
<el-form-item label="课编号" prop="kbh">
|
||||
<el-input v-model="addForm.kbh" :disabled="isEdit" placeholder="新增时留空自动生成" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="8">
|
||||
<el-form-item label="课名称" prop="kmc">
|
||||
<el-input v-model="addForm.kmc" placeholder="请输入课名称" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="8">
|
||||
<el-form-item label="简称" prop="jc">
|
||||
<el-input v-model="addForm.jc" placeholder="请输入简称" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="8">
|
||||
<el-form-item label="教研室代号" prop="jysdh">
|
||||
<el-input v-model="addForm.jysdh" placeholder="请输入教研室代号" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="8">
|
||||
<el-form-item label="学分" prop="xf">
|
||||
<el-input-number v-model="addForm.xf" :min="0" :precision="1" controls-position="right" class="w-full" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="8">
|
||||
<el-form-item label="学时" prop="xs">
|
||||
<el-input-number v-model="addForm.xs" :min="0" controls-position="right" class="w-full" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="8">
|
||||
<el-form-item label="培训层次" prop="pxcc">
|
||||
<el-input v-model="addForm.pxcc" placeholder="请输入培训层次" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="8">
|
||||
<el-form-item label="培训类型" prop="pxlx">
|
||||
<el-input v-model="addForm.pxlx" placeholder="请输入培训类型" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="8">
|
||||
<el-form-item label="课程类型" prop="kclx">
|
||||
<el-input v-model="addForm.kclx" placeholder="请输入课程类型" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="8">
|
||||
<el-form-item label="考试课时" prop="ksks">
|
||||
<el-input-number v-model="addForm.ksks" :min="0" controls-position="right" class="w-full" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="8">
|
||||
<el-form-item label="成绩分制" prop="cjfz">
|
||||
<el-input v-model="addForm.cjfz" placeholder="请输入成绩分制" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="8">
|
||||
<el-form-item label="不计入学员平均分" prop="bjrxypjf">
|
||||
<el-select v-model="addForm.bjrxypjf" class="w-full">
|
||||
<el-option label="否" :value="0" />
|
||||
<el-option label="是" :value="1" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="8">
|
||||
<el-form-item label="理论学时" prop="llxs">
|
||||
<el-input-number v-model="addForm.llxs" :min="0" controls-position="right" class="w-full" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="8">
|
||||
<el-form-item label="实践学时" prop="sjxs">
|
||||
<el-input-number v-model="addForm.sjxs" :min="0" controls-position="right" class="w-full" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="8">
|
||||
<el-form-item label="周课时" prop="zks">
|
||||
<el-input-number v-model="addForm.zks" :min="0" controls-position="right" class="w-full" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="8">
|
||||
<el-form-item label="课类型" prop="klx">
|
||||
<el-input v-model="addForm.klx" placeholder="请输入课类型" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="8">
|
||||
<el-form-item label="考试课时不显示" prop="ksksbxs">
|
||||
<el-select v-model="addForm.ksksbxs" class="w-full">
|
||||
<el-option label="否" :value="0" />
|
||||
<el-option label="是" :value="1" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="8">
|
||||
<el-form-item label="课程统一编号" prop="kctybh">
|
||||
<el-input v-model="addForm.kctybh" placeholder="请输入课程统一编号" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="8">
|
||||
<el-form-item label="辅导答疑课时" prop="fddyks">
|
||||
<el-input-number v-model="addForm.fddyks" :min="0" controls-position="right" class="w-full" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="8">
|
||||
<el-form-item label="自修课时" prop="zxks">
|
||||
<el-input-number v-model="addForm.zxks" :min="0" controls-position="right" class="w-full" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="8">
|
||||
<el-form-item label="修订日期" prop="xdrq">
|
||||
<el-input v-model="addForm.xdrq" placeholder="如 2026-08-24" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="8">
|
||||
<el-form-item label="节次优选级" prop="jcyxj">
|
||||
<el-input v-model="addForm.jcyxj" placeholder="请输入节次优选级" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="8">
|
||||
<el-form-item label="课程支持方式" prop="kczcfs">
|
||||
<el-input v-model="addForm.kczcfs" placeholder="请输入课程支持方式" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="8">
|
||||
<el-form-item label="课程建设方式" prop="kcjsfs">
|
||||
<el-input v-model="addForm.kcjsfs" placeholder="请输入课程建设方式" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="8">
|
||||
<el-form-item label="政治类课程" prop="zzlkc">
|
||||
<el-select v-model="addForm.zzlkc" class="w-full">
|
||||
<el-option label="否" :value="0" />
|
||||
<el-option label="是" :value="1" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="8">
|
||||
<el-form-item label="考核方式" prop="khfs">
|
||||
<el-input v-model="addForm.khfs" placeholder="请输入考核方式" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="8">
|
||||
<el-form-item label="形成性成绩分值" prop="xcxcjfz">
|
||||
<el-input-number v-model="addForm.xcxcjfz" :min="0" controls-position="right" class="w-full" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="8">
|
||||
<el-form-item label="终结性成绩分值" prop="zjxcjfz">
|
||||
<el-input-number v-model="addForm.zjxcjfz" :min="0" controls-position="right" class="w-full" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="8">
|
||||
<el-form-item label="密级" prop="mj">
|
||||
<el-input v-model="addForm.mj" placeholder="请输入密级" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="8">
|
||||
<el-form-item label="评选情况" prop="pxqk">
|
||||
<el-input v-model="addForm.pxqk" placeholder="请输入评选情况" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="24" :md="24">
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="addForm.bz" type="textarea" :rows="2" placeholder="请输入备注" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<div slot="footer">
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="handleCancel">取消</el-button>
|
||||
<el-button type="primary" :loading="addLoading" @click="handleSubmit">保存</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listKb as listSubject, addKb as addSubject, updateKb as updateSubject, deleteKb as deleteSubject, getKb } from '@/api/teachOffice/kb'
|
||||
|
||||
export default {
|
||||
name: 'SubjectIndex',
|
||||
data() {
|
||||
return {
|
||||
// ==================== 1. 顶部状态提示 ====================
|
||||
statusTip: '请输入查询条件',
|
||||
|
||||
// ==================== 2. 查询条件 ====================
|
||||
searchForm: {
|
||||
kmc: '',
|
||||
jc: '',
|
||||
kmdm: '',
|
||||
jysdh: '',
|
||||
kclx: '',
|
||||
pxlx: '',
|
||||
pxcc: ''
|
||||
},
|
||||
|
||||
// ==================== 列表数据 ====================
|
||||
loading: false,
|
||||
tableData: [],
|
||||
total: 0,
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
selectedRows: [],
|
||||
|
||||
// ==================== 文件上传 ====================
|
||||
selectedFile: null,
|
||||
coverExist: false,
|
||||
|
||||
// ==================== 新增/编辑弹窗 ====================
|
||||
dialogVisible: false,
|
||||
addLoading: false,
|
||||
isEdit: false,
|
||||
addForm: this.createEmptyForm(),
|
||||
rules: {
|
||||
kmc: [{ required: true, message: '请输入课名称', trigger: 'blur' }],
|
||||
jysdh: [{ required: true, message: '请输入教研室代号', trigger: 'blur' }],
|
||||
xf: [{ required: true, message: '请输入学分', trigger: 'blur' }],
|
||||
pxcc: [{ required: true, message: '请输入培训层次', trigger: 'blur' }],
|
||||
pxlx: [{ required: true, message: '请输入培训类型', trigger: 'blur' }],
|
||||
kclx: [{ required: true, message: '请输入课程类型', trigger: 'blur' }],
|
||||
ksks: [{ required: true, message: '请输入考试课时', trigger: 'blur' }],
|
||||
cjfz: [{ required: true, message: '请输入成绩分制', trigger: 'blur' }],
|
||||
llxs: [{ required: true, message: '请输入理论学时', trigger: 'blur' }],
|
||||
sjxs: [{ required: true, message: '请输入实践学时', trigger: 'blur' }],
|
||||
zks: [{ required: true, message: '请输入周课时', trigger: 'blur' }],
|
||||
klx: [{ required: true, message: '请输入课类型', trigger: 'blur' }],
|
||||
kctybh: [{ required: true, message: '请输入课程统一编号', trigger: 'blur' }],
|
||||
fddyks: [{ required: true, message: '请输入辅导答疑课时', trigger: 'blur' }],
|
||||
zxks: [{ required: true, message: '请输入自修课时', trigger: 'blur' }],
|
||||
xdrq: [{ required: true, message: '请输入修订日期', trigger: 'blur' }],
|
||||
jcyxj: [{ required: true, message: '请输入节次优选级', trigger: 'blur' }],
|
||||
kczcfs: [{ required: true, message: '请输入课程支持方式', trigger: 'blur' }],
|
||||
kcjsfs: [{ required: true, message: '请输入课程建设方式', trigger: 'blur' }],
|
||||
khfs: [{ required: true, message: '请输入考核方式', trigger: 'blur' }],
|
||||
xcxcjfz: [{ required: true, message: '请输入形成性成绩分值', trigger: 'blur' }],
|
||||
zjxcjfz: [{ required: true, message: '请输入终结性成绩分值', trigger: 'blur' }],
|
||||
mj: [{ required: true, message: '请输入密级', trigger: 'blur' }],
|
||||
pxqk: [{ required: true, message: '请输入评选情况', trigger: 'blur' }]
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
dialogTitle() {
|
||||
return this.isEdit ? '编辑课程科目' : '新增课程科目'
|
||||
},
|
||||
fileName() {
|
||||
return (this.selectedFile && this.selectedFile.name) || '未选择任何文件'
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.fetchList()
|
||||
},
|
||||
methods: {
|
||||
/** 移除空值 */
|
||||
cleanPayload(obj) {
|
||||
const payload = {}
|
||||
Object.keys(obj).forEach(key => {
|
||||
const value = obj[key]
|
||||
if (value !== '' && value !== null && value !== undefined) {
|
||||
payload[key] = value
|
||||
}
|
||||
})
|
||||
return payload
|
||||
},
|
||||
|
||||
/** 创建空白课程科目表单 */
|
||||
createEmptyForm() {
|
||||
return {
|
||||
kbh: '', kmc: '', jysdh: '', bz: '', xh: '', xf: 0, jc: '', xs: 0,
|
||||
pxcc: '', pxlx: '', kclx: '', ksks: 0, py: '', gfmc: '', cjfz: '',
|
||||
bjrxypjf: 0, jxglbmbh: '', llxs: 0, sjxs: 0, kmdm: '', zks: 0, klx: '',
|
||||
ksksbxs: 0, kctybh: '', sydx: '', fddyks: 0, zxks: 0, bb: '', xdrq: '',
|
||||
jcyxj: '', kczcfs: '', kcjsfs: '', jsonzd: '', zzlkc: 0, khfs: '',
|
||||
xcxcjfz: 0, zjxcjfz: 0, mj: '', pxqk: ''
|
||||
}
|
||||
},
|
||||
|
||||
resetForm() {
|
||||
this.addForm = this.createEmptyForm()
|
||||
},
|
||||
|
||||
// ==================== 列表加载 ====================
|
||||
buildQuery() {
|
||||
return this.cleanPayload({
|
||||
pageNum: this.pageNum,
|
||||
pageSize: this.pageSize,
|
||||
kmc: this.searchForm.kmc,
|
||||
jc: this.searchForm.jc,
|
||||
kmdm: this.searchForm.kmdm,
|
||||
jysdh: this.searchForm.jysdh,
|
||||
kclx: this.searchForm.kclx,
|
||||
pxlx: this.searchForm.pxlx,
|
||||
pxcc: this.searchForm.pxcc
|
||||
})
|
||||
},
|
||||
|
||||
fetchList() {
|
||||
this.loading = true
|
||||
listSubject(this.buildQuery())
|
||||
.then(res => {
|
||||
const data = (res && res.data) || {}
|
||||
this.tableData = data.records || []
|
||||
this.total = data.total || 0
|
||||
this.statusTip = `共检索到 ${this.total} 条记录`
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
|
||||
// ==================== 查询 ====================
|
||||
handleQuery() {
|
||||
this.pageNum = 1
|
||||
this.fetchList()
|
||||
},
|
||||
|
||||
handlePageChange(current) {
|
||||
this.pageNum = current
|
||||
this.fetchList()
|
||||
},
|
||||
handleSizeChange(size) {
|
||||
this.pageSize = size
|
||||
this.pageNum = 1
|
||||
this.fetchList()
|
||||
},
|
||||
handleSelectionChange(rows) {
|
||||
this.selectedRows = rows
|
||||
},
|
||||
|
||||
// ==================== 删除所选 ====================
|
||||
handleBatchDelete() {
|
||||
if (this.selectedRows.length === 0) {
|
||||
this.$message.warning('请先选择要删除的记录')
|
||||
return
|
||||
}
|
||||
const kbhList = this.selectedRows.map(r => r.kbh)
|
||||
this.$confirm(`确定删除所选 ${kbhList.length} 条课程科目吗?`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
const tasks = kbhList.map(kbh => deleteSubject(kbh))
|
||||
Promise.all(tasks)
|
||||
.then(() => {
|
||||
this.$message.success(`已删除 ${kbhList.length} 条记录`)
|
||||
this.fetchList()
|
||||
})
|
||||
.catch(() => {
|
||||
this.$message.error('删除失败')
|
||||
})
|
||||
}).catch(() => {})
|
||||
},
|
||||
|
||||
/** 单条删除 */
|
||||
handleDelete(row) {
|
||||
this.$confirm(`确定删除课程科目「${row.kmc || row.kbh}」吗?`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
return deleteSubject(row.kbh)
|
||||
}).then(() => {
|
||||
this.$message.success('删除成功')
|
||||
this.fetchList()
|
||||
}).catch((err) => {
|
||||
if (err && err !== 'cancel') {
|
||||
this.$message.error('删除失败')
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// ==================== 下载(后端暂未提供) ====================
|
||||
handleDownloadCourse() {
|
||||
this.$message.warning('后端暂未提供该接口')
|
||||
},
|
||||
handleDownloadTextbook() {
|
||||
this.$message.warning('后端暂未提供该接口')
|
||||
},
|
||||
handleDownloadTemplate() {
|
||||
this.$message.warning('后端暂未提供该接口')
|
||||
},
|
||||
|
||||
// ==================== 文件上传(后端暂未提供) ====================
|
||||
handleSelectFile() {
|
||||
this.$refs.fileInputRef && this.$refs.fileInputRef.click()
|
||||
},
|
||||
|
||||
handleFileChange(e) {
|
||||
const input = e.target
|
||||
this.selectedFile = (input.files && input.files[0]) || null
|
||||
},
|
||||
|
||||
handleUpload() {
|
||||
this.$message.warning('后端暂未提供该接口')
|
||||
},
|
||||
|
||||
// ==================== 新增/编辑弹窗 ====================
|
||||
handleOpenDialog() {
|
||||
this.isEdit = false
|
||||
this.resetForm()
|
||||
this.dialogVisible = true
|
||||
},
|
||||
|
||||
handleEdit(row) {
|
||||
this.addLoading = true
|
||||
getKb(row.kbh)
|
||||
.then(res => {
|
||||
const data = (res && res.data) || row
|
||||
this.isEdit = true
|
||||
this.addForm = Object.assign(this.createEmptyForm(), data)
|
||||
this.dialogVisible = true
|
||||
})
|
||||
.finally(() => {
|
||||
this.addLoading = false
|
||||
})
|
||||
},
|
||||
|
||||
handleSubmit() {
|
||||
this.$refs.addFormRef.validate((valid) => {
|
||||
if (!valid) return
|
||||
this.addLoading = true
|
||||
const payload = this.cleanPayload({ ...this.addForm })
|
||||
const requestFn = this.isEdit ? updateSubject : addSubject
|
||||
requestFn(payload)
|
||||
.then(() => {
|
||||
this.$message.success(this.isEdit ? '修改成功' : '保存成功')
|
||||
this.dialogVisible = false
|
||||
this.fetchList()
|
||||
})
|
||||
.finally(() => {
|
||||
this.addLoading = false
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
handleCancel() {
|
||||
this.dialogVisible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.app-container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
// ==================== 1. 顶部状态提示 ====================
|
||||
.status-tip {
|
||||
display: inline-block;
|
||||
background: #dc3545;
|
||||
color: #fff;
|
||||
font-size: 12px;
|
||||
padding: 3px 10px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
// ==================== 2. 查询条件区域 ====================
|
||||
.search-card {
|
||||
margin-bottom: 16px;
|
||||
|
||||
.query-field {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 14px;
|
||||
min-height: 32px;
|
||||
|
||||
::v-deep(.el-checkbox) {
|
||||
margin-right: 6px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.q-label {
|
||||
width: 120px;
|
||||
flex-shrink: 0;
|
||||
font-size: 13px;
|
||||
color: #303133;
|
||||
text-align: right;
|
||||
margin-right: 10px;
|
||||
white-space: nowrap;
|
||||
|
||||
&.no-check {
|
||||
margin-left: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.q-control {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.notice {
|
||||
font-size: 12px;
|
||||
color: #f56c6c;
|
||||
margin-top: 8px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// ==================== 3. 操作按钮区域 ====================
|
||||
.action-card {
|
||||
margin-bottom: 16px;
|
||||
|
||||
.action-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
margin-bottom: 12px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 4. 文件上传区域 ====================
|
||||
.upload-card {
|
||||
margin-bottom: 16px;
|
||||
|
||||
.upload-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
|
||||
.upload-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
|
||||
.file-name {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
|
||||
&.has-file {
|
||||
color: #303133;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.upload-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 5. 红色提示文字 ====================
|
||||
.red-tip {
|
||||
font-size: 14px;
|
||||
color: #f56c6c;
|
||||
margin-bottom: 16px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
// ==================== 6. 数据表格区域 ====================
|
||||
.table-card {
|
||||
.pagination-wrapper {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
::v-deep(.el-table .cell) {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.text-danger {
|
||||
color: #f56c6c;
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 新增/编辑弹窗 ====================
|
||||
.add-dialog {
|
||||
::v-deep(.el-dialog__body) {
|
||||
max-height: 70vh;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.add-form {
|
||||
.w-full {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,513 @@
|
||||
<template>
|
||||
<div class="app-container task-plan-page">
|
||||
<!-- ==================== 页面标题 ==================== -->
|
||||
<div class="page-title">教学任务计划管理</div>
|
||||
|
||||
<!-- ==================== 1. 查询条件区域 ==================== -->
|
||||
<el-card shadow="never" class="search-card">
|
||||
<el-form :inline="true" :model="searchForm" class="search-form">
|
||||
<el-form-item label="任务名称">
|
||||
<el-input
|
||||
v-model="searchForm.rwmc"
|
||||
placeholder="请输入任务名称"
|
||||
clearable
|
||||
style="width: 180px"
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="年度">
|
||||
<el-select v-model="searchForm.nd" placeholder="请选择年度" clearable style="width: 140px">
|
||||
<el-option v-for="y in yearOptions" :key="y" :label="y" :value="y" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态">
|
||||
<el-select v-model="searchForm.zt" placeholder="请选择状态" clearable style="width: 140px">
|
||||
<el-option
|
||||
v-for="opt in statusOptions"
|
||||
:key="opt.value"
|
||||
:label="opt.label"
|
||||
:value="opt.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" @click="handleQuery">查询</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<!-- ==================== 2. 操作区域 ==================== -->
|
||||
<div class="list-toolbar">
|
||||
<div class="left-group">
|
||||
<el-button type="primary" icon="el-icon-plus" @click="handleAdd">新建教学任务</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ==================== 3. 数据表格 ==================== -->
|
||||
<el-card shadow="never" class="table-card">
|
||||
<el-table v-loading="loading" :data="tableData" border stripe class="task-table">
|
||||
<template slot="empty">
|
||||
<span>无数据!</span>
|
||||
</template>
|
||||
<el-table-column type="index" label="序号" width="70" align="center" />
|
||||
<el-table-column prop="rwmc" label="任务名称" width="250" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="nd" label="年度" width="150" align="center" />
|
||||
<el-table-column label="状态" width="100" align="center">
|
||||
<template slot-scope="{ row }">
|
||||
<el-tag :type="row.zt === '发布' ? 'success' : 'info'" size="small">
|
||||
{{ row.zt || '-' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="fbsj" label="发布时间" width="180" align="center" :formatter="fmtDateTime" />
|
||||
<el-table-column prop="jssj" label="结束时间" width="180" align="center" :formatter="fmtDateTime" />
|
||||
<el-table-column prop="cjsj" label="创建时间" width="180" align="center" :formatter="fmtDateTime" />
|
||||
<el-table-column prop="jcxqscsj" label="教材需求生成时间" align="center" :formatter="fmtDateTime" />
|
||||
<el-table-column label="操作" width="300" align="center" fixed="right">
|
||||
<template slot-scope="{ row }">
|
||||
<el-button type="text" size="small" @click="handleDetail(row)">详情</el-button>
|
||||
<el-button type="text" size="small" @click="handleEdit(row)">编辑</el-button>
|
||||
<el-button
|
||||
type="text"
|
||||
size="small"
|
||||
:disabled="row.zt === '发布'"
|
||||
@click="handlePublish(row)"
|
||||
>发布</el-button>
|
||||
<el-button type="text" size="small" class="text-danger" @click="handleDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-pagination
|
||||
:current-page="pageNum"
|
||||
:page-size="pageSize"
|
||||
:total="total"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
class="pagination"
|
||||
@current-change="handlePageChange"
|
||||
@size-change="handleSizeChange"
|
||||
/>
|
||||
</el-card>
|
||||
|
||||
<!-- ==================== 4. 新增/修改对话框 ==================== -->
|
||||
<el-dialog
|
||||
:visible="dialogVisible"
|
||||
:title="dialogTitle"
|
||||
width="560px"
|
||||
:close-on-click-modal="false"
|
||||
@update:visible="val => dialogVisible = val"
|
||||
>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="140px" class="add-form">
|
||||
<el-form-item v-if="isEdit" label="编号" prop="bh">
|
||||
<el-input v-model="form.bh" disabled placeholder="编号不可修改" />
|
||||
</el-form-item>
|
||||
<el-form-item v-else label="编号">
|
||||
<el-input disabled placeholder="新增后由系统自动生成" />
|
||||
</el-form-item>
|
||||
<el-form-item label="任务名称" prop="rwmc">
|
||||
<el-input v-model="form.rwmc" placeholder="请输入任务名称" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="年度" prop="nd">
|
||||
<el-select v-model="form.nd" placeholder="请选择年度" style="width: 100%">
|
||||
<el-option v-for="y in yearOptions" :key="y" :label="y" :value="y" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="zt">
|
||||
<el-select v-model="form.zt" placeholder="请选择状态" style="width: 100%">
|
||||
<el-option
|
||||
v-for="opt in statusOptions"
|
||||
:key="opt.value"
|
||||
:label="opt.label"
|
||||
:value="opt.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="发布时间">
|
||||
<el-date-picker
|
||||
v-model="form.fbsj"
|
||||
type="datetime"
|
||||
value-format="yyyy-MM-ddTHH:mm:ss"
|
||||
placeholder="请选择发布时间"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="结束时间">
|
||||
<el-date-picker
|
||||
v-model="form.jssj"
|
||||
type="datetime"
|
||||
value-format="yyyy-MM-ddTHH:mm:ss"
|
||||
placeholder="请选择结束时间"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="教材需求生成时间">
|
||||
<el-date-picker
|
||||
v-model="form.jcxqscsj"
|
||||
type="datetime"
|
||||
value-format="yyyy-MM-ddTHH:mm:ss"
|
||||
placeholder="请选择教材需求生成时间"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer">
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="saving" @click="handleSubmit">确定</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- ==================== 5. 详情对话框 ==================== -->
|
||||
<el-dialog
|
||||
:visible="detailVisible"
|
||||
title="教学任务详情"
|
||||
width="620px"
|
||||
:close-on-click-modal="false"
|
||||
@update:visible="val => detailVisible = val"
|
||||
>
|
||||
<el-descriptions v-if="detailData.bh" :column="2" border>
|
||||
<el-descriptions-item label="编号">{{ detailData.bh || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="任务名称">{{ detailData.rwmc || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="年度">{{ fmtVal(detailData.nd) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="状态">{{ detailData.zt || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="发布时间">{{ fmtVal(detailData.fbsj) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="结束时间">{{ fmtVal(detailData.jssj) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">{{ fmtVal(detailData.cjsj) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="教材需求生成时间">{{ fmtVal(detailData.jcxqscsj) }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<div v-else v-loading="detailLoading" class="detail-empty">加载中...</div>
|
||||
<div slot="footer">
|
||||
<el-button @click="detailVisible = false">关闭</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* 教学任务计划管理(完整管理页)
|
||||
* 对应菜单:teachOffice/taskPlan
|
||||
* 提供 teachingTask 全套真实接口操作:分页查询 list、详情 get?bh=、新增 add、编辑 update、
|
||||
* 发布 batchPublish?bh=(需先填写教研室任务书)、删除 delete?bh=(级联删除教研室任务书)
|
||||
* 只读查询视图在 src/views/teachBusiness/teachingTask/index.vue(教学任务列表)提供,避免重复操作同一实体
|
||||
*/
|
||||
import {
|
||||
listTeachingTask,
|
||||
getTeachingTask,
|
||||
addTeachingTask,
|
||||
updateTeachingTask,
|
||||
deleteTeachingTask,
|
||||
publishTeachingTask
|
||||
} from '@/api/teachBusiness/teachingTask'
|
||||
import { listAllSemester } from '@/api/teachBusiness/semester'
|
||||
|
||||
export default {
|
||||
name: 'TaskPlan',
|
||||
data() {
|
||||
return {
|
||||
// ==================== 1. 查询条件 ====================
|
||||
searchForm: {
|
||||
rwmc: '',
|
||||
nd: undefined,
|
||||
zt: ''
|
||||
},
|
||||
yearOptions: [],
|
||||
statusOptions: [
|
||||
{ label: '未发布', value: '未发布' },
|
||||
{ label: '发布', value: '发布' }
|
||||
],
|
||||
|
||||
// ==================== 2. 表格数据 ====================
|
||||
loading: false,
|
||||
tableData: [],
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
|
||||
// ==================== 3. 新增/修改表单 ====================
|
||||
dialogVisible: false,
|
||||
dialogTitle: '新建教学任务',
|
||||
isEdit: false,
|
||||
saving: false,
|
||||
form: this.createEmptyForm(),
|
||||
rules: {
|
||||
rwmc: [{ required: true, message: '请输入任务名称', trigger: 'blur' }],
|
||||
nd: [{ required: true, message: '请选择年度', trigger: 'change' }],
|
||||
zt: [{ required: true, message: '请选择状态', trigger: 'change' }]
|
||||
},
|
||||
|
||||
// ==================== 4. 详情 ====================
|
||||
detailVisible: false,
|
||||
detailLoading: false,
|
||||
detailData: {}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadYearOptions().then(() => this.fetchList())
|
||||
},
|
||||
methods: {
|
||||
/* ---------- 年度下拉(数据来自 /semester/all,禁止硬编码) ---------- */
|
||||
loadYearOptions() {
|
||||
return listAllSemester().then(response => {
|
||||
const list = response.data || []
|
||||
const map = {}
|
||||
list.forEach(item => {
|
||||
if (item.nd !== null && item.nd !== undefined && item.nd !== '') map[item.nd] = item
|
||||
})
|
||||
const arr = Object.keys(map).sort((a, b) => String(b).localeCompare(String(a)))
|
||||
this.yearOptions = arr
|
||||
// 默认年度:优先当前学期(dqxq=true),否则取最新年度
|
||||
const current = list.find(item => item.dqxq === true)
|
||||
const defaultNd = (current && current.nd) || arr[0]
|
||||
if (!this.searchForm.nd) this.searchForm.nd = defaultNd
|
||||
return arr
|
||||
}).catch(() => {
|
||||
this.yearOptions = []
|
||||
return []
|
||||
})
|
||||
},
|
||||
|
||||
/* ---------- 通用格式化 ---------- */
|
||||
fmtDateTime(row, column, cellValue) {
|
||||
if (cellValue === null || cellValue === undefined || cellValue === '') return '-'
|
||||
return String(cellValue).replace('T', ' ').slice(0, 16)
|
||||
},
|
||||
fmtVal(val) {
|
||||
if (val === null || val === undefined || val === '') return '-'
|
||||
return String(val).replace('T', ' ').slice(0, 16)
|
||||
},
|
||||
/** 移除空值(''/null/undefined),保留 0 等有效值 */
|
||||
cleanPayload(obj) {
|
||||
const payload = {}
|
||||
Object.keys(obj).forEach(key => {
|
||||
const value = obj[key]
|
||||
if (value !== '' && value !== null && value !== undefined) {
|
||||
payload[key] = value
|
||||
}
|
||||
})
|
||||
return payload
|
||||
},
|
||||
|
||||
/* ---------- 列表加载 ---------- */
|
||||
fetchList() {
|
||||
this.loading = true
|
||||
const params = { pageNum: this.pageNum, pageSize: this.pageSize }
|
||||
if (this.searchForm.rwmc && this.searchForm.rwmc.trim()) params.rwmc = this.searchForm.rwmc.trim()
|
||||
if (this.searchForm.nd !== undefined && this.searchForm.nd !== null && this.searchForm.nd !== '') {
|
||||
params.nd = this.searchForm.nd
|
||||
}
|
||||
if (this.searchForm.zt && this.searchForm.zt.trim()) params.zt = this.searchForm.zt.trim()
|
||||
listTeachingTask(params).then(res => {
|
||||
const data = (res && res.data) || {}
|
||||
this.tableData = data.records || []
|
||||
this.total = data.total || 0
|
||||
this.loading = false
|
||||
}).catch(() => {
|
||||
this.tableData = []
|
||||
this.total = 0
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
|
||||
handleQuery() {
|
||||
this.pageNum = 1
|
||||
this.fetchList()
|
||||
},
|
||||
|
||||
handlePageChange(page) {
|
||||
this.pageNum = page
|
||||
this.fetchList()
|
||||
},
|
||||
|
||||
handleSizeChange(size) {
|
||||
this.pageSize = size
|
||||
this.pageNum = 1
|
||||
this.fetchList()
|
||||
},
|
||||
|
||||
/* ---------- 新增 / 修改 ---------- */
|
||||
createEmptyForm() {
|
||||
return {
|
||||
bh: '',
|
||||
rwmc: '',
|
||||
nd: undefined,
|
||||
zt: '未发布',
|
||||
fbsj: '',
|
||||
jssj: '',
|
||||
jcxqscsj: ''
|
||||
}
|
||||
},
|
||||
|
||||
handleAdd() {
|
||||
this.form = this.createEmptyForm()
|
||||
if (!this.form.nd && this.searchForm.nd) this.form.nd = this.searchForm.nd
|
||||
this.isEdit = false
|
||||
this.dialogTitle = '新建教学任务'
|
||||
this.dialogVisible = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.formRef && this.$refs.formRef.clearValidate()
|
||||
})
|
||||
},
|
||||
|
||||
handleEdit(row) {
|
||||
this.form = {
|
||||
bh: row.bh || '',
|
||||
rwmc: row.rwmc || '',
|
||||
nd: row.nd,
|
||||
zt: row.zt || '未发布',
|
||||
fbsj: row.fbsj || '',
|
||||
jssj: row.jssj || '',
|
||||
jcxqscsj: row.jcxqscsj || ''
|
||||
}
|
||||
this.isEdit = true
|
||||
this.dialogTitle = '修改教学任务'
|
||||
this.dialogVisible = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.formRef && this.$refs.formRef.clearValidate()
|
||||
})
|
||||
},
|
||||
|
||||
handleSubmit() {
|
||||
this.$refs.formRef.validate(valid => {
|
||||
if (!valid) {
|
||||
this.$message.warning('请完善必填项后再提交')
|
||||
return
|
||||
}
|
||||
const payload = this.cleanPayload(this.buildPayload())
|
||||
this.saving = true
|
||||
const req = this.isEdit ? updateTeachingTask(payload) : addTeachingTask(payload)
|
||||
req.then(() => {
|
||||
this.$message.success(this.isEdit ? '修改成功' : '新建成功')
|
||||
this.dialogVisible = false
|
||||
this.fetchList()
|
||||
}).catch(() => {}).finally(() => {
|
||||
this.saving = false
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
/** 构建请求体:年度转数字以匹配后端 Integer,时间字段有值才传 */
|
||||
buildPayload() {
|
||||
const f = this.form
|
||||
const payload = {}
|
||||
if (this.isEdit && f.bh && String(f.bh).trim()) payload.bh = String(f.bh).trim()
|
||||
if (f.rwmc && String(f.rwmc).trim()) payload.rwmc = String(f.rwmc).trim()
|
||||
if (f.nd !== undefined && f.nd !== null && f.nd !== '') payload.nd = Number(f.nd)
|
||||
if (f.zt && String(f.zt).trim()) payload.zt = String(f.zt).trim()
|
||||
if (f.fbsj) payload.fbsj = f.fbsj
|
||||
if (f.jssj) payload.jssj = f.jssj
|
||||
if (f.jcxqscsj) payload.jcxqscsj = f.jcxqscsj
|
||||
return payload
|
||||
},
|
||||
|
||||
/* ---------- 详情 ---------- */
|
||||
handleDetail(row) {
|
||||
this.detailVisible = true
|
||||
this.detailLoading = true
|
||||
this.detailData = {}
|
||||
getTeachingTask(row.bh).then(res => {
|
||||
this.detailData = (res && res.data) || {}
|
||||
this.detailLoading = false
|
||||
}).catch(() => {
|
||||
this.detailLoading = false
|
||||
})
|
||||
},
|
||||
|
||||
/* ---------- 删除 ---------- */
|
||||
handleDelete(row) {
|
||||
this.$confirm(`确定要删除「${row.rwmc || row.bh || '该记录'}」吗?删除将级联删除关联的教研室任务书。`, '系统提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => deleteTeachingTask(row.bh)).then(() => {
|
||||
this.$message.success('删除成功')
|
||||
this.fetchList()
|
||||
}).catch(() => {})
|
||||
},
|
||||
|
||||
/* ---------- 发布 ---------- */
|
||||
handlePublish(row) {
|
||||
this.$confirm(`确定要发布教学任务「${row.rwmc || row.bh || '该记录'}」吗?发布前请先填写关联的教研室任务书。`, '系统提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => publishTeachingTask(row.bh)).then(() => {
|
||||
this.$message.success('发布成功')
|
||||
this.fetchList()
|
||||
}).catch(() => {})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.app-container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.task-plan-page {
|
||||
.page-title {
|
||||
text-align: center;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
// ========== 1. 查询条件区域 ==========
|
||||
.search-card {
|
||||
margin-bottom: 16px;
|
||||
|
||||
.search-form {
|
||||
.el-form-item {
|
||||
margin-bottom: 0;
|
||||
margin-right: 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 2. 工具栏 ==========
|
||||
.list-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
margin-bottom: 16px;
|
||||
|
||||
.left-group,
|
||||
.right-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 3. 数据表格区域 ==========
|
||||
.table-card {
|
||||
.task-table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.text-danger {
|
||||
color: #f56c6c;
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 4. 详情 ==========
|
||||
.detail-empty {
|
||||
min-height: 120px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #909399;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,388 @@
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<!-- 查询条件 -->
|
||||
<el-card shadow="never" class="search-card">
|
||||
<el-form :model="searchForm" label-width="110px" class="search-form">
|
||||
<el-row :gutter="0">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="开始日期">
|
||||
<el-date-picker v-model="searchForm.ksrq" type="date" value-format="yyyy-MM-dd" placeholder="选择开始日期"
|
||||
class="w-full" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="结束日期">
|
||||
<el-date-picker v-model="searchForm.jsrq" type="date" value-format="yyyy-MM-dd" placeholder="选择结束日期"
|
||||
class="w-full" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="0">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="课程名称">
|
||||
<el-input v-model="searchForm.kcmc" placeholder="请输入课程名称" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="教研室">
|
||||
<el-select v-model="searchForm.jys" placeholder="请选择教研室" clearable class="w-full">
|
||||
<el-option v-for="item in jysOptions" :key="item" :label="item" :value="item" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="0">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="教学方法">
|
||||
<el-select v-model="searchForm.jxff" placeholder="请选择教学方法" clearable class="w-full">
|
||||
<el-option v-for="item in jxffOptions" :key="item" :label="item" :value="item" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="授课教员">
|
||||
<el-select v-model="searchForm.skjy" placeholder="请选择授课教员" clearable class="w-full">
|
||||
<el-option v-for="item in skjyOptions" :key="item" :label="item" :value="item" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="0">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="改动情况">
|
||||
<div class="change-options">
|
||||
<el-radio-group v-model="searchForm.createSource">
|
||||
<el-radio v-for="item in createSourceOptions" :key="item" :label="item">{{ item }}</el-radio>
|
||||
</el-radio-group>
|
||||
<el-checkbox-group v-model="searchForm.changeTypes">
|
||||
<el-checkbox v-for="item in changeTypeOptions" :key="item" :label="item">{{ item }}</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="0">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="任务类别">
|
||||
<el-select v-model="searchForm.rwlb" placeholder="请选择任务类别" clearable class="w-full">
|
||||
<el-option v-for="item in rwlbOptions" :key="item" :label="item" :value="item" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="培训期班">
|
||||
<el-input v-model="searchForm.pxqb" placeholder="请输入培训期班" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
|
||||
<div class="search-actions">
|
||||
<el-button type="primary" @click="handleSearch">查询</el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<el-card shadow="never" class="table-card">
|
||||
<el-table v-loading="loading" :data="tableData" stripe border highlight-current-row
|
||||
@selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="50" align="center" />
|
||||
<el-table-column type="index" label="序号" width="60" align="center" />
|
||||
<el-table-column prop="rwlb" label="任务类别" width="130" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="jyxm" label="主讲教员" width="100" align="center" />
|
||||
<el-table-column prop="rq" label="日期" width="150" align="center">
|
||||
<template slot-scope="scope">{{ scope.row.rq ? formatDate(scope.row.rq) : '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="节次" width="80" align="center">
|
||||
<template slot-scope="scope">
|
||||
{{ scope.row.qsjc && scope.row.jsjc ? `${scope.row.qsjc}-${scope.row.jsjc}节` : (scope.row.qsjc || '-') }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="kcmc" label="课程名称" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column prop="pxqbxx" label="培训期班信息" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column label="人数" width="70" align="center">
|
||||
<template slot-scope="scope">{{ scope.row.sdrs || scope.row.ydrs || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="分组指导教员" width="120" align="center">
|
||||
<template slot-scope="scope">{{ scope.row.fzyzdjy || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="jsfs" label="教学方法" width="100" align="center" />
|
||||
<el-table-column prop="zt" label="日志状态" width="100" align="center" />
|
||||
<el-table-column prop="gdqk" label="变动汇总" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column prop="jxrzcjfs" label="创建方式" width="100" align="center" />
|
||||
<el-table-column label="操作" width="80" align="center" fixed="right" class-name="table-action-column">
|
||||
<template slot-scope="scope">
|
||||
<el-button type="text" @click="handleDetail(scope.row)">详细</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="pagination-wrapper">
|
||||
<el-pagination :current-page="pageNum" :page-size="pageSize" :total="total" :page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper" background @current-change="handlePageChange"
|
||||
@size-change="handleSizeChange" />
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 日志详情对话框 -->
|
||||
<el-dialog :visible.sync="detailVisible" title="教学日志详情" width="700px" destroy-on-close>
|
||||
<div v-loading="detailLoading">
|
||||
<el-descriptions v-if="!detailLoading" :column="2" border>
|
||||
<el-descriptions-item label="编号">{{ detailData.bh || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="统一编号">{{ detailData.tybh || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="教员编号">{{ detailData.jybh || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="教员姓名">{{ detailData.jyxm || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="课程名称">{{ detailData.kcmc || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="任务类别">{{ detailData.rwlb || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="日期">{{ detailData.rq || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="节次">
|
||||
{{ detailData.qsjc && detailData.jsjc ? `${detailData.qsjc}-${detailData.jsjc}节` : '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="教学方法">{{ detailData.jsfs || detailData.jxff || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="创建方式">{{ detailData.jxrzcjfs || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="应到人数">{{ detailData.ydrs || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="实到人数">{{ detailData.sdrs || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="培训期班信息" :span="2">{{ detailData.pxqbxx || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="授课地点" :span="2">{{ detailData.skdd || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="状态">{{ detailData.zt || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="教研室">{{ detailData.jysmc || detailData.jys || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="变动汇总" :span="2">{{ detailData.gdqk || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="变动详细情况" :span="2">{{ detailData.gdxxqk || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="退回意见" :span="2">{{ detailData.thyj || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">{{ detailData.cjsj || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="修改时间">{{ detailData.xgsj || '-' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="detailVisible = false">关 闭</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { formatDate } from '@/utils/index'
|
||||
import { getAuditedTeachingLogList, getTeachingLogByBh } from '@/api/log'
|
||||
|
||||
export default {
|
||||
name: 'AuditedView',
|
||||
data() {
|
||||
return {
|
||||
// ==================== 查询表单 ====================
|
||||
searchForm: {
|
||||
ksrq: '',
|
||||
jsrq: '',
|
||||
kcmc: '',
|
||||
jys: '',
|
||||
jxff: '',
|
||||
skjy: '',
|
||||
createSource: '',
|
||||
changeTypes: [],
|
||||
rwlb: '',
|
||||
pxqb: ''
|
||||
},
|
||||
|
||||
jysOptions: ['教研室', '防空兵系', '通信工程系', '装甲兵系'],
|
||||
jxffOptions: ['理论讲授', '讨论', '实操', '演示'],
|
||||
skjyOptions: ['教务处', '张三', '李四', '王五'],
|
||||
rwlbOptions: ['全军院校训练规划内任务', '其他任务'],
|
||||
createSourceOptions: ['由教学计划创建', '已改动教学计划', '手动拟制'],
|
||||
changeTypeOptions: [
|
||||
'教员变动',
|
||||
'时间变动',
|
||||
'课程变动',
|
||||
'授课方式变动',
|
||||
'任务类别变动',
|
||||
'期班变动',
|
||||
'地点变动',
|
||||
'课题变动'
|
||||
],
|
||||
|
||||
// ==================== 数据表格 ====================
|
||||
loading: false,
|
||||
tableData: [],
|
||||
selectedRows: [],
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
total: 0,
|
||||
|
||||
// ==================== 详情 ====================
|
||||
detailVisible: false,
|
||||
detailLoading: false,
|
||||
detailData: {}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.fetchList()
|
||||
},
|
||||
methods: {
|
||||
handleSelectionChange(rows) {
|
||||
this.selectedRows = rows
|
||||
},
|
||||
|
||||
/** 根据已填写的查询值构建实际查询参数 */
|
||||
buildSearchParams() {
|
||||
const form = this.searchForm
|
||||
const params = { pageNum: this.pageNum, pageSize: this.pageSize }
|
||||
if (form.ksrq) params.ksrq = form.ksrq
|
||||
if (form.jsrq) params.jsrq = form.jsrq
|
||||
if (form.kcmc) params.kcmc = form.kcmc
|
||||
if (form.jys) params.jys = form.jys
|
||||
if (form.jxff) params.jxff = form.jxff
|
||||
if (form.skjy) params.skjy = form.skjy
|
||||
if (form.rwlb) params.rwlb = form.rwlb
|
||||
if (form.pxqb) params.pxqb = form.pxqb
|
||||
if (form.createSource) params.gdqk = form.createSource
|
||||
if (form.changeTypes.length > 0) {
|
||||
params.gdxxqk = form.changeTypes.join(',')
|
||||
}
|
||||
return params
|
||||
},
|
||||
|
||||
extractListData(res) {
|
||||
const data = res.data || res
|
||||
const list = data.records || data.list || data.rows || []
|
||||
const totalCount = data.total ?? data.totalCount ?? data.totalRows ?? 0
|
||||
return { list, total: totalCount }
|
||||
},
|
||||
|
||||
fetchList() {
|
||||
this.loading = true
|
||||
return getAuditedTeachingLogList(this.buildSearchParams())
|
||||
.then((res) => {
|
||||
if (res.code === 200 || res.code === 0) {
|
||||
const { list, total } = this.extractListData(res)
|
||||
this.tableData = list
|
||||
this.total = total
|
||||
}
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
|
||||
handleSearch() {
|
||||
this.pageNum = 1
|
||||
this.fetchList()
|
||||
},
|
||||
|
||||
handlePageChange(page) {
|
||||
this.pageNum = page
|
||||
this.fetchList()
|
||||
},
|
||||
|
||||
handleSizeChange(size) {
|
||||
this.pageSize = size
|
||||
this.pageNum = 1
|
||||
this.fetchList()
|
||||
},
|
||||
|
||||
handleDetail(row) {
|
||||
this.detailData = { ...row }
|
||||
this.detailVisible = true
|
||||
const bh = row.bh
|
||||
if (!bh) return
|
||||
this.detailLoading = true
|
||||
getTeachingLogByBh(String(bh))
|
||||
.then((res) => {
|
||||
if ((res.code === 200 || res.code === 0) && res.data) {
|
||||
this.detailData = res.data
|
||||
}
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
this.detailLoading = false
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.page-container {
|
||||
padding: 4px 8px;
|
||||
}
|
||||
|
||||
.search-card {
|
||||
margin-bottom: 16px;
|
||||
|
||||
.search-form {
|
||||
.w-full {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
::v-deep .el-row {
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .el-col {
|
||||
border-right: 1px solid #ebeef5;
|
||||
|
||||
&:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .el-form-item__label {
|
||||
white-space: normal;
|
||||
line-height: 1.4;
|
||||
text-align: left;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.el-form-item {
|
||||
margin-bottom: 0;
|
||||
padding: 10px 12px;
|
||||
}
|
||||
|
||||
.change-options {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
|
||||
::v-deep .el-radio-group,
|
||||
::v-deep .el-checkbox-group {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.search-tip {
|
||||
padding: 6px 12px 0;
|
||||
font-size: 12px;
|
||||
color: #f56c6c;
|
||||
}
|
||||
|
||||
.search-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 12px;
|
||||
border-top: 1px solid #ebeef5;
|
||||
}
|
||||
}
|
||||
|
||||
.table-card {
|
||||
.el-table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.pagination-wrapper {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 16px 0 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,542 @@
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<!-- 查询条件 -->
|
||||
<el-card shadow="never" class="search-card">
|
||||
<el-form :model="searchForm" label-width="110px" class="search-form">
|
||||
<el-row :gutter="0">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="开始日期">
|
||||
<el-date-picker v-model="searchForm.ksrq" type="date" value-format="yyyy-MM-dd" placeholder="选择开始日期"
|
||||
class="w-full" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="结束日期">
|
||||
<el-date-picker v-model="searchForm.jsrq" type="date" value-format="yyyy-MM-dd" placeholder="选择结束日期"
|
||||
class="w-full" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="0">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="课程名称">
|
||||
<el-input v-model="searchForm.kcmc" placeholder="请输入课程名称" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="教研室">
|
||||
<el-select v-model="searchForm.jys" placeholder="请选择教研室" clearable class="w-full">
|
||||
<el-option v-for="item in jysOptions" :key="item" :label="item" :value="item" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="0">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="教学方法">
|
||||
<el-select v-model="searchForm.jxff" placeholder="请选择教学方法" clearable class="w-full">
|
||||
<el-option v-for="item in jxffOptions" :key="item" :label="item" :value="item" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="日志状态">
|
||||
<el-select v-model="searchForm.rzzt" placeholder="请选择日志状态" clearable class="w-full">
|
||||
<el-option v-for="item in rzztOptions" :key="item" :label="item" :value="item" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="0">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="授课教员">
|
||||
<el-select v-model="searchForm.skjy" placeholder="请选择授课教员" clearable class="w-full">
|
||||
<el-option v-for="item in skjyOptions" :key="item" :label="item" :value="item" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="培训期班">
|
||||
<el-input v-model="searchForm.pxqb" placeholder="请输入培训期班" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="0">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="改动情况">
|
||||
<div class="change-options">
|
||||
<el-radio-group v-model="searchForm.changeType">
|
||||
<el-radio v-for="item in changeTypeOptions" :key="item.value" :label="item.value">{{ item.label
|
||||
}}</el-radio>
|
||||
</el-radio-group>
|
||||
<el-checkbox-group v-model="searchForm.changeTypes">
|
||||
<el-checkbox v-for="item in changeTypeList" :key="item.value" :label="item.value">{{ item.label
|
||||
}}</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="任务类别">
|
||||
<el-select v-model="searchForm.rwlb" placeholder="请选择任务类别" clearable class="w-full">
|
||||
<el-option v-for="item in rwlbOptions" :key="item" :label="item" :value="item" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
|
||||
<div class="search-actions">
|
||||
<div class="left-group">
|
||||
<el-button type="danger" plain @click="handleDeleteSelected">删除所选</el-button>
|
||||
<el-button type="primary" @click="handleBatchReport">批准上报所选</el-button>
|
||||
<el-button @click="handleRecheck">重新检查通报变更情况</el-button>
|
||||
<el-button icon="el-icon-download" @click="handleExport">导出到 Word</el-button>
|
||||
<el-button icon="el-icon-refresh" @click="handleRefresh">刷新</el-button>
|
||||
<el-button icon="el-icon-upload2" :loading="importing" @click="handleImportExcel">导入教学日志</el-button>
|
||||
<el-button icon="el-icon-download" @click="handleDownloadTemplate">下载模板</el-button>
|
||||
</div>
|
||||
<el-button type="primary" @click="handleSearch">查询</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 隐藏的文件选择器,用于导入 Excel -->
|
||||
<input ref="importFileInput" type="file" accept=".xls,.xlsx" style="display: none" @change="handleImportFileChange" />
|
||||
</el-card>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<el-card shadow="never" class="table-card">
|
||||
<el-table v-loading="loading" :data="tableData" stripe border highlight-current-row
|
||||
@selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="50" align="center" />
|
||||
<el-table-column type="index" label="序号" width="60" align="center" />
|
||||
<el-table-column prop="rwlb" label="库任务类别" width="120" align="center" />
|
||||
<el-table-column prop="jyxm" label="主讲教员" width="100" align="center" />
|
||||
<el-table-column prop="rq" label="日期" width="150" align="center">
|
||||
<template slot-scope="scope">{{ scope.row.rq ? formatDate(scope.row.rq) : '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="节次" width="80" align="center">
|
||||
<template slot-scope="scope">
|
||||
{{ scope.row.qsjc && scope.row.jsjc ? `${scope.row.qsjc}-${scope.row.jsjc}节` : (scope.row.qsjc || '-') }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="kcmc" label="课程名称" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column prop="pxqbxx" label="培训期班信息" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column label="人数" width="70" align="center">
|
||||
<template slot-scope="scope">{{ scope.row.sdrs || scope.row.ydrs || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="分组指导教员" width="120" align="center">
|
||||
<template slot-scope="scope">{{ scope.row.fzyzdjy || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="jsfs" label="教学方法" width="100" align="center" />
|
||||
<el-table-column prop="zt" label="日志状态" width="100" align="center" />
|
||||
<el-table-column prop="gdqk" label="变动汇总" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column prop="jxrzcjfs" label="创建方式" width="100" align="center" />
|
||||
<el-table-column label="操作" width="80" align="center" fixed="right" class-name="table-action-column">
|
||||
<template slot-scope="scope">
|
||||
<el-button type="text" @click="handleDetail(scope.row)">详细</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="pagination-wrapper">
|
||||
<el-pagination :current-page="pageNum" :page-size="pageSize" :total="total" :page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper" background @current-change="handlePageChange"
|
||||
@size-change="handleSizeChange" />
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 日志详情对话框 -->
|
||||
<el-dialog :visible.sync="detailVisible" title="教学日志详情" width="700px" destroy-on-close>
|
||||
<div v-loading="detailLoading">
|
||||
<el-descriptions v-if="!detailLoading" :column="2" border>
|
||||
<el-descriptions-item label="编号">{{ detailData.bh || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="统一编号">{{ detailData.tybh || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="教员编号">{{ detailData.jybh || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="教员姓名">{{ detailData.jyxm || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="课程名称">{{ detailData.kcmc || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="任务类别">{{ detailData.rwlb || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="日期">{{ detailData.rq || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="节次">
|
||||
{{ detailData.qsjc && detailData.jsjc ? `${detailData.qsjc}-${detailData.jsjc}节` : '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="教学方法">{{ detailData.jsfs || detailData.jxff || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="创建方式">{{ detailData.jxrzcjfs || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="应到人数">{{ detailData.ydrs || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="实到人数">{{ detailData.sdrs || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="培训期班信息" :span="2">{{ detailData.pxqbxx || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="授课地点" :span="2">{{ detailData.skdd || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="状态">{{ detailData.zt || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="教研室">{{ detailData.jysmc || detailData.jys || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="变动汇总" :span="2">{{ detailData.gdqk || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="变动详细情况" :span="2">{{ detailData.gdxxqk || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="退回意见" :span="2">{{ detailData.thyj || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">{{ detailData.cjsj || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="修改时间">{{ detailData.xgsj || '-' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="detailVisible = false">关 闭</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { formatDate } from '@/utils/index'
|
||||
import {
|
||||
getTeachingLogList,
|
||||
submitTeachingLog,
|
||||
exportTeachingLog,
|
||||
importTeachingLogExcel,
|
||||
downloadTeachingLogTemplate,
|
||||
getTeachingLogByBh
|
||||
} from '@/api/log'
|
||||
|
||||
export default {
|
||||
name: 'LogView',
|
||||
data() {
|
||||
return {
|
||||
// ==================== 查询表单 ====================
|
||||
searchForm: {
|
||||
ksrq: '',
|
||||
jsrq: '',
|
||||
kcmc: '',
|
||||
jys: '',
|
||||
jxff: '',
|
||||
rzzt: '',
|
||||
skjy: '',
|
||||
pxqb: '',
|
||||
changeType: '',
|
||||
changeTypes: [],
|
||||
rwlb: ''
|
||||
},
|
||||
|
||||
jysOptions: ['教研室', '防空兵系', '通信工程系', '装甲兵系'],
|
||||
jxffOptions: ['理论讲授', '讨论', '实操', '演示', '实验', '演习'],
|
||||
skjyOptions: ['教务处', '张三', '李四', '王五', '赵六'],
|
||||
rzztOptions: ['教务部门导入教员类', '管理员手工添加'],
|
||||
rwlbOptions: ['全军院校训练规划内任务', '其他任务'],
|
||||
changeTypeOptions: [
|
||||
{ label: '与教学计划一致', value: '与教学计划一致' },
|
||||
{ label: '已改动教学计划', value: '已改动教学计划' },
|
||||
{ label: '手动拟制', value: '手动拟制' }
|
||||
],
|
||||
changeTypeList: [
|
||||
{ label: '已改动教员', value: '已改动教员' },
|
||||
{ label: '已改动时间', value: '已改动时间' },
|
||||
{ label: '已改动课目', value: '已改动课目' },
|
||||
{ label: '已改动内容', value: '已改动内容' },
|
||||
{ label: '已改动教学方法', value: '已改动教学方法' },
|
||||
{ label: '已改动场地', value: '已改动场地' },
|
||||
{ label: '已改动期班', value: '已改动期班' }
|
||||
],
|
||||
|
||||
// ==================== 数据表格 ====================
|
||||
loading: false,
|
||||
tableData: [],
|
||||
selectedRows: [],
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
total: 0,
|
||||
|
||||
// ==================== 导入 ====================
|
||||
importing: false,
|
||||
|
||||
// ==================== 详情 ====================
|
||||
detailVisible: false,
|
||||
detailLoading: false,
|
||||
detailData: {}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.fetchList()
|
||||
},
|
||||
methods: {
|
||||
handleSelectionChange(rows) {
|
||||
this.selectedRows = rows
|
||||
},
|
||||
|
||||
/** 根据已填写的查询值构建实际查询参数 */
|
||||
buildSearchParams() {
|
||||
const form = this.searchForm
|
||||
const params = { pageNum: this.pageNum, pageSize: this.pageSize }
|
||||
if (form.ksrq) params.ksrq = form.ksrq
|
||||
if (form.jsrq) params.jsrq = form.jsrq
|
||||
if (form.kcmc) params.kcmc = form.kcmc
|
||||
if (form.jys) params.jys = form.jys
|
||||
if (form.jxff) params.jxff = form.jxff
|
||||
if (form.rzzt) params.rzzt = form.rzzt
|
||||
if (form.skjy) params.skjy = form.skjy
|
||||
if (form.pxqb) params.pxqb = form.pxqb
|
||||
if (form.rwlb) params.rwlb = form.rwlb
|
||||
if (form.changeType) params.gdqk = form.changeType
|
||||
if (form.changeTypes.length > 0) {
|
||||
params.gdxxqk = form.changeTypes.join(',')
|
||||
}
|
||||
return params
|
||||
},
|
||||
|
||||
/** 从响应中提取列表数据和总数(兼容 records / list / rows 等常见字段名) */
|
||||
extractListData(res) {
|
||||
const data = res.data || res
|
||||
const list = data.records || data.list || data.rows || []
|
||||
const totalCount = data.total ?? data.totalCount ?? data.totalRows ?? 0
|
||||
return { list, total: totalCount }
|
||||
},
|
||||
|
||||
fetchList() {
|
||||
this.loading = true
|
||||
return getTeachingLogList(this.buildSearchParams())
|
||||
.then((res) => {
|
||||
if (res.code === 200 || res.code === 0) {
|
||||
const { list, total } = this.extractListData(res)
|
||||
this.tableData = list
|
||||
this.total = total
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
// 错误已由拦截器统一处理
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
|
||||
handleSearch() {
|
||||
this.pageNum = 1
|
||||
this.fetchList()
|
||||
},
|
||||
|
||||
handlePageChange(page) {
|
||||
this.pageNum = page
|
||||
this.fetchList()
|
||||
},
|
||||
|
||||
handleSizeChange(size) {
|
||||
this.pageSize = size
|
||||
this.pageNum = 1
|
||||
this.fetchList()
|
||||
},
|
||||
|
||||
handleRefresh() {
|
||||
this.fetchList()
|
||||
},
|
||||
|
||||
getSelectedBhs() {
|
||||
const ids = this.selectedRows.map((row) => row.bh).filter(Boolean)
|
||||
if (ids.length === 0) {
|
||||
this.$message.warning('请先选择要操作的记录')
|
||||
return []
|
||||
}
|
||||
return ids
|
||||
},
|
||||
|
||||
handleDeleteSelected() {
|
||||
// 后端暂未提供删除接口,仅作提示
|
||||
this.$message.info('后端暂未提供该接口')
|
||||
},
|
||||
|
||||
handleBatchReport() {
|
||||
const ids = this.getSelectedBhs()
|
||||
if (ids.length === 0) return
|
||||
this.$confirm(`确定要上报选中的 ${ids.length} 条记录吗?`, '上报确认', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
.then(() => {
|
||||
// 提交教学日志审核,逐条上报至「已上报」状态
|
||||
let settled = Promise.resolve()
|
||||
ids.forEach((bh) => {
|
||||
settled = settled.then(() => submitTeachingLog(bh, '已上报'))
|
||||
})
|
||||
return settled
|
||||
})
|
||||
.then(() => {
|
||||
this.$message.success(`已上报 ${ids.length} 条记录`)
|
||||
this.fetchList()
|
||||
})
|
||||
.catch(() => {})
|
||||
},
|
||||
|
||||
handleRecheck() {
|
||||
// 后端暂未提供重新检查接口,仅作提示
|
||||
this.$message.info('后端暂未提供该接口')
|
||||
},
|
||||
|
||||
downloadBlob(blob, fileName) {
|
||||
const url = window.URL.createObjectURL(blob)
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.download = fileName
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
window.URL.revokeObjectURL(url)
|
||||
},
|
||||
|
||||
handleExport() {
|
||||
const params = this.buildSearchParams()
|
||||
delete params.pageNum
|
||||
delete params.pageSize
|
||||
exportTeachingLog(params)
|
||||
.then((blob) => {
|
||||
this.downloadBlob(blob, `教学日志_${new Date().toISOString().slice(0, 10)}.xls`)
|
||||
this.$message.success('导出成功')
|
||||
})
|
||||
.catch(() => {})
|
||||
},
|
||||
|
||||
handleImportExcel() {
|
||||
this.$refs.importFileInput && this.$refs.importFileInput.click()
|
||||
},
|
||||
|
||||
handleImportFileChange(e) {
|
||||
const files = e.target.files
|
||||
if (!files || files.length === 0) return
|
||||
const file = files[0]
|
||||
this.importing = true
|
||||
importTeachingLogExcel(file)
|
||||
.then((res) => {
|
||||
if (res.code === 200 || res.code === 0) {
|
||||
this.$message.success(res.message || '导入成功')
|
||||
this.fetchList()
|
||||
} else {
|
||||
this.$message.error(res.message || '导入失败')
|
||||
}
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
this.importing = false
|
||||
if (this.$refs.importFileInput) {
|
||||
this.$refs.importFileInput.value = ''
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
handleDownloadTemplate() {
|
||||
downloadTeachingLogTemplate()
|
||||
.then((blob) => {
|
||||
this.downloadBlob(blob, '导入教学日志模板.xls')
|
||||
this.$message.success('模板下载成功')
|
||||
})
|
||||
.catch(() => {})
|
||||
},
|
||||
|
||||
handleDetail(row) {
|
||||
this.detailData = { ...row }
|
||||
this.detailVisible = true
|
||||
const bh = row.bh
|
||||
if (!bh) return
|
||||
this.detailLoading = true
|
||||
getTeachingLogByBh(String(bh))
|
||||
.then((res) => {
|
||||
if ((res.code === 200 || res.code === 0) && res.data) {
|
||||
this.detailData = res.data
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
// 详情接口不可用时保留行数据兜底展示
|
||||
})
|
||||
.finally(() => {
|
||||
this.detailLoading = false
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.page-container {
|
||||
padding: 4px 8px;
|
||||
}
|
||||
|
||||
.search-card {
|
||||
margin-bottom: 16px;
|
||||
|
||||
.search-form {
|
||||
.w-full {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
::v-deep .el-row {
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .el-col {
|
||||
border-right: 1px solid #ebeef5;
|
||||
|
||||
&:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .el-form-item__label {
|
||||
white-space: normal;
|
||||
line-height: 1.4;
|
||||
text-align: left;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.el-form-item {
|
||||
margin-bottom: 0;
|
||||
padding: 10px 12px;
|
||||
}
|
||||
|
||||
.change-options {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
|
||||
::v-deep .el-radio-group,
|
||||
::v-deep .el-checkbox-group {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.search-tip {
|
||||
padding: 6px 12px 0;
|
||||
font-size: 12px;
|
||||
color: #f56c6c;
|
||||
}
|
||||
|
||||
.search-actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 12px;
|
||||
border-top: 1px solid #ebeef5;
|
||||
|
||||
.left-group {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.table-card {
|
||||
.el-table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.pagination-wrapper {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 16px 0 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,511 @@
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<!-- 查询条件 -->
|
||||
<el-card shadow="never" class="search-card">
|
||||
<el-form :model="searchForm" label-width="110px" class="search-form">
|
||||
<el-row :gutter="0">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="开始日期">
|
||||
<el-date-picker v-model="searchForm.startDate" type="date" value-format="yyyy-MM-dd" placeholder="选择开始日期"
|
||||
class="w-full" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="结束日期">
|
||||
<el-date-picker v-model="searchForm.endDate" type="date" value-format="yyyy-MM-dd" placeholder="选择结束日期"
|
||||
class="w-full" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="0">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="课程名称">
|
||||
<el-input v-model="searchForm.courseName" placeholder="请输入课程名称" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="教学方法">
|
||||
<el-select v-model="searchForm.teachMethod" placeholder="请选择教学方法" clearable class="w-full">
|
||||
<el-option v-for="item in teachMethodOptions" :key="item" :label="item" :value="item" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="0">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="改动情况">
|
||||
<el-select v-model="searchForm.change" placeholder="请选择改动情况" clearable class="w-full">
|
||||
<el-option v-for="item in changeOptions" :key="item" :label="item" :value="item" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="任务类别">
|
||||
<el-select v-model="searchForm.taskCategory" placeholder="请选择任务类别" clearable class="w-full">
|
||||
<el-option v-for="item in taskCategoryOptions" :key="item" :label="item" :value="item" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="0">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="教研室">
|
||||
<el-select v-model="searchForm.dept" placeholder="请选择教研室" clearable class="w-full">
|
||||
<el-option v-for="item in deptOptions" :key="item" :label="item" :value="item" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="授课教员">
|
||||
<el-select v-model="searchForm.teacher" placeholder="请选择授课教员" clearable class="w-full">
|
||||
<el-option v-for="item in teacherOptions" :key="item" :label="item" :value="item" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="0">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="培训班">
|
||||
<el-input v-model="searchForm.className" placeholder="请输入培训班" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
|
||||
<div class="search-actions">
|
||||
<div class="left-group">
|
||||
<el-button type="danger" plain @click="handleDelete">删除所选</el-button>
|
||||
<el-button type="primary" @click="handleAudit">审核通过所选</el-button>
|
||||
<el-input v-model="remark" placeholder="退回原因/意见" clearable class="action-input" />
|
||||
<el-button type="warning" plain @click="handleWithdraw">撤回重填所选</el-button>
|
||||
<el-button @click="handleRecheck">重新检查所选变更情况</el-button>
|
||||
<el-button icon="el-icon-download" @click="handleExport">导出到 Word</el-button>
|
||||
</div>
|
||||
<div class="right-group">
|
||||
<el-button @click="handleReset">重置</el-button>
|
||||
<el-button type="primary" @click="handleSearch">查询</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<el-card shadow="never" class="table-card">
|
||||
<el-table v-loading="loading" :data="tableData" stripe border highlight-current-row
|
||||
@selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="50" align="center" />
|
||||
<el-table-column type="index" label="序号" width="60" align="center" />
|
||||
<el-table-column prop="rwlb" label="任务类别" width="120" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="jyxm" label="主讲教员" width="100" align="center" />
|
||||
<el-table-column prop="rq" label="日期" width="150" align="center">
|
||||
<template slot-scope="scope">{{ scope.row.rq ? formatDate(scope.row.rq) : '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="节次" width="80" align="center">
|
||||
<template slot-scope="scope">
|
||||
{{ scope.row.qsjc && scope.row.jsjc ? `${scope.row.qsjc}-${scope.row.jsjc}节` : (scope.row.qsjc || '-') }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="kcmc" label="课程名称" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column prop="pxqbxx" label="培训期班信息" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column label="人数" width="70" align="center">
|
||||
<template slot-scope="scope">{{ scope.row.sdrs || scope.row.ydrs || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="分组指导教员" width="120" align="center">
|
||||
<template slot-scope="scope">{{ scope.row.fzyzdjy || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="jsfs" label="教学方法" width="100" align="center" />
|
||||
<el-table-column prop="zt" label="日志状态" width="100" align="center" />
|
||||
<el-table-column prop="gdqk" label="变动汇总" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column prop="jxrzcjfs" label="创建方式" width="100" align="center" />
|
||||
<el-table-column label="操作" width="80" align="center" fixed="right" class-name="table-action-column">
|
||||
<template slot-scope="scope">
|
||||
<el-button type="text" @click="handleDetail(scope.row)">详细</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="pagination-wrapper">
|
||||
<el-pagination :current-page="pageNum" :page-size="pageSize" :total="total" :page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper" background @current-change="handlePageChange"
|
||||
@size-change="handleSizeChange" />
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 日志详情对话框 -->
|
||||
<el-dialog :visible.sync="detailVisible" title="教学日志详情" width="700px" destroy-on-close>
|
||||
<div v-loading="detailLoading">
|
||||
<el-descriptions v-if="!detailLoading" :column="2" border>
|
||||
<el-descriptions-item label="编号">{{ detailData.bh || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="统一编号">{{ detailData.tybh || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="教员编号">{{ detailData.jybh || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="教员姓名">{{ detailData.jyxm || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="课程名称">{{ detailData.kcmc || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="任务类别">{{ detailData.rwlb || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="日期">{{ detailData.rq || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="节次">
|
||||
{{ detailData.qsjc && detailData.jsjc ? `${detailData.qsjc}-${detailData.jsjc}节` : '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="教学方法">{{ detailData.jsfs || detailData.jxff || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="创建方式">{{ detailData.jxrzcjfs || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="应到人数">{{ detailData.ydrs || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="实到人数">{{ detailData.sdrs || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="培训期班信息" :span="2">{{ detailData.pxqbxx || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="授课地点" :span="2">{{ detailData.skdd || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="状态">{{ detailData.zt || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="教研室">{{ detailData.jysmc || detailData.jys || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="变动汇总" :span="2">{{ detailData.gdqk || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="变动详细情况" :span="2">{{ detailData.gdxxqk || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="退回意见" :span="2">{{ detailData.thyj || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">{{ detailData.cjsj || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="修改时间">{{ detailData.xgsj || '-' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="detailVisible = false">关 闭</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { formatDate } from '@/utils/index'
|
||||
import {
|
||||
getReportedTeachingLogList,
|
||||
approveTeachingLog,
|
||||
rejectTeachingLog,
|
||||
exportTeachingLog,
|
||||
getTeachingLogByBh
|
||||
} from '@/api/log'
|
||||
|
||||
export default {
|
||||
name: 'ReportedView',
|
||||
data() {
|
||||
return {
|
||||
// ==================== 查询表单 ====================
|
||||
searchForm: {
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
courseName: '',
|
||||
teachMethod: '',
|
||||
change: '',
|
||||
taskCategory: '',
|
||||
dept: '',
|
||||
teacher: '',
|
||||
className: ''
|
||||
},
|
||||
|
||||
teachMethodOptions: ['理论讲授', '实践教学', '研讨教学', '案例教学', '实操演练'],
|
||||
changeOptions: [
|
||||
'与教学计划一致',
|
||||
'已改动教学计划',
|
||||
'手动拟制',
|
||||
'教员变动',
|
||||
'时间变动',
|
||||
'课程变动',
|
||||
'授课方式变动',
|
||||
'任务类别变动',
|
||||
'期班变动',
|
||||
'地点变动',
|
||||
'课题变动'
|
||||
],
|
||||
taskCategoryOptions: ['全军院校训练规划内任务', '其他任务(含研究生导师指导)'],
|
||||
deptOptions: ['教务处', '指挥系', '政治工作教研室', '军事基础教研室', '装备保障教研室'],
|
||||
teacherOptions: ['教务处', '张三', '李四', '王五', '赵六', '孙七'],
|
||||
|
||||
// ==================== 数据表格 ====================
|
||||
loading: false,
|
||||
tableData: [],
|
||||
selectedRows: [],
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
total: 0,
|
||||
|
||||
// ==================== 退回意见 ====================
|
||||
remark: '',
|
||||
|
||||
// ==================== 详情 ====================
|
||||
detailVisible: false,
|
||||
detailLoading: false,
|
||||
detailData: {}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.fetchList()
|
||||
},
|
||||
methods: {
|
||||
handleSelectionChange(rows) {
|
||||
this.selectedRows = rows
|
||||
},
|
||||
|
||||
/** 根据已填写的查询值构建实际查询参数 */
|
||||
buildSearchParams() {
|
||||
const form = this.searchForm
|
||||
const params = { pageNum: this.pageNum, pageSize: this.pageSize }
|
||||
if (form.startDate) params.ksrq = form.startDate
|
||||
if (form.endDate) params.jsrq = form.endDate
|
||||
if (form.courseName) params.kcmc = form.courseName
|
||||
if (form.teachMethod) params.jxff = form.teachMethod
|
||||
if (form.change) params.gdqk = form.change
|
||||
if (form.taskCategory) params.rwlb = form.taskCategory
|
||||
if (form.dept) params.jys = form.dept
|
||||
if (form.teacher) params.skjy = form.teacher
|
||||
if (form.className) params.pxqb = form.className
|
||||
return params
|
||||
},
|
||||
|
||||
extractListData(res) {
|
||||
const data = res.data || res
|
||||
const list = data.records || data.list || data.rows || []
|
||||
const totalCount = data.total ?? data.totalCount ?? data.totalRows ?? 0
|
||||
return { list, total: totalCount }
|
||||
},
|
||||
|
||||
fetchList() {
|
||||
this.loading = true
|
||||
return getReportedTeachingLogList(this.buildSearchParams())
|
||||
.then((res) => {
|
||||
if (res.code === 200 || res.code === 0) {
|
||||
const { list, total } = this.extractListData(res)
|
||||
this.tableData = list
|
||||
this.total = total
|
||||
}
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
|
||||
handleSearch() {
|
||||
this.pageNum = 1
|
||||
this.fetchList()
|
||||
},
|
||||
|
||||
handleReset() {
|
||||
this.searchForm = {
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
courseName: '',
|
||||
teachMethod: '',
|
||||
change: '',
|
||||
taskCategory: '',
|
||||
dept: '',
|
||||
teacher: '',
|
||||
className: ''
|
||||
}
|
||||
this.remark = ''
|
||||
this.handleSearch()
|
||||
},
|
||||
|
||||
handlePageChange(page) {
|
||||
this.pageNum = page
|
||||
this.fetchList()
|
||||
},
|
||||
|
||||
handleSizeChange(size) {
|
||||
this.pageSize = size
|
||||
this.pageNum = 1
|
||||
this.fetchList()
|
||||
},
|
||||
|
||||
getSelectedBhs() {
|
||||
const ids = this.selectedRows.map((row) => row.bh).filter(Boolean)
|
||||
if (ids.length === 0) {
|
||||
this.$message.warning('请先选择记录')
|
||||
return []
|
||||
}
|
||||
return ids
|
||||
},
|
||||
|
||||
handleDelete() {
|
||||
// 后端暂未提供删除接口,仅作提示
|
||||
this.$message.info('后端暂未提供该接口')
|
||||
},
|
||||
|
||||
handleAudit() {
|
||||
const ids = this.getSelectedBhs()
|
||||
if (ids.length === 0) return
|
||||
this.$confirm(`确定要审核通过选中的 ${ids.length} 条记录吗?`, '审核确认', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
.then(() => {
|
||||
// 审核通过教学日志,逐条审核
|
||||
let settled = Promise.resolve()
|
||||
ids.forEach((bh) => {
|
||||
settled = settled.then(() => approveTeachingLog(bh, ''))
|
||||
})
|
||||
return settled
|
||||
})
|
||||
.then(() => {
|
||||
this.$message.success(`已审核通过 ${ids.length} 条记录`)
|
||||
this.fetchList()
|
||||
})
|
||||
.catch(() => {})
|
||||
},
|
||||
|
||||
handleWithdraw() {
|
||||
const ids = this.getSelectedBhs()
|
||||
if (ids.length === 0) return
|
||||
if (!this.remark.trim()) {
|
||||
this.$message.warning('请填写退回原因/意见')
|
||||
return
|
||||
}
|
||||
const opinion = this.remark.trim()
|
||||
let settled = Promise.resolve()
|
||||
ids.forEach((bh) => {
|
||||
settled = settled.then(() => rejectTeachingLog(bh, opinion))
|
||||
})
|
||||
this.$confirm(`确定要撤回重填选中的 ${ids.length} 条记录吗?`, '撤回确认', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
.then(() => settled)
|
||||
.then(() => {
|
||||
this.$message.success(`已撤回 ${ids.length} 条记录`)
|
||||
this.fetchList()
|
||||
})
|
||||
.catch(() => {})
|
||||
},
|
||||
|
||||
handleRecheck() {
|
||||
// 后端暂未提供重新检查接口,仅作提示
|
||||
this.$message.info('后端暂未提供该接口')
|
||||
},
|
||||
|
||||
downloadBlob(blob, fileName) {
|
||||
const url = window.URL.createObjectURL(blob)
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.download = fileName
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
window.URL.revokeObjectURL(url)
|
||||
},
|
||||
|
||||
handleExport() {
|
||||
const params = this.buildSearchParams()
|
||||
delete params.pageNum
|
||||
delete params.pageSize
|
||||
exportTeachingLog(params)
|
||||
.then((blob) => {
|
||||
this.downloadBlob(blob, `已上报教学日志_${new Date().toISOString().slice(0, 10)}.xls`)
|
||||
this.$message.success('导出成功')
|
||||
})
|
||||
.catch(() => {})
|
||||
},
|
||||
|
||||
handleDetail(row) {
|
||||
this.detailData = { ...row }
|
||||
this.detailVisible = true
|
||||
const bh = row.bh
|
||||
if (!bh) return
|
||||
this.detailLoading = true
|
||||
getTeachingLogByBh(String(bh))
|
||||
.then((res) => {
|
||||
if ((res.code === 200 || res.code === 0) && res.data) {
|
||||
this.detailData = res.data
|
||||
}
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
this.detailLoading = false
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.page-container {
|
||||
padding: 4px 8px;
|
||||
}
|
||||
|
||||
.search-card {
|
||||
margin-bottom: 16px;
|
||||
|
||||
.search-form {
|
||||
.w-full {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
::v-deep .el-row {
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .el-col {
|
||||
border-right: 1px solid #ebeef5;
|
||||
|
||||
&:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .el-form-item__label {
|
||||
white-space: normal;
|
||||
line-height: 1.4;
|
||||
text-align: left;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.el-form-item {
|
||||
margin-bottom: 0;
|
||||
padding: 10px 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.search-tip {
|
||||
padding: 6px 12px 0;
|
||||
font-size: 12px;
|
||||
color: #f56c6c;
|
||||
}
|
||||
|
||||
.search-actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 12px;
|
||||
border-top: 1px solid #ebeef5;
|
||||
|
||||
.left-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.action-input {
|
||||
width: 240px;
|
||||
}
|
||||
}
|
||||
|
||||
.right-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.table-card {
|
||||
.el-table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.pagination-wrapper {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 16px 0 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,51 @@
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<el-card shadow="never" class="tab-card">
|
||||
<el-tabs v-model="activeTab" class="organ-tabs">
|
||||
<el-tab-pane label="教学日志管理查询" name="query">
|
||||
<log-view v-if="activeTab === 'query'" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="已上报机关教学日志" name="reported">
|
||||
<reported-view v-if="activeTab === 'reported'" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="机关已审核教学日志" name="audited">
|
||||
<audited-view v-if="activeTab === 'audited'" />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import LogView from './components/logView.vue'
|
||||
import ReportedView from './components/reportedView.vue'
|
||||
import AuditedView from './components/auditedView.vue'
|
||||
|
||||
export default {
|
||||
name: 'OrganLog',
|
||||
components: { LogView, ReportedView, AuditedView },
|
||||
data() {
|
||||
return {
|
||||
activeTab: 'query'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.page-container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.tab-card {
|
||||
.organ-tabs {
|
||||
::v-deep .el-tabs__header {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
::v-deep .el-tabs__content {
|
||||
padding-top: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,106 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-dialog
|
||||
v-bind="$attrs"
|
||||
width="500px"
|
||||
:close-on-click-modal="false"
|
||||
:modal-append-to-body="false"
|
||||
v-on="$listeners"
|
||||
@open="onOpen"
|
||||
@close="onClose"
|
||||
>
|
||||
<el-row :gutter="15">
|
||||
<el-form
|
||||
ref="elForm"
|
||||
:model="formData"
|
||||
:rules="rules"
|
||||
size="medium"
|
||||
label-width="100px"
|
||||
>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="生成类型" prop="type">
|
||||
<el-radio-group v-model="formData.type">
|
||||
<el-radio-button
|
||||
v-for="(item, index) in typeOptions"
|
||||
:key="index"
|
||||
:label="item.value"
|
||||
:disabled="item.disabled"
|
||||
>
|
||||
{{ item.label }}
|
||||
</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="showFileName" label="文件名" prop="fileName">
|
||||
<el-input v-model="formData.fileName" placeholder="请输入文件名" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-form>
|
||||
</el-row>
|
||||
|
||||
<div slot="footer">
|
||||
<el-button @click="close">
|
||||
取消
|
||||
</el-button>
|
||||
<el-button type="primary" @click="handleConfirm">
|
||||
确定
|
||||
</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
inheritAttrs: false,
|
||||
props: ['showFileName'],
|
||||
data() {
|
||||
return {
|
||||
formData: {
|
||||
fileName: undefined,
|
||||
type: 'file'
|
||||
},
|
||||
rules: {
|
||||
fileName: [{
|
||||
required: true,
|
||||
message: '请输入文件名',
|
||||
trigger: 'blur'
|
||||
}],
|
||||
type: [{
|
||||
required: true,
|
||||
message: '生成类型不能为空',
|
||||
trigger: 'change'
|
||||
}]
|
||||
},
|
||||
typeOptions: [{
|
||||
label: '页面',
|
||||
value: 'file'
|
||||
}, {
|
||||
label: '弹窗',
|
||||
value: 'dialog'
|
||||
}]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
},
|
||||
watch: {},
|
||||
mounted() {},
|
||||
methods: {
|
||||
onOpen() {
|
||||
if (this.showFileName) {
|
||||
this.formData.fileName = `${+new Date()}.vue`
|
||||
}
|
||||
},
|
||||
onClose() {
|
||||
},
|
||||
close(e) {
|
||||
this.$emit('update:visible', false)
|
||||
},
|
||||
handleConfirm() {
|
||||
this.$refs.elForm.validate(valid => {
|
||||
if (!valid) return
|
||||
this.$emit('confirm', { ...this.formData })
|
||||
this.close()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,100 @@
|
||||
<script>
|
||||
import draggable from 'vuedraggable'
|
||||
import render from '@/utils/generator/render'
|
||||
|
||||
const components = {
|
||||
itemBtns(h, element, index, parent) {
|
||||
const { copyItem, deleteItem } = this.$listeners
|
||||
return [
|
||||
<span class="drawing-item-copy" title="复制" onClick={event => {
|
||||
copyItem(element, parent); event.stopPropagation()
|
||||
}}>
|
||||
<i class="el-icon-copy-document" />
|
||||
</span>,
|
||||
<span class="drawing-item-delete" title="删除" onClick={event => {
|
||||
deleteItem(index, parent); event.stopPropagation()
|
||||
}}>
|
||||
<i class="el-icon-delete" />
|
||||
</span>
|
||||
]
|
||||
}
|
||||
}
|
||||
const layouts = {
|
||||
colFormItem(h, element, index, parent) {
|
||||
const { activeItem } = this.$listeners
|
||||
let className = this.activeId === element.formId ? 'drawing-item active-from-item' : 'drawing-item'
|
||||
if (this.formConf.unFocusedComponentBorder) className += ' unfocus-bordered'
|
||||
return (
|
||||
<el-col span={element.span} class={className}
|
||||
nativeOnClick={event => { activeItem(element); event.stopPropagation() }}>
|
||||
<el-form-item label-width={element.labelWidth ? `${element.labelWidth}px` : null}
|
||||
label={element.label} required={element.required}>
|
||||
<render key={element.renderKey} conf={element} onInput={ event => {
|
||||
this.$set(element, 'defaultValue', event)
|
||||
}} />
|
||||
</el-form-item>
|
||||
{components.itemBtns.apply(this, arguments)}
|
||||
</el-col>
|
||||
)
|
||||
},
|
||||
rowFormItem(h, element, index, parent) {
|
||||
const { activeItem } = this.$listeners
|
||||
const className = this.activeId === element.formId ? 'drawing-row-item active-from-item' : 'drawing-row-item'
|
||||
let child = renderChildren.apply(this, arguments)
|
||||
if (element.type === 'flex') {
|
||||
child = <el-row type={element.type} justify={element.justify} align={element.align}>
|
||||
{child}
|
||||
</el-row>
|
||||
}
|
||||
return (
|
||||
<el-col span={element.span}>
|
||||
<el-row gutter={element.gutter} class={className}
|
||||
nativeOnClick={event => { activeItem(element); event.stopPropagation() }}>
|
||||
<span class="component-name">{element.componentName}</span>
|
||||
<draggable list={element.children} animation={340} group="componentsGroup" class="drag-wrapper">
|
||||
{child}
|
||||
</draggable>
|
||||
{components.itemBtns.apply(this, arguments)}
|
||||
</el-row>
|
||||
</el-col>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function renderChildren(h, element, index, parent) {
|
||||
if (!Array.isArray(element.children)) return null
|
||||
return element.children.map((el, i) => {
|
||||
const layout = layouts[el.layout]
|
||||
if (layout) {
|
||||
return layout.call(this, h, el, i, element.children)
|
||||
}
|
||||
return layoutIsNotFound()
|
||||
})
|
||||
}
|
||||
|
||||
function layoutIsNotFound() {
|
||||
throw new Error(`没有与${this.element.layout}匹配的layout`)
|
||||
}
|
||||
|
||||
export default {
|
||||
components: {
|
||||
render,
|
||||
draggable
|
||||
},
|
||||
props: [
|
||||
'element',
|
||||
'index',
|
||||
'drawingList',
|
||||
'activeId',
|
||||
'formConf'
|
||||
],
|
||||
render(h) {
|
||||
const layout = layouts[this.element.layout]
|
||||
|
||||
if (layout) {
|
||||
return layout.call(this, h, this.element, this.index, this.drawingList)
|
||||
}
|
||||
return layoutIsNotFound()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,123 @@
|
||||
<template>
|
||||
<div class="icon-dialog">
|
||||
<el-dialog
|
||||
v-bind="$attrs"
|
||||
width="980px"
|
||||
:modal-append-to-body="false"
|
||||
v-on="$listeners"
|
||||
@open="onOpen"
|
||||
@close="onClose"
|
||||
>
|
||||
<div slot="title">
|
||||
选择图标
|
||||
<el-input
|
||||
v-model="key"
|
||||
size="mini"
|
||||
:style="{width: '260px'}"
|
||||
placeholder="请输入图标名称"
|
||||
prefix-icon="el-icon-search"
|
||||
clearable
|
||||
/>
|
||||
</div>
|
||||
<ul class="icon-ul">
|
||||
<li
|
||||
v-for="icon in iconList"
|
||||
:key="icon"
|
||||
:class="active===icon?'active-item':''"
|
||||
@click="onSelect(icon)"
|
||||
>
|
||||
<i :class="icon" />
|
||||
<div>{{ icon }}</div>
|
||||
</li>
|
||||
</ul>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import iconList from '@/utils/generator/icon.json'
|
||||
|
||||
const originList = iconList.map(name => `el-icon-${name}`)
|
||||
|
||||
export default {
|
||||
inheritAttrs: false,
|
||||
props: ['current'],
|
||||
data() {
|
||||
return {
|
||||
iconList: originList,
|
||||
active: null,
|
||||
key: ''
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
key(val) {
|
||||
if (val) {
|
||||
this.iconList = originList.filter(name => name.indexOf(val) > -1)
|
||||
} else {
|
||||
this.iconList = originList
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onOpen() {
|
||||
this.active = this.current
|
||||
this.key = ''
|
||||
},
|
||||
onClose() {},
|
||||
onSelect(icon) {
|
||||
this.active = icon
|
||||
this.$emit('select', icon)
|
||||
this.$emit('update:visible', false)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.icon-ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-size: 0;
|
||||
li {
|
||||
list-style-type: none;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
display: inline-block;
|
||||
width: 16.66%;
|
||||
box-sizing: border-box;
|
||||
height: 108px;
|
||||
padding: 15px 6px 6px 6px;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
&:hover {
|
||||
background: #f2f2f2;
|
||||
}
|
||||
&.active-item{
|
||||
background: #e1f3fb;
|
||||
color: #7a6df0
|
||||
}
|
||||
> i {
|
||||
font-size: 30px;
|
||||
line-height: 50px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.icon-dialog {
|
||||
::v-deep .el-dialog {
|
||||
border-radius: 8px;
|
||||
margin-bottom: 0;
|
||||
margin-top: 4vh !important;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-height: 92vh;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
.el-dialog__header {
|
||||
padding-top: 14px;
|
||||
}
|
||||
.el-dialog__body {
|
||||
margin: 0 20px 20px 20px;
|
||||
padding: 0;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,944 @@
|
||||
<template>
|
||||
<div class="right-board">
|
||||
<el-tabs v-model="currentTab" class="center-tabs">
|
||||
<el-tab-pane label="组件属性" name="field" />
|
||||
<el-tab-pane label="表单属性" name="form" />
|
||||
</el-tabs>
|
||||
<div class="field-box">
|
||||
<a class="document-link" target="_blank" :href="documentLink" title="查看组件文档">
|
||||
<i class="el-icon-link" />
|
||||
</a>
|
||||
<el-scrollbar class="right-scrollbar">
|
||||
<!-- 组件属性 -->
|
||||
<el-form v-show="currentTab==='field' && showField" size="small" label-width="90px">
|
||||
<el-form-item v-if="activeData.changeTag" label="组件类型">
|
||||
<el-select
|
||||
v-model="activeData.tagIcon"
|
||||
placeholder="请选择组件类型"
|
||||
:style="{width: '100%'}"
|
||||
@change="tagChange"
|
||||
>
|
||||
<el-option-group v-for="group in tagList" :key="group.label" :label="group.label">
|
||||
<el-option
|
||||
v-for="item in group.options"
|
||||
:key="item.label"
|
||||
:label="item.label"
|
||||
:value="item.tagIcon"
|
||||
>
|
||||
<svg-icon class="node-icon" :icon-class="item.tagIcon" />
|
||||
<span> {{ item.label }}</span>
|
||||
</el-option>
|
||||
</el-option-group>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="activeData.vModel!==undefined" label="字段名">
|
||||
<el-input v-model="activeData.vModel" placeholder="请输入字段名(v-model)" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="activeData.componentName!==undefined" label="组件名">
|
||||
{{ activeData.componentName }}
|
||||
</el-form-item>
|
||||
<el-form-item v-if="activeData.label!==undefined" label="标题">
|
||||
<el-input v-model="activeData.label" placeholder="请输入标题" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="activeData.placeholder!==undefined" label="占位提示">
|
||||
<el-input v-model="activeData.placeholder" placeholder="请输入占位提示" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="activeData['start-placeholder']!==undefined" label="开始占位">
|
||||
<el-input v-model="activeData['start-placeholder']" placeholder="请输入占位提示" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="activeData['end-placeholder']!==undefined" label="结束占位">
|
||||
<el-input v-model="activeData['end-placeholder']" placeholder="请输入占位提示" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="activeData.span!==undefined" label="表单栅格">
|
||||
<el-slider v-model="activeData.span" :max="24" :min="1" :marks="{12:''}" @change="spanChange" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="activeData.layout==='rowFormItem'" label="栅格间隔">
|
||||
<el-input-number v-model="activeData.gutter" :min="0" placeholder="栅格间隔" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="activeData.layout==='rowFormItem'" label="布局模式">
|
||||
<el-radio-group v-model="activeData.type">
|
||||
<el-radio-button label="default" />
|
||||
<el-radio-button label="flex" />
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="activeData.justify!==undefined&&activeData.type==='flex'" label="水平排列">
|
||||
<el-select v-model="activeData.justify" placeholder="请选择水平排列" :style="{width: '100%'}">
|
||||
<el-option
|
||||
v-for="(item, index) in justifyOptions"
|
||||
:key="index"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="activeData.align!==undefined&&activeData.type==='flex'" label="垂直排列">
|
||||
<el-radio-group v-model="activeData.align">
|
||||
<el-radio-button label="top" />
|
||||
<el-radio-button label="middle" />
|
||||
<el-radio-button label="bottom" />
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="activeData.labelWidth!==undefined" label="标签宽度">
|
||||
<el-input v-model.number="activeData.labelWidth" type="number" placeholder="请输入标签宽度" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="activeData.style&&activeData.style.width!==undefined" label="组件宽度">
|
||||
<el-input v-model="activeData.style.width" placeholder="请输入组件宽度" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="activeData.vModel!==undefined" label="默认值">
|
||||
<el-input
|
||||
:value="setDefaultValue(activeData.defaultValue)"
|
||||
placeholder="请输入默认值"
|
||||
@input="onDefaultValueInput"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="activeData.tag==='el-checkbox-group'" label="至少应选">
|
||||
<el-input-number
|
||||
:value="activeData.min"
|
||||
:min="0"
|
||||
placeholder="至少应选"
|
||||
@input="$set(activeData, 'min', $event?$event:undefined)"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="activeData.tag==='el-checkbox-group'" label="最多可选">
|
||||
<el-input-number
|
||||
:value="activeData.max"
|
||||
:min="0"
|
||||
placeholder="最多可选"
|
||||
@input="$set(activeData, 'max', $event?$event:undefined)"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="activeData.prepend!==undefined" label="前缀">
|
||||
<el-input v-model="activeData.prepend" placeholder="请输入前缀" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="activeData.append!==undefined" label="后缀">
|
||||
<el-input v-model="activeData.append" placeholder="请输入后缀" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="activeData['prefix-icon']!==undefined" label="前图标">
|
||||
<el-input v-model="activeData['prefix-icon']" placeholder="请输入前图标名称">
|
||||
<el-button slot="append" icon="el-icon-thumb" @click="openIconsDialog('prefix-icon')">
|
||||
选择
|
||||
</el-button>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="activeData['suffix-icon'] !== undefined" label="后图标">
|
||||
<el-input v-model="activeData['suffix-icon']" placeholder="请输入后图标名称">
|
||||
<el-button slot="append" icon="el-icon-thumb" @click="openIconsDialog('suffix-icon')">
|
||||
选择
|
||||
</el-button>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="activeData.tag === 'el-cascader'" label="选项分隔符">
|
||||
<el-input v-model="activeData.separator" placeholder="请输入选项分隔符" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="activeData.autosize !== undefined" label="最小行数">
|
||||
<el-input-number v-model="activeData.autosize.minRows" :min="1" placeholder="最小行数" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="activeData.autosize !== undefined" label="最大行数">
|
||||
<el-input-number v-model="activeData.autosize.maxRows" :min="1" placeholder="最大行数" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="activeData.min !== undefined" label="最小值">
|
||||
<el-input-number v-model="activeData.min" placeholder="最小值" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="activeData.max !== undefined" label="最大值">
|
||||
<el-input-number v-model="activeData.max" placeholder="最大值" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="activeData.step !== undefined" label="步长">
|
||||
<el-input-number v-model="activeData.step" placeholder="步数" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="activeData.tag === 'el-input-number'" label="精度">
|
||||
<el-input-number v-model="activeData.precision" :min="0" placeholder="精度" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="activeData.tag === 'el-input-number'" label="按钮位置">
|
||||
<el-radio-group v-model="activeData['controls-position']">
|
||||
<el-radio-button label="">
|
||||
默认
|
||||
</el-radio-button>
|
||||
<el-radio-button label="right">
|
||||
右侧
|
||||
</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="activeData.maxlength !== undefined" label="最多输入">
|
||||
<el-input v-model="activeData.maxlength" placeholder="请输入字符长度">
|
||||
<template slot="append">
|
||||
个字符
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="activeData['active-text'] !== undefined" label="开启提示">
|
||||
<el-input v-model="activeData['active-text']" placeholder="请输入开启提示" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="activeData['inactive-text'] !== undefined" label="关闭提示">
|
||||
<el-input v-model="activeData['inactive-text']" placeholder="请输入关闭提示" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="activeData['active-value'] !== undefined" label="开启值">
|
||||
<el-input
|
||||
:value="setDefaultValue(activeData['active-value'])"
|
||||
placeholder="请输入开启值"
|
||||
@input="onSwitchValueInput($event, 'active-value')"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="activeData['inactive-value'] !== undefined" label="关闭值">
|
||||
<el-input
|
||||
:value="setDefaultValue(activeData['inactive-value'])"
|
||||
placeholder="请输入关闭值"
|
||||
@input="onSwitchValueInput($event, 'inactive-value')"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="activeData.type !== undefined && 'el-date-picker' === activeData.tag"
|
||||
label="时间类型"
|
||||
>
|
||||
<el-select
|
||||
v-model="activeData.type"
|
||||
placeholder="请选择时间类型"
|
||||
:style="{ width: '100%' }"
|
||||
@change="dateTypeChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="(item, index) in dateOptions"
|
||||
:key="index"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="activeData.name !== undefined" label="文件字段名">
|
||||
<el-input v-model="activeData.name" placeholder="请输入上传文件字段名" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="activeData.accept !== undefined" label="文件类型">
|
||||
<el-select
|
||||
v-model="activeData.accept"
|
||||
placeholder="请选择文件类型"
|
||||
:style="{ width: '100%' }"
|
||||
clearable
|
||||
>
|
||||
<el-option label="图片" value="image/*" />
|
||||
<el-option label="视频" value="video/*" />
|
||||
<el-option label="音频" value="audio/*" />
|
||||
<el-option label="excel" value=".xls,.xlsx" />
|
||||
<el-option label="word" value=".doc,.docx" />
|
||||
<el-option label="pdf" value=".pdf" />
|
||||
<el-option label="txt" value=".txt" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="activeData.fileSize !== undefined" label="文件大小">
|
||||
<el-input v-model.number="activeData.fileSize" placeholder="请输入文件大小">
|
||||
<el-select slot="append" v-model="activeData.sizeUnit" :style="{ width: '66px' }">
|
||||
<el-option label="KB" value="KB" />
|
||||
<el-option label="MB" value="MB" />
|
||||
<el-option label="GB" value="GB" />
|
||||
</el-select>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="activeData.action !== undefined" label="上传地址">
|
||||
<el-input v-model="activeData.action" placeholder="请输入上传地址" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="activeData['list-type'] !== undefined" label="列表类型">
|
||||
<el-radio-group v-model="activeData['list-type']" size="small">
|
||||
<el-radio-button label="text">
|
||||
text
|
||||
</el-radio-button>
|
||||
<el-radio-button label="picture">
|
||||
picture
|
||||
</el-radio-button>
|
||||
<el-radio-button label="picture-card">
|
||||
picture-card
|
||||
</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="activeData.buttonText !== undefined"
|
||||
v-show="'picture-card' !== activeData['list-type']"
|
||||
label="按钮文字"
|
||||
>
|
||||
<el-input v-model="activeData.buttonText" placeholder="请输入按钮文字" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="activeData['range-separator'] !== undefined" label="分隔符">
|
||||
<el-input v-model="activeData['range-separator']" placeholder="请输入分隔符" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="activeData['picker-options'] !== undefined" label="时间段">
|
||||
<el-input
|
||||
v-model="activeData['picker-options'].selectableRange"
|
||||
placeholder="请输入时间段"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="activeData.format !== undefined" label="时间格式">
|
||||
<el-input
|
||||
:value="activeData.format"
|
||||
placeholder="请输入时间格式"
|
||||
@input="setTimeValue($event)"
|
||||
/>
|
||||
</el-form-item>
|
||||
<template v-if="['el-checkbox-group', 'el-radio-group', 'el-select'].indexOf(activeData.tag) > -1">
|
||||
<el-divider>选项</el-divider>
|
||||
<draggable
|
||||
:list="activeData.options"
|
||||
:animation="340"
|
||||
group="selectItem"
|
||||
handle=".option-drag"
|
||||
>
|
||||
<div v-for="(item, index) in activeData.options" :key="index" class="select-item">
|
||||
<div class="select-line-icon option-drag">
|
||||
<i class="el-icon-s-operation" />
|
||||
</div>
|
||||
<el-input v-model="item.label" placeholder="选项名" size="small" />
|
||||
<el-input
|
||||
placeholder="选项值"
|
||||
size="small"
|
||||
:value="item.value"
|
||||
@input="setOptionValue(item, $event)"
|
||||
/>
|
||||
<div class="close-btn select-line-icon" @click="activeData.options.splice(index, 1)">
|
||||
<i class="el-icon-remove-outline" />
|
||||
</div>
|
||||
</div>
|
||||
</draggable>
|
||||
<div style="margin-left: 20px;">
|
||||
<el-button
|
||||
style="padding-bottom: 0"
|
||||
icon="el-icon-circle-plus-outline"
|
||||
type="text"
|
||||
@click="addSelectItem"
|
||||
>
|
||||
添加选项
|
||||
</el-button>
|
||||
</div>
|
||||
<el-divider />
|
||||
</template>
|
||||
|
||||
<template v-if="['el-cascader'].indexOf(activeData.tag) > -1">
|
||||
<el-divider>选项</el-divider>
|
||||
<el-form-item label="数据类型">
|
||||
<el-radio-group v-model="activeData.dataType" size="small">
|
||||
<el-radio-button label="dynamic">
|
||||
动态数据
|
||||
</el-radio-button>
|
||||
<el-radio-button label="static">
|
||||
静态数据
|
||||
</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
<template v-if="activeData.dataType === 'dynamic'">
|
||||
<el-form-item label="标签键名">
|
||||
<el-input v-model="activeData.labelKey" placeholder="请输入标签键名" />
|
||||
</el-form-item>
|
||||
<el-form-item label="值键名">
|
||||
<el-input v-model="activeData.valueKey" placeholder="请输入值键名" />
|
||||
</el-form-item>
|
||||
<el-form-item label="子级键名">
|
||||
<el-input v-model="activeData.childrenKey" placeholder="请输入子级键名" />
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<el-tree
|
||||
v-if="activeData.dataType === 'static'"
|
||||
draggable
|
||||
:data="activeData.options"
|
||||
node-key="id"
|
||||
:expand-on-click-node="false"
|
||||
:render-content="renderContent"
|
||||
/>
|
||||
<div v-if="activeData.dataType === 'static'" style="margin-left: 20px">
|
||||
<el-button
|
||||
style="padding-bottom: 0"
|
||||
icon="el-icon-circle-plus-outline"
|
||||
type="text"
|
||||
@click="addTreeItem"
|
||||
>
|
||||
添加父级
|
||||
</el-button>
|
||||
</div>
|
||||
<el-divider />
|
||||
</template>
|
||||
|
||||
<el-form-item v-if="activeData.optionType !== undefined" label="选项样式">
|
||||
<el-radio-group v-model="activeData.optionType">
|
||||
<el-radio-button label="default">
|
||||
默认
|
||||
</el-radio-button>
|
||||
<el-radio-button label="button">
|
||||
按钮
|
||||
</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="activeData['active-color'] !== undefined" label="开启颜色">
|
||||
<el-color-picker v-model="activeData['active-color']" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="activeData['inactive-color'] !== undefined" label="关闭颜色">
|
||||
<el-color-picker v-model="activeData['inactive-color']" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item v-if="activeData['allow-half'] !== undefined" label="允许半选">
|
||||
<el-switch v-model="activeData['allow-half']" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="activeData['show-text'] !== undefined" label="辅助文字">
|
||||
<el-switch v-model="activeData['show-text']" @change="rateTextChange" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="activeData['show-score'] !== undefined" label="显示分数">
|
||||
<el-switch v-model="activeData['show-score']" @change="rateScoreChange" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="activeData['show-stops'] !== undefined" label="显示间断点">
|
||||
<el-switch v-model="activeData['show-stops']" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="activeData.range !== undefined" label="范围选择">
|
||||
<el-switch v-model="activeData.range" @change="rangeChange" />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="activeData.border !== undefined && activeData.optionType === 'default'"
|
||||
label="是否带边框"
|
||||
>
|
||||
<el-switch v-model="activeData.border" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="activeData.tag === 'el-color-picker'" label="颜色格式">
|
||||
<el-select
|
||||
v-model="activeData['color-format']"
|
||||
placeholder="请选择颜色格式"
|
||||
:style="{ width: '100%' }"
|
||||
@change="colorFormatChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="(item, index) in colorFormatOptions"
|
||||
:key="index"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="activeData.size !== undefined &&
|
||||
(activeData.optionType === 'button' ||
|
||||
activeData.border ||
|
||||
activeData.tag === 'el-color-picker')"
|
||||
label="选项尺寸"
|
||||
>
|
||||
<el-radio-group v-model="activeData.size">
|
||||
<el-radio-button label="medium">
|
||||
中等
|
||||
</el-radio-button>
|
||||
<el-radio-button label="small">
|
||||
较小
|
||||
</el-radio-button>
|
||||
<el-radio-button label="mini">
|
||||
迷你
|
||||
</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="activeData['show-word-limit'] !== undefined" label="输入统计">
|
||||
<el-switch v-model="activeData['show-word-limit']" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="activeData.tag === 'el-input-number'" label="严格步数">
|
||||
<el-switch v-model="activeData['step-strictly']" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="activeData.tag === 'el-cascader'" label="是否多选">
|
||||
<el-switch v-model="activeData.props.props.multiple" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="activeData.tag === 'el-cascader'" label="展示全路径">
|
||||
<el-switch v-model="activeData['show-all-levels']" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="activeData.tag === 'el-cascader'" label="可否筛选">
|
||||
<el-switch v-model="activeData.filterable" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="activeData.clearable !== undefined" label="能否清空">
|
||||
<el-switch v-model="activeData.clearable" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="activeData.showTip !== undefined" label="显示提示">
|
||||
<el-switch v-model="activeData.showTip" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="activeData.multiple !== undefined" label="多选文件">
|
||||
<el-switch v-model="activeData.multiple" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="activeData['auto-upload'] !== undefined" label="自动上传">
|
||||
<el-switch v-model="activeData['auto-upload']" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="activeData.readonly !== undefined" label="是否只读">
|
||||
<el-switch v-model="activeData.readonly" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="activeData.disabled !== undefined" label="是否禁用">
|
||||
<el-switch v-model="activeData.disabled" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="activeData.tag === 'el-select'" label="是否可搜索">
|
||||
<el-switch v-model="activeData.filterable" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="activeData.tag === 'el-select'" label="是否多选">
|
||||
<el-switch v-model="activeData.multiple" @change="multipleChange" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="activeData.required !== undefined" label="是否必填">
|
||||
<el-switch v-model="activeData.required" />
|
||||
</el-form-item>
|
||||
|
||||
<template v-if="activeData.layoutTree">
|
||||
<el-divider>布局结构树</el-divider>
|
||||
<el-tree
|
||||
:data="[activeData]"
|
||||
:props="layoutTreeProps"
|
||||
node-key="renderKey"
|
||||
default-expand-all
|
||||
draggable
|
||||
>
|
||||
<span slot-scope="{ node, data }">
|
||||
<span class="node-label">
|
||||
<svg-icon class="node-icon" :icon-class="data.tagIcon" />
|
||||
{{ node.label }}
|
||||
</span>
|
||||
</span>
|
||||
</el-tree>
|
||||
</template>
|
||||
|
||||
<template v-if="activeData.layout === 'colFormItem' && activeData.tag !== 'el-button'">
|
||||
<el-divider>正则校验</el-divider>
|
||||
<div
|
||||
v-for="(item, index) in activeData.regList"
|
||||
:key="index"
|
||||
class="reg-item"
|
||||
>
|
||||
<span class="close-btn" @click="activeData.regList.splice(index, 1)">
|
||||
<i class="el-icon-close" />
|
||||
</span>
|
||||
<el-form-item label="表达式">
|
||||
<el-input v-model="item.pattern" placeholder="请输入正则" />
|
||||
</el-form-item>
|
||||
<el-form-item label="错误提示" style="margin-bottom:0">
|
||||
<el-input v-model="item.message" placeholder="请输入错误提示" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div style="margin-left: 20px">
|
||||
<el-button icon="el-icon-circle-plus-outline" type="text" @click="addReg">
|
||||
添加规则
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-form>
|
||||
<!-- 表单属性 -->
|
||||
<el-form v-show="currentTab === 'form'" size="small" label-width="90px">
|
||||
<el-form-item label="表单名">
|
||||
<el-input v-model="formConf.formRef" placeholder="请输入表单名(ref)" />
|
||||
</el-form-item>
|
||||
<el-form-item label="表单模型">
|
||||
<el-input v-model="formConf.formModel" placeholder="请输入数据模型" />
|
||||
</el-form-item>
|
||||
<el-form-item label="校验模型">
|
||||
<el-input v-model="formConf.formRules" placeholder="请输入校验模型" />
|
||||
</el-form-item>
|
||||
<el-form-item label="表单尺寸">
|
||||
<el-radio-group v-model="formConf.size">
|
||||
<el-radio-button label="medium">
|
||||
中等
|
||||
</el-radio-button>
|
||||
<el-radio-button label="small">
|
||||
较小
|
||||
</el-radio-button>
|
||||
<el-radio-button label="mini">
|
||||
迷你
|
||||
</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="标签对齐">
|
||||
<el-radio-group v-model="formConf.labelPosition">
|
||||
<el-radio-button label="left">
|
||||
左对齐
|
||||
</el-radio-button>
|
||||
<el-radio-button label="right">
|
||||
右对齐
|
||||
</el-radio-button>
|
||||
<el-radio-button label="top">
|
||||
顶部对齐
|
||||
</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="标签宽度">
|
||||
<el-input-number v-model="formConf.labelWidth" placeholder="标签宽度" />
|
||||
</el-form-item>
|
||||
<el-form-item label="栅格间隔">
|
||||
<el-input-number v-model="formConf.gutter" :min="0" placeholder="栅格间隔" />
|
||||
</el-form-item>
|
||||
<el-form-item label="禁用表单">
|
||||
<el-switch v-model="formConf.disabled" />
|
||||
</el-form-item>
|
||||
<el-form-item label="表单按钮">
|
||||
<el-switch v-model="formConf.formBtns" />
|
||||
</el-form-item>
|
||||
<el-form-item label="显示未选中组件边框">
|
||||
<el-switch v-model="formConf.unFocusedComponentBorder" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
|
||||
<treeNode-dialog :visible.sync="dialogVisible" title="添加选项" @commit="addNode" />
|
||||
<icons-dialog :visible.sync="iconsVisible" :current="activeData[currentIconModel]" @select="setIcon" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import draggable from 'vuedraggable'
|
||||
import TreeNodeDialog from './TreeNodeDialog'
|
||||
import { isNumberStr } from '@/utils/index'
|
||||
import IconsDialog from './IconsDialog'
|
||||
import {
|
||||
inputComponents,
|
||||
selectComponents
|
||||
} from '@/utils/generator/config'
|
||||
|
||||
const dateTimeFormat = {
|
||||
date: 'yyyy-MM-dd',
|
||||
week: 'yyyy 第 WW 周',
|
||||
month: 'yyyy-MM',
|
||||
year: 'yyyy',
|
||||
datetime: 'yyyy-MM-dd HH:mm:ss',
|
||||
daterange: 'yyyy-MM-dd',
|
||||
monthrange: 'yyyy-MM',
|
||||
datetimerange: 'yyyy-MM-dd HH:mm:ss'
|
||||
}
|
||||
|
||||
export default {
|
||||
components: {
|
||||
draggable,
|
||||
TreeNodeDialog,
|
||||
IconsDialog
|
||||
},
|
||||
props: ['showField', 'activeData', 'formConf'],
|
||||
data() {
|
||||
return {
|
||||
currentTab: 'field',
|
||||
currentNode: null,
|
||||
dialogVisible: false,
|
||||
iconsVisible: false,
|
||||
currentIconModel: null,
|
||||
dateTypeOptions: [
|
||||
{
|
||||
label: '日(date)',
|
||||
value: 'date'
|
||||
},
|
||||
{
|
||||
label: '周(week)',
|
||||
value: 'week'
|
||||
},
|
||||
{
|
||||
label: '月(month)',
|
||||
value: 'month'
|
||||
},
|
||||
{
|
||||
label: '年(year)',
|
||||
value: 'year'
|
||||
},
|
||||
{
|
||||
label: '日期时间(datetime)',
|
||||
value: 'datetime'
|
||||
}
|
||||
],
|
||||
dateRangeTypeOptions: [
|
||||
{
|
||||
label: '日期范围(daterange)',
|
||||
value: 'daterange'
|
||||
},
|
||||
{
|
||||
label: '月范围(monthrange)',
|
||||
value: 'monthrange'
|
||||
},
|
||||
{
|
||||
label: '日期时间范围(datetimerange)',
|
||||
value: 'datetimerange'
|
||||
}
|
||||
],
|
||||
colorFormatOptions: [
|
||||
{
|
||||
label: 'hex',
|
||||
value: 'hex'
|
||||
},
|
||||
{
|
||||
label: 'rgb',
|
||||
value: 'rgb'
|
||||
},
|
||||
{
|
||||
label: 'rgba',
|
||||
value: 'rgba'
|
||||
},
|
||||
{
|
||||
label: 'hsv',
|
||||
value: 'hsv'
|
||||
},
|
||||
{
|
||||
label: 'hsl',
|
||||
value: 'hsl'
|
||||
}
|
||||
],
|
||||
justifyOptions: [
|
||||
{
|
||||
label: 'start',
|
||||
value: 'start'
|
||||
},
|
||||
{
|
||||
label: 'end',
|
||||
value: 'end'
|
||||
},
|
||||
{
|
||||
label: 'center',
|
||||
value: 'center'
|
||||
},
|
||||
{
|
||||
label: 'space-around',
|
||||
value: 'space-around'
|
||||
},
|
||||
{
|
||||
label: 'space-between',
|
||||
value: 'space-between'
|
||||
}
|
||||
],
|
||||
layoutTreeProps: {
|
||||
label(data) {
|
||||
return data.componentName || `${data.label}: ${data.vModel}`
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
documentLink() {
|
||||
return (
|
||||
this.activeData.document
|
||||
|| 'https://element.eleme.cn/#/zh-CN/component/installation'
|
||||
)
|
||||
},
|
||||
dateOptions() {
|
||||
if (
|
||||
this.activeData.type !== undefined
|
||||
&& this.activeData.tag === 'el-date-picker'
|
||||
) {
|
||||
if (this.activeData['start-placeholder'] === undefined) {
|
||||
return this.dateTypeOptions
|
||||
}
|
||||
return this.dateRangeTypeOptions
|
||||
}
|
||||
return []
|
||||
},
|
||||
tagList() {
|
||||
return [
|
||||
{
|
||||
label: '输入型组件',
|
||||
options: inputComponents
|
||||
},
|
||||
{
|
||||
label: '选择型组件',
|
||||
options: selectComponents
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
addReg() {
|
||||
this.activeData.regList.push({
|
||||
pattern: '',
|
||||
message: ''
|
||||
})
|
||||
},
|
||||
addSelectItem() {
|
||||
this.activeData.options.push({
|
||||
label: '',
|
||||
value: ''
|
||||
})
|
||||
},
|
||||
addTreeItem() {
|
||||
++this.idGlobal
|
||||
this.dialogVisible = true
|
||||
this.currentNode = this.activeData.options
|
||||
},
|
||||
renderContent(h, { node, data }) {
|
||||
return (
|
||||
<div class="custom-tree-node">
|
||||
<span>{node.label}</span>
|
||||
<span class="node-operation">
|
||||
<i on-click={() => this.append(data)}
|
||||
class="el-icon-plus"
|
||||
title="添加"
|
||||
></i>
|
||||
<i on-click={() => this.remove(node, data)}
|
||||
class="el-icon-delete"
|
||||
title="删除"
|
||||
></i>
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
append(data) {
|
||||
if (!data.children) {
|
||||
this.$set(data, 'children', [])
|
||||
}
|
||||
this.dialogVisible = true
|
||||
this.currentNode = data.children
|
||||
},
|
||||
remove(node, data) {
|
||||
const { parent } = node
|
||||
const children = parent.data.children || parent.data
|
||||
const index = children.findIndex(d => d.id === data.id)
|
||||
children.splice(index, 1)
|
||||
},
|
||||
addNode(data) {
|
||||
this.currentNode.push(data)
|
||||
},
|
||||
setOptionValue(item, val) {
|
||||
item.value = isNumberStr(val) ? +val : val
|
||||
},
|
||||
setDefaultValue(val) {
|
||||
if (Array.isArray(val)) {
|
||||
return val.join(',')
|
||||
}
|
||||
if (['string', 'number'].indexOf(val) > -1) {
|
||||
return val
|
||||
}
|
||||
if (typeof val === 'boolean') {
|
||||
return `${val}`
|
||||
}
|
||||
return val
|
||||
},
|
||||
onDefaultValueInput(str) {
|
||||
if (Array.isArray(this.activeData.defaultValue)) {
|
||||
// 数组
|
||||
this.$set(
|
||||
this.activeData,
|
||||
'defaultValue',
|
||||
str.split(',').map(val => (isNumberStr(val) ? +val : val))
|
||||
)
|
||||
} else if (['true', 'false'].indexOf(str) > -1) {
|
||||
// 布尔
|
||||
this.$set(this.activeData, 'defaultValue', JSON.parse(str))
|
||||
} else {
|
||||
// 字符串和数字
|
||||
this.$set(
|
||||
this.activeData,
|
||||
'defaultValue',
|
||||
isNumberStr(str) ? +str : str
|
||||
)
|
||||
}
|
||||
},
|
||||
onSwitchValueInput(val, name) {
|
||||
if (['true', 'false'].indexOf(val) > -1) {
|
||||
this.$set(this.activeData, name, JSON.parse(val))
|
||||
} else {
|
||||
this.$set(this.activeData, name, isNumberStr(val) ? +val : val)
|
||||
}
|
||||
},
|
||||
setTimeValue(val, type) {
|
||||
const valueFormat = type === 'week' ? dateTimeFormat.date : val
|
||||
this.$set(this.activeData, 'defaultValue', null)
|
||||
this.$set(this.activeData, 'value-format', valueFormat)
|
||||
this.$set(this.activeData, 'format', val)
|
||||
},
|
||||
spanChange(val) {
|
||||
this.formConf.span = val
|
||||
},
|
||||
multipleChange(val) {
|
||||
this.$set(this.activeData, 'defaultValue', val ? [] : '')
|
||||
},
|
||||
dateTypeChange(val) {
|
||||
this.setTimeValue(dateTimeFormat[val], val)
|
||||
},
|
||||
rangeChange(val) {
|
||||
this.$set(
|
||||
this.activeData,
|
||||
'defaultValue',
|
||||
val ? [this.activeData.min, this.activeData.max] : this.activeData.min
|
||||
)
|
||||
},
|
||||
rateTextChange(val) {
|
||||
if (val) this.activeData['show-score'] = false
|
||||
},
|
||||
rateScoreChange(val) {
|
||||
if (val) this.activeData['show-text'] = false
|
||||
},
|
||||
colorFormatChange(val) {
|
||||
this.activeData.defaultValue = null
|
||||
this.activeData['show-alpha'] = val.indexOf('a') > -1
|
||||
this.activeData.renderKey = +new Date() // 更新renderKey,重新渲染该组件
|
||||
},
|
||||
openIconsDialog(model) {
|
||||
this.iconsVisible = true
|
||||
this.currentIconModel = model
|
||||
},
|
||||
setIcon(val) {
|
||||
this.activeData[this.currentIconModel] = val
|
||||
},
|
||||
tagChange(tagIcon) {
|
||||
let target = inputComponents.find(item => item.tagIcon === tagIcon)
|
||||
if (!target) target = selectComponents.find(item => item.tagIcon === tagIcon)
|
||||
this.$emit('tag-change', target)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.right-board {
|
||||
width: 350px;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
padding-top: 3px;
|
||||
.field-box {
|
||||
position: relative;
|
||||
height: calc(100vh - 42px);
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
}
|
||||
.el-scrollbar {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
.select-item {
|
||||
display: flex;
|
||||
border: 1px dashed #fff;
|
||||
box-sizing: border-box;
|
||||
& .close-btn {
|
||||
cursor: pointer;
|
||||
color: #f56c6c;
|
||||
}
|
||||
& .el-input + .el-input {
|
||||
margin-left: 4px;
|
||||
}
|
||||
}
|
||||
.select-item + .select-item {
|
||||
margin-top: 4px;
|
||||
}
|
||||
.select-item.sortable-chosen {
|
||||
border: 1px dashed #00875A;
|
||||
}
|
||||
.select-line-icon {
|
||||
line-height: 32px;
|
||||
font-size: 22px;
|
||||
padding: 0 4px;
|
||||
color: #777;
|
||||
}
|
||||
.option-drag {
|
||||
cursor: move;
|
||||
}
|
||||
.time-range {
|
||||
.el-date-editor {
|
||||
width: 227px;
|
||||
}
|
||||
::v-deep .el-icon-time {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.document-link {
|
||||
position: absolute;
|
||||
display: block;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
top: 0;
|
||||
left: 0;
|
||||
cursor: pointer;
|
||||
background: #00875A;
|
||||
z-index: 1;
|
||||
border-radius: 0 0 6px 0;
|
||||
text-align: center;
|
||||
line-height: 26px;
|
||||
color: #fff;
|
||||
font-size: 18px;
|
||||
}
|
||||
.node-label{
|
||||
font-size: 14px;
|
||||
}
|
||||
.node-icon{
|
||||
color: #bebfc3;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,148 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-dialog
|
||||
v-bind="$attrs"
|
||||
:close-on-click-modal="false"
|
||||
:modal-append-to-body="false"
|
||||
v-on="$listeners"
|
||||
@open="onOpen"
|
||||
@close="onClose"
|
||||
>
|
||||
<el-row :gutter="0">
|
||||
<el-form
|
||||
ref="elForm"
|
||||
:model="formData"
|
||||
:rules="rules"
|
||||
size="small"
|
||||
label-width="100px"
|
||||
>
|
||||
<el-col :span="24">
|
||||
<el-form-item
|
||||
label="选项名"
|
||||
prop="label"
|
||||
>
|
||||
<el-input
|
||||
v-model="formData.label"
|
||||
placeholder="请输入选项名"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-form-item
|
||||
label="选项值"
|
||||
prop="value"
|
||||
>
|
||||
<el-input
|
||||
v-model="formData.value"
|
||||
placeholder="请输入选项值"
|
||||
clearable
|
||||
>
|
||||
<el-select
|
||||
slot="append"
|
||||
v-model="dataType"
|
||||
:style="{width: '100px'}"
|
||||
>
|
||||
<el-option
|
||||
v-for="(item, index) in dataTypeOptions"
|
||||
:key="index"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
:disabled="item.disabled"
|
||||
/>
|
||||
</el-select>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-form>
|
||||
</el-row>
|
||||
<div slot="footer">
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="handleConfirm"
|
||||
>
|
||||
确定
|
||||
</el-button>
|
||||
<el-button @click="close">
|
||||
取消
|
||||
</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { isNumberStr } from '@/utils/index'
|
||||
|
||||
export default {
|
||||
components: {},
|
||||
inheritAttrs: false,
|
||||
props: [],
|
||||
data() {
|
||||
return {
|
||||
id: 100,
|
||||
formData: {
|
||||
label: undefined,
|
||||
value: undefined
|
||||
},
|
||||
rules: {
|
||||
label: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入选项名',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
value: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入选项值',
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
},
|
||||
dataType: 'string',
|
||||
dataTypeOptions: [
|
||||
{
|
||||
label: '字符串',
|
||||
value: 'string'
|
||||
},
|
||||
{
|
||||
label: '数字',
|
||||
value: 'number'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
computed: {},
|
||||
watch: {
|
||||
'formData.value': function (val) {
|
||||
this.dataType = isNumberStr(val) ? 'number' : 'string'
|
||||
}
|
||||
},
|
||||
created() {},
|
||||
mounted() {},
|
||||
methods: {
|
||||
onOpen() {
|
||||
this.formData = {
|
||||
label: undefined,
|
||||
value: undefined
|
||||
}
|
||||
},
|
||||
onClose() {},
|
||||
close() {
|
||||
this.$emit('update:visible', false)
|
||||
},
|
||||
handleConfirm() {
|
||||
this.$refs.elForm.validate(valid => {
|
||||
if (!valid) return
|
||||
if (this.dataType === 'number') {
|
||||
this.formData.value = parseFloat(this.formData.value)
|
||||
}
|
||||
this.formData.id = this.id++
|
||||
this.$emit('commit', this.formData)
|
||||
this.close()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,775 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<div class="left-board">
|
||||
<div class="logo-wrapper">
|
||||
<div class="logo">
|
||||
<img :src="logo" alt="logo"> Form Generator
|
||||
</div>
|
||||
</div>
|
||||
<el-scrollbar class="left-scrollbar">
|
||||
<div class="components-list">
|
||||
<div class="components-title">
|
||||
<svg-icon icon-class="component" />输入型组件
|
||||
</div>
|
||||
<draggable
|
||||
class="components-draggable"
|
||||
:list="inputComponents"
|
||||
:group="{ name: 'componentsGroup', pull: 'clone', put: false }"
|
||||
:clone="cloneComponent"
|
||||
draggable=".components-item"
|
||||
:sort="false"
|
||||
@end="onEnd"
|
||||
>
|
||||
<div
|
||||
v-for="(element, index) in inputComponents" :key="index" class="components-item"
|
||||
@click="addComponent(element)"
|
||||
>
|
||||
<div class="components-body">
|
||||
<svg-icon :icon-class="element.tagIcon" />
|
||||
{{ element.label }}
|
||||
</div>
|
||||
</div>
|
||||
</draggable>
|
||||
<div class="components-title">
|
||||
<svg-icon icon-class="component" />选择型组件
|
||||
</div>
|
||||
<draggable
|
||||
class="components-draggable"
|
||||
:list="selectComponents"
|
||||
:group="{ name: 'componentsGroup', pull: 'clone', put: false }"
|
||||
:clone="cloneComponent"
|
||||
draggable=".components-item"
|
||||
:sort="false"
|
||||
@end="onEnd"
|
||||
>
|
||||
<div
|
||||
v-for="(element, index) in selectComponents"
|
||||
:key="index"
|
||||
class="components-item"
|
||||
@click="addComponent(element)"
|
||||
>
|
||||
<div class="components-body">
|
||||
<svg-icon :icon-class="element.tagIcon" />
|
||||
{{ element.label }}
|
||||
</div>
|
||||
</div>
|
||||
</draggable>
|
||||
<div class="components-title">
|
||||
<svg-icon icon-class="component" /> 布局型组件
|
||||
</div>
|
||||
<draggable
|
||||
class="components-draggable" :list="layoutComponents"
|
||||
:group="{ name: 'componentsGroup', pull: 'clone', put: false }" :clone="cloneComponent"
|
||||
draggable=".components-item" :sort="false" @end="onEnd"
|
||||
>
|
||||
<div
|
||||
v-for="(element, index) in layoutComponents" :key="index" class="components-item"
|
||||
@click="addComponent(element)"
|
||||
>
|
||||
<div class="components-body">
|
||||
<svg-icon :icon-class="element.tagIcon" />
|
||||
{{ element.label }}
|
||||
</div>
|
||||
</div>
|
||||
</draggable>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
|
||||
<div class="center-board">
|
||||
<div class="action-bar">
|
||||
<el-button icon="el-icon-download" type="text" @click="download">
|
||||
导出vue文件
|
||||
</el-button>
|
||||
<el-button class="copy-btn-main" icon="el-icon-document-copy" type="text" @click="copy">
|
||||
复制代码
|
||||
</el-button>
|
||||
<el-button class="delete-btn" icon="el-icon-delete" type="text" @click="empty">
|
||||
清空
|
||||
</el-button>
|
||||
</div>
|
||||
<el-scrollbar class="center-scrollbar">
|
||||
<el-row class="center-board-row" :gutter="formConf.gutter">
|
||||
<el-form
|
||||
:size="formConf.size"
|
||||
:label-position="formConf.labelPosition"
|
||||
:disabled="formConf.disabled"
|
||||
:label-width="formConf.labelWidth + 'px'"
|
||||
>
|
||||
<draggable class="drawing-board" :list="drawingList" :animation="340" group="componentsGroup">
|
||||
<draggable-item
|
||||
v-for="(element, index) in drawingList"
|
||||
:key="element.renderKey"
|
||||
:drawing-list="drawingList"
|
||||
:element="element"
|
||||
:index="index"
|
||||
:active-id="activeId"
|
||||
:form-conf="formConf"
|
||||
@activeItem="activeFormItem"
|
||||
@copyItem="drawingItemCopy"
|
||||
@deleteItem="drawingItemDelete"
|
||||
/>
|
||||
</draggable>
|
||||
<div v-show="!drawingList.length" class="empty-info">
|
||||
从左侧拖入或点选组件进行表单设计
|
||||
</div>
|
||||
</el-form>
|
||||
</el-row>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
|
||||
<right-panel
|
||||
:active-data="activeData"
|
||||
:form-conf="formConf"
|
||||
:show-field="!!drawingList.length"
|
||||
@tag-change="tagChange"
|
||||
/>
|
||||
|
||||
<code-type-dialog
|
||||
:visible.sync="dialogVisible"
|
||||
title="选择生成类型"
|
||||
:show-file-name="showFileName"
|
||||
@confirm="generate"
|
||||
/>
|
||||
<input id="copyNode" type="hidden">
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import draggable from 'vuedraggable'
|
||||
import beautifier from 'js-beautify'
|
||||
import ClipboardJS from 'clipboard'
|
||||
import render from '@/utils/generator/render'
|
||||
import RightPanel from './RightPanel'
|
||||
import { inputComponents, selectComponents, layoutComponents, formConf } from '@/utils/generator/config'
|
||||
import { beautifierConf, titleCase } from '@/utils/index'
|
||||
import { makeUpHtml, vueTemplate, vueScript, cssStyle } from '@/utils/generator/html'
|
||||
import { makeUpJs } from '@/utils/generator/js'
|
||||
import { makeUpCss } from '@/utils/generator/css'
|
||||
import { drawingDefaultValue, initDrawingDefaultValue, cleanDrawingDefaultValue } from '@/utils/generator/drawingDefault'
|
||||
import logo from '@/assets/logo/logo.png'
|
||||
import CodeTypeDialog from './CodeTypeDialog'
|
||||
import DraggableItem from './DraggableItem'
|
||||
|
||||
let oldActiveId
|
||||
let tempActiveData
|
||||
let clipboard = null
|
||||
|
||||
export default {
|
||||
components: {
|
||||
draggable,
|
||||
render,
|
||||
RightPanel,
|
||||
CodeTypeDialog,
|
||||
DraggableItem
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
logo,
|
||||
idGlobal: 100,
|
||||
formConf,
|
||||
inputComponents,
|
||||
selectComponents,
|
||||
layoutComponents,
|
||||
labelWidth: 100,
|
||||
drawingList: drawingDefaultValue,
|
||||
drawingData: {},
|
||||
activeId: drawingDefaultValue[0].formId,
|
||||
drawerVisible: false,
|
||||
formData: {},
|
||||
dialogVisible: false,
|
||||
generateConf: null,
|
||||
showFileName: false,
|
||||
activeData: drawingDefaultValue[0]
|
||||
}
|
||||
},
|
||||
beforeCreate() {
|
||||
initDrawingDefaultValue()
|
||||
},
|
||||
created() {
|
||||
// 防止 firefox 下 拖拽 会新打卡一个选项卡
|
||||
document.body.ondrop = event => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'activeData.label': function (val, oldVal) {
|
||||
if (
|
||||
this.activeData.placeholder === undefined
|
||||
|| !this.activeData.tag
|
||||
|| oldActiveId !== this.activeId
|
||||
) {
|
||||
return
|
||||
}
|
||||
this.activeData.placeholder = this.activeData.placeholder.replace(oldVal, '') + val
|
||||
},
|
||||
activeId: {
|
||||
handler(val) {
|
||||
oldActiveId = val
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
clipboard = new ClipboardJS('#copyNode', {
|
||||
text: () => {
|
||||
const codeStr = this.generateCode()
|
||||
this.$notify({
|
||||
title: '成功',
|
||||
message: '代码已复制到剪切板,可粘贴。',
|
||||
type: 'success'
|
||||
})
|
||||
return codeStr
|
||||
}
|
||||
})
|
||||
clipboard.on('error', () => {
|
||||
this.$message.error('代码复制失败')
|
||||
})
|
||||
},
|
||||
beforeDestroy() {
|
||||
clipboard.destroy()
|
||||
},
|
||||
methods: {
|
||||
activeFormItem(element) {
|
||||
this.activeData = element
|
||||
this.activeId = element.formId
|
||||
},
|
||||
onEnd(obj) {
|
||||
if (obj.from !== obj.to) {
|
||||
this.activeData = tempActiveData
|
||||
this.activeId = this.idGlobal
|
||||
}
|
||||
},
|
||||
addComponent(item) {
|
||||
const clone = this.cloneComponent(item)
|
||||
this.drawingList.push(clone)
|
||||
this.activeFormItem(clone)
|
||||
},
|
||||
cloneComponent(origin) {
|
||||
const clone = JSON.parse(JSON.stringify(origin))
|
||||
clone.formId = ++this.idGlobal
|
||||
clone.span = formConf.span
|
||||
clone.renderKey = +new Date() // 改变renderKey后可以实现强制更新组件
|
||||
if (!clone.layout) clone.layout = 'colFormItem'
|
||||
if (clone.layout === 'colFormItem') {
|
||||
clone.vModel = `field${this.idGlobal}`
|
||||
clone.placeholder !== undefined && (clone.placeholder += clone.label)
|
||||
tempActiveData = clone
|
||||
} else if (clone.layout === 'rowFormItem') {
|
||||
delete clone.label
|
||||
clone.componentName = `row${this.idGlobal}`
|
||||
clone.gutter = this.formConf.gutter
|
||||
tempActiveData = clone
|
||||
}
|
||||
return tempActiveData
|
||||
},
|
||||
AssembleFormData() {
|
||||
this.formData = {
|
||||
fields: JSON.parse(JSON.stringify(this.drawingList)),
|
||||
...this.formConf
|
||||
}
|
||||
},
|
||||
generate(data) {
|
||||
const func = this[`exec${titleCase(this.operationType)}`]
|
||||
this.generateConf = data
|
||||
func && func(data)
|
||||
},
|
||||
execRun() {
|
||||
this.AssembleFormData()
|
||||
this.drawerVisible = true
|
||||
},
|
||||
execDownload(data) {
|
||||
const codeStr = this.generateCode()
|
||||
const blob = new Blob([codeStr], { type: 'text/plain;charset=utf-8' })
|
||||
this.$download.saveAs(blob, data.fileName)
|
||||
},
|
||||
execCopy() {
|
||||
document.getElementById('copyNode').click()
|
||||
},
|
||||
empty() {
|
||||
this.$confirm('确定要清空所有组件吗?', '提示', { type: 'warning' }).then(
|
||||
() => {
|
||||
this.drawingList = []
|
||||
cleanDrawingDefaultValue()
|
||||
}
|
||||
)
|
||||
},
|
||||
drawingItemCopy(item, parent) {
|
||||
let clone = JSON.parse(JSON.stringify(item))
|
||||
clone = this.createIdAndKey(clone)
|
||||
parent.push(clone)
|
||||
this.activeFormItem(clone)
|
||||
},
|
||||
createIdAndKey(item) {
|
||||
item.formId = ++this.idGlobal
|
||||
item.renderKey = +new Date()
|
||||
if (item.layout === 'colFormItem') {
|
||||
item.vModel = `field${this.idGlobal}`
|
||||
} else if (item.layout === 'rowFormItem') {
|
||||
item.componentName = `row${this.idGlobal}`
|
||||
}
|
||||
if (Array.isArray(item.children)) {
|
||||
item.children = item.children.map(childItem => this.createIdAndKey(childItem))
|
||||
}
|
||||
return item
|
||||
},
|
||||
drawingItemDelete(index, parent) {
|
||||
parent.splice(index, 1)
|
||||
this.$nextTick(() => {
|
||||
const len = this.drawingList.length
|
||||
if (len) {
|
||||
this.activeFormItem(this.drawingList[len - 1])
|
||||
}
|
||||
})
|
||||
},
|
||||
generateCode() {
|
||||
const { type } = this.generateConf
|
||||
this.AssembleFormData()
|
||||
const script = vueScript(makeUpJs(this.formData, type))
|
||||
const html = vueTemplate(makeUpHtml(this.formData, type))
|
||||
const css = cssStyle(makeUpCss(this.formData))
|
||||
return beautifier.html(html + script + css, beautifierConf.html)
|
||||
},
|
||||
download() {
|
||||
this.dialogVisible = true
|
||||
this.showFileName = true
|
||||
this.operationType = 'download'
|
||||
},
|
||||
run() {
|
||||
this.dialogVisible = true
|
||||
this.showFileName = false
|
||||
this.operationType = 'run'
|
||||
},
|
||||
copy() {
|
||||
this.dialogVisible = true
|
||||
this.showFileName = false
|
||||
this.operationType = 'copy'
|
||||
},
|
||||
tagChange(newTag) {
|
||||
newTag = this.cloneComponent(newTag)
|
||||
newTag.vModel = this.activeData.vModel
|
||||
newTag.formId = this.activeId
|
||||
newTag.span = this.activeData.span
|
||||
delete this.activeData.tag
|
||||
delete this.activeData.tagIcon
|
||||
delete this.activeData.document
|
||||
Object.keys(newTag).forEach(key => {
|
||||
if (this.activeData[key] !== undefined
|
||||
&& typeof this.activeData[key] === typeof newTag[key]) {
|
||||
newTag[key] = this.activeData[key]
|
||||
}
|
||||
})
|
||||
this.activeData = newTag
|
||||
this.updateDrawingList(newTag, this.drawingList)
|
||||
},
|
||||
updateDrawingList(newTag, list) {
|
||||
const index = list.findIndex(item => item.formId === this.activeId)
|
||||
if (index > -1) {
|
||||
list.splice(index, 1, newTag)
|
||||
} else {
|
||||
list.forEach(item => {
|
||||
if (Array.isArray(item.children)) this.updateDrawingList(newTag, item.children)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss'>
|
||||
.editor-tabs{
|
||||
background: #121315;
|
||||
.el-tabs__header{
|
||||
margin: 0;
|
||||
border-bottom-color: #121315;
|
||||
.el-tabs__nav{
|
||||
border-color: #121315;
|
||||
}
|
||||
}
|
||||
.el-tabs__item{
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
color: #888a8e;
|
||||
border-left: 1px solid #121315 !important;
|
||||
background: #363636;
|
||||
margin-right: 5px;
|
||||
user-select: none;
|
||||
}
|
||||
.el-tabs__item.is-active{
|
||||
background: #1e1e1e;
|
||||
border-bottom-color: #1e1e1e!important;
|
||||
color: #fff;
|
||||
}
|
||||
.el-icon-edit{
|
||||
color: #f1fa8c;
|
||||
}
|
||||
.el-icon-document{
|
||||
color: #a95812;
|
||||
}
|
||||
}
|
||||
|
||||
// home
|
||||
.right-scrollbar {
|
||||
.el-scrollbar__view {
|
||||
padding: 12px 18px 15px 15px;
|
||||
}
|
||||
}
|
||||
.left-scrollbar .el-scrollbar__wrap {
|
||||
box-sizing: border-box;
|
||||
overflow-x: hidden !important;
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
.center-tabs{
|
||||
.el-tabs__header{
|
||||
margin-bottom: 0!important;
|
||||
}
|
||||
.el-tabs__item{
|
||||
width: 50%;
|
||||
text-align: center;
|
||||
}
|
||||
.el-tabs__nav{
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
.reg-item{
|
||||
padding: 12px 6px;
|
||||
background: #f8f8f8;
|
||||
position: relative;
|
||||
border-radius: 4px;
|
||||
.close-btn{
|
||||
position: absolute;
|
||||
right: -6px;
|
||||
top: -6px;
|
||||
display: block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
line-height: 16px;
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
border-radius: 50%;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
z-index: 1;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
&:hover{
|
||||
background: rgba(210, 23, 23, 0.5)
|
||||
}
|
||||
}
|
||||
& + .reg-item{
|
||||
margin-top: 18px;
|
||||
}
|
||||
}
|
||||
.action-bar{
|
||||
& .el-button+.el-button {
|
||||
margin-left: 15px;
|
||||
}
|
||||
& i {
|
||||
font-size: 20px;
|
||||
vertical-align: middle;
|
||||
position: relative;
|
||||
top: -1px;
|
||||
}
|
||||
}
|
||||
|
||||
.custom-tree-node{
|
||||
width: 100%;
|
||||
font-size: 14px;
|
||||
.node-operation{
|
||||
float: right;
|
||||
}
|
||||
i[class*="el-icon"] + i[class*="el-icon"]{
|
||||
margin-left: 6px;
|
||||
}
|
||||
.el-icon-plus{
|
||||
color: #00875A;
|
||||
}
|
||||
.el-icon-delete{
|
||||
color: #157a0c;
|
||||
}
|
||||
}
|
||||
|
||||
.left-scrollbar .el-scrollbar__view{
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.el-rate{
|
||||
display: inline-block;
|
||||
vertical-align: text-top;
|
||||
}
|
||||
.el-upload__tip{
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
$selectedColor: #f6f7ff;
|
||||
$lighterBlue: #00875A;
|
||||
|
||||
.container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.components-list {
|
||||
padding: 8px;
|
||||
box-sizing: border-box;
|
||||
height: 100%;
|
||||
.components-item {
|
||||
display: inline-block;
|
||||
width: 48%;
|
||||
margin: 1%;
|
||||
transition: transform 0ms !important;
|
||||
}
|
||||
}
|
||||
.components-draggable{
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
.components-title{
|
||||
font-size: 14px;
|
||||
color: #222;
|
||||
margin: 6px 2px;
|
||||
.svg-icon{
|
||||
color: #666;
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
.components-body {
|
||||
padding: 8px 10px;
|
||||
background: $selectedColor;
|
||||
font-size: 12px;
|
||||
cursor: move;
|
||||
border: 1px dashed $selectedColor;
|
||||
border-radius: 3px;
|
||||
.svg-icon{
|
||||
color: #777;
|
||||
font-size: 15px;
|
||||
}
|
||||
&:hover {
|
||||
border: 1px dashed #787be8;
|
||||
color: #787be8;
|
||||
.svg-icon {
|
||||
color: #787be8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.left-board {
|
||||
width: 260px;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 100vh;
|
||||
}
|
||||
.left-scrollbar{
|
||||
height: calc(100vh - 42px);
|
||||
overflow: hidden;
|
||||
}
|
||||
.center-scrollbar {
|
||||
height: calc(100vh - 42px);
|
||||
overflow: hidden;
|
||||
border-left: 1px solid #f1e8e8;
|
||||
border-right: 1px solid #f1e8e8;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.center-board {
|
||||
height: 100vh;
|
||||
width: auto;
|
||||
margin: 0 350px 0 260px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.empty-info{
|
||||
position: absolute;
|
||||
top: 46%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
text-align: center;
|
||||
font-size: 18px;
|
||||
color: #ccb1ea;
|
||||
letter-spacing: 4px;
|
||||
}
|
||||
.action-bar{
|
||||
position: relative;
|
||||
height: 42px;
|
||||
text-align: right;
|
||||
padding: 0 15px;
|
||||
box-sizing: border-box;;
|
||||
border: 1px solid #f1e8e8;
|
||||
border-top: none;
|
||||
border-left: none;
|
||||
.delete-btn{
|
||||
color: #F56C6C;
|
||||
}
|
||||
}
|
||||
.logo-wrapper{
|
||||
position: relative;
|
||||
height: 42px;
|
||||
background: #fff;
|
||||
border-bottom: 1px solid #f1e8e8;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.logo{
|
||||
position: absolute;
|
||||
left: 12px;
|
||||
top: 6px;
|
||||
line-height: 30px;
|
||||
color: #00afff;
|
||||
font-weight: 600;
|
||||
font-size: 17px;
|
||||
white-space: nowrap;
|
||||
> img{
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
vertical-align: top;
|
||||
}
|
||||
.github{
|
||||
display: inline-block;
|
||||
vertical-align: sub;
|
||||
margin-left: 15px;
|
||||
> img{
|
||||
height: 22px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.center-board-row {
|
||||
padding: 12px 12px 15px 12px;
|
||||
box-sizing: border-box;
|
||||
& > .el-form {
|
||||
// 69 = 12+15+42
|
||||
height: calc(100vh - 69px);
|
||||
}
|
||||
}
|
||||
.drawing-board {
|
||||
height: 100%;
|
||||
position: relative;
|
||||
.components-body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-size: 0;
|
||||
}
|
||||
.sortable-ghost {
|
||||
position: relative;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
&::before {
|
||||
content: " ";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
height: 3px;
|
||||
background: rgb(89, 89, 223);
|
||||
z-index: 2;
|
||||
}
|
||||
}
|
||||
.components-item.sortable-ghost {
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
background-color: $selectedColor;
|
||||
}
|
||||
.active-from-item {
|
||||
& > .el-form-item{
|
||||
background: $selectedColor;
|
||||
border-radius: 6px;
|
||||
}
|
||||
& > .drawing-item-copy, & > .drawing-item-delete{
|
||||
display: initial;
|
||||
}
|
||||
& > .component-name{
|
||||
color: $lighterBlue;
|
||||
}
|
||||
}
|
||||
.el-form-item{
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
}
|
||||
.drawing-item{
|
||||
position: relative;
|
||||
cursor: move;
|
||||
&.unfocus-bordered:not(.activeFromItem) > div:first-child {
|
||||
border: 1px dashed #ccc;
|
||||
}
|
||||
.el-form-item{
|
||||
padding: 12px 10px;
|
||||
}
|
||||
}
|
||||
.drawing-row-item{
|
||||
position: relative;
|
||||
cursor: move;
|
||||
box-sizing: border-box;
|
||||
border: 1px dashed #ccc;
|
||||
border-radius: 3px;
|
||||
padding: 0 2px;
|
||||
margin-bottom: 15px;
|
||||
.drawing-row-item {
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
.el-col{
|
||||
margin-top: 22px;
|
||||
}
|
||||
.el-form-item{
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.drag-wrapper{
|
||||
min-height: 80px;
|
||||
}
|
||||
&.active-from-item{
|
||||
border: 1px dashed $lighterBlue;
|
||||
}
|
||||
.component-name{
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
font-size: 12px;
|
||||
color: #bbb;
|
||||
display: inline-block;
|
||||
padding: 0 6px;
|
||||
}
|
||||
}
|
||||
.drawing-item, .drawing-row-item{
|
||||
&:hover {
|
||||
& > .el-form-item{
|
||||
background: $selectedColor;
|
||||
border-radius: 6px;
|
||||
}
|
||||
& > .drawing-item-copy, & > .drawing-item-delete{
|
||||
display: initial;
|
||||
}
|
||||
}
|
||||
& > .drawing-item-copy, & > .drawing-item-delete{
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: -10px;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
line-height: 22px;
|
||||
text-align: center;
|
||||
border-radius: 50%;
|
||||
font-size: 12px;
|
||||
border: 1px solid;
|
||||
cursor: pointer;
|
||||
z-index: 1;
|
||||
}
|
||||
& > .drawing-item-copy{
|
||||
right: 56px;
|
||||
border-color: $lighterBlue;
|
||||
color: $lighterBlue;
|
||||
background: #fff;
|
||||
&:hover{
|
||||
background: $lighterBlue;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
& > .drawing-item-delete{
|
||||
right: 24px;
|
||||
border-color: #F56C6C;
|
||||
color: #F56C6C;
|
||||
background: #fff;
|
||||
&:hover{
|
||||
background: #F56C6C;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,60 @@
|
||||
<template>
|
||||
<el-form ref="basicInfoForm" :model="info" :rules="rules" label-width="150px">
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="表名称" prop="tableName">
|
||||
<el-input placeholder="请输入仓库名称" v-model="info.tableName" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="表描述" prop="tableComment">
|
||||
<el-input placeholder="请输入" v-model="info.tableComment" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="实体类名称" prop="className">
|
||||
<el-input placeholder="请输入" v-model="info.className" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="作者" prop="functionAuthor">
|
||||
<el-input placeholder="请输入" v-model="info.functionAuthor" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input type="textarea" :rows="3" v-model="info.remark"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
info: {
|
||||
type: Object,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
rules: {
|
||||
tableName: [
|
||||
{ required: true, message: "请输入表名称", trigger: "blur" }
|
||||
],
|
||||
tableComment: [
|
||||
{ required: true, message: "请输入表描述", trigger: "blur" }
|
||||
],
|
||||
className: [
|
||||
{ required: true, message: "请输入实体类名称", trigger: "blur" }
|
||||
],
|
||||
functionAuthor: [
|
||||
{ required: true, message: "请输入作者", trigger: "blur" }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<!-- 创建表 -->
|
||||
<el-dialog title="创建表" :visible.sync="visible" width="800px" top="5vh" append-to-body>
|
||||
<span>创建表语句(支持多个建表语句):</span>
|
||||
<el-input type="textarea" :rows="10" placeholder="请输入文本" v-model="content"></el-input>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button type="primary" @click="handleCreateTable">确 定</el-button>
|
||||
<el-button @click="visible = false">取 消</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { createTable } from "@/api/tool/gen"
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
// 遮罩层
|
||||
visible: false,
|
||||
// 文本内容
|
||||
content: ""
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 显示弹框
|
||||
show() {
|
||||
this.visible = true
|
||||
},
|
||||
/** 创建按钮操作 */
|
||||
handleCreateTable() {
|
||||
if (this.content === "") {
|
||||
this.$modal.msgError("请输入建表语句")
|
||||
return
|
||||
}
|
||||
createTable({ sql: this.content, tplWebType: 'element-ui' }).then(res => {
|
||||
this.$modal.msgSuccess(res.msg)
|
||||
if (res.code === 200) {
|
||||
this.visible = false
|
||||
this.$emit("ok")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,230 @@
|
||||
<template>
|
||||
<el-card>
|
||||
<el-tabs v-model="activeName">
|
||||
<el-tab-pane label="基本信息" name="basic">
|
||||
<basic-info-form ref="basicInfo" :info="info" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="字段信息" name="columnInfo">
|
||||
<el-table ref="dragTable" :data="columns" row-key="columnId" :max-height="tableHeight">
|
||||
<el-table-column label="序号" type="index" min-width="5%" class-name="allowDrag"/>
|
||||
<el-table-column label="字段列名" prop="columnName" min-width="10%" :show-overflow-tooltip="true" class-name="allowDrag"/>
|
||||
<el-table-column label="字段描述" min-width="10%">
|
||||
<template slot-scope="scope">
|
||||
<el-input v-model="scope.row.columnComment"></el-input>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="物理类型"
|
||||
prop="columnType"
|
||||
min-width="10%"
|
||||
:show-overflow-tooltip="true"
|
||||
/>
|
||||
<el-table-column label="Java类型" min-width="11%">
|
||||
<template slot-scope="scope">
|
||||
<el-select v-model="scope.row.javaType">
|
||||
<el-option label="Long" value="Long" />
|
||||
<el-option label="String" value="String" />
|
||||
<el-option label="Integer" value="Integer" />
|
||||
<el-option label="Double" value="Double" />
|
||||
<el-option label="BigDecimal" value="BigDecimal" />
|
||||
<el-option label="Date" value="Date" />
|
||||
<el-option label="Boolean" value="Boolean" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="java属性" min-width="10%">
|
||||
<template slot-scope="scope">
|
||||
<el-input v-model="scope.row.javaField"></el-input>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="插入" min-width="5%">
|
||||
<template slot-scope="scope">
|
||||
<el-checkbox true-label="1" false-label="0" v-model="scope.row.isInsert"></el-checkbox>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="编辑" min-width="5%">
|
||||
<template slot-scope="scope">
|
||||
<el-checkbox true-label="1" false-label="0" v-model="scope.row.isEdit"></el-checkbox>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="列表" min-width="5%">
|
||||
<template slot-scope="scope">
|
||||
<el-checkbox true-label="1" false-label="0" v-model="scope.row.isList"></el-checkbox>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="查询" min-width="5%">
|
||||
<template slot-scope="scope">
|
||||
<el-checkbox true-label="1" false-label="0" v-model="scope.row.isQuery"></el-checkbox>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="查询方式" min-width="10%">
|
||||
<template slot-scope="scope">
|
||||
<el-select v-model="scope.row.queryType">
|
||||
<el-option label="=" value="EQ" />
|
||||
<el-option label="!=" value="NE" />
|
||||
<el-option label=">" value="GT" />
|
||||
<el-option label=">=" value="GTE" />
|
||||
<el-option label="<" value="LT" />
|
||||
<el-option label="<=" value="LTE" />
|
||||
<el-option label="LIKE" value="LIKE" />
|
||||
<el-option label="BETWEEN" value="BETWEEN" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="必填" min-width="5%">
|
||||
<template slot-scope="scope">
|
||||
<el-checkbox true-label="1" false-label="0" v-model="scope.row.isRequired"></el-checkbox>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="显示类型" min-width="12%">
|
||||
<template slot-scope="scope">
|
||||
<el-select v-model="scope.row.htmlType">
|
||||
<el-option label="文本框" value="input" />
|
||||
<el-option label="文本域" value="textarea" />
|
||||
<el-option label="下拉框" value="select" />
|
||||
<el-option label="单选框" value="radio" />
|
||||
<el-option label="复选框" value="checkbox" />
|
||||
<el-option label="日期控件" value="datetime" />
|
||||
<el-option label="图片上传" value="imageUpload" />
|
||||
<el-option label="文件上传" value="fileUpload" />
|
||||
<el-option label="富文本控件" value="editor" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="字典类型" min-width="12%">
|
||||
<template slot-scope="scope">
|
||||
<el-select v-model="scope.row.dictType" clearable filterable placeholder="请选择">
|
||||
<el-option
|
||||
v-for="dict in dictOptions"
|
||||
:key="dict.dictType"
|
||||
:label="dict.dictName"
|
||||
:value="dict.dictType">
|
||||
<span style="float: left">{{ dict.dictName }}</span>
|
||||
<span style="float: right; color: #8492a6; font-size: 13px">{{ dict.dictType }}</span>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="生成信息" name="genInfo">
|
||||
<gen-info-form ref="genInfo" :info="info" :tables="tables" :menus="menus"/>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
<el-form label-width="100px">
|
||||
<el-form-item style="text-align: center;margin-left:-100px;margin-top:10px;">
|
||||
<el-button type="primary" @click="submitForm()">提交</el-button>
|
||||
<el-button @click="close()">返回</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getGenTable, updateGenTable } from "@/api/tool/gen"
|
||||
import { optionselect as getDictOptionselect } from "@/api/system/dict/type"
|
||||
import { listMenu as getMenuTreeselect } from "@/api/system/menu"
|
||||
import basicInfoForm from "./basicInfoForm"
|
||||
import genInfoForm from "./genInfoForm"
|
||||
import Sortable from 'sortablejs'
|
||||
|
||||
export default {
|
||||
name: "GenEdit",
|
||||
components: {
|
||||
basicInfoForm,
|
||||
genInfoForm
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 选中选项卡的 name
|
||||
activeName: "columnInfo",
|
||||
// 表格的高度
|
||||
tableHeight: document.documentElement.scrollHeight - 245 + "px",
|
||||
// 表信息
|
||||
tables: [],
|
||||
// 表列信息
|
||||
columns: [],
|
||||
// 字典信息
|
||||
dictOptions: [],
|
||||
// 菜单信息
|
||||
menus: [],
|
||||
// 表详细信息
|
||||
info: {}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
const tableId = this.$route.params && this.$route.params.tableId
|
||||
if (tableId) {
|
||||
// 获取表详细信息
|
||||
getGenTable(tableId).then(res => {
|
||||
this.columns = res.data.rows
|
||||
this.info = res.data.info
|
||||
this.tables = res.data.tables
|
||||
})
|
||||
/** 查询字典下拉列表 */
|
||||
getDictOptionselect().then(response => {
|
||||
this.dictOptions = response.data
|
||||
})
|
||||
/** 查询菜单下拉列表 */
|
||||
getMenuTreeselect().then(response => {
|
||||
this.menus = this.handleTree(response.data, "menuId")
|
||||
})
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/** 提交按钮 */
|
||||
submitForm() {
|
||||
const basicForm = this.$refs.basicInfo.$refs.basicInfoForm
|
||||
const genForm = this.$refs.genInfo.$refs.genInfoForm
|
||||
Promise.all([basicForm, genForm].map(this.getFormPromise)).then(res => {
|
||||
const validateResult = res.every(item => !!item)
|
||||
if (validateResult) {
|
||||
const genTable = Object.assign({}, basicForm.model, genForm.model)
|
||||
genTable.columns = this.columns
|
||||
genTable.params = {
|
||||
genView: genTable.view ? '1' : '0',
|
||||
treeCode: genTable.treeCode,
|
||||
treeName: genTable.treeName,
|
||||
treeParentCode: genTable.treeParentCode,
|
||||
parentMenuId: genTable.parentMenuId
|
||||
}
|
||||
updateGenTable(genTable).then(res => {
|
||||
this.$modal.msgSuccess(res.msg)
|
||||
if (res.code === 200) {
|
||||
this.close()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
this.$modal.msgError("表单校验未通过,请重新检查提交内容")
|
||||
}
|
||||
})
|
||||
},
|
||||
getFormPromise(form) {
|
||||
return new Promise(resolve => {
|
||||
form.validate(res => {
|
||||
resolve(res)
|
||||
})
|
||||
})
|
||||
},
|
||||
/** 关闭按钮 */
|
||||
close() {
|
||||
const obj = { path: "/tool/gen", query: { t: Date.now(), pageNum: this.$route.query.pageNum } }
|
||||
this.$tab.closeOpenPage(obj)
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
const el = this.$refs.dragTable.$el.querySelectorAll(".el-table__body-wrapper > table > tbody")[0]
|
||||
const sortable = Sortable.create(el, {
|
||||
handle: ".allowDrag",
|
||||
onEnd: evt => {
|
||||
const targetRow = this.columns.splice(evt.oldIndex, 1)[0]
|
||||
this.columns.splice(evt.newIndex, 0, targetRow)
|
||||
for (let index in this.columns) {
|
||||
this.columns[index].sort = parseInt(index) + 1
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,336 @@
|
||||
<template>
|
||||
<el-form ref="genInfoForm" :model="info" :rules="rules" label-width="150px">
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item prop="tplCategory">
|
||||
<span slot="label">生成模板</span>
|
||||
<el-select v-model="info.tplCategory" @change="tplSelectChange">
|
||||
<el-option label="单表(增删改查)" value="crud" />
|
||||
<el-option label="树表(增删改查)" value="tree" />
|
||||
<el-option label="主子表(增删改查)" value="sub" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item prop="tplWebType">
|
||||
<span slot="label">前端类型</span>
|
||||
<el-select v-model="info.tplWebType">
|
||||
<el-option label="Vue2 Element UI 模版" value="element-ui" />
|
||||
<el-option label="Vue3 Element Plus 模版" value="element-plus" />
|
||||
<el-option label="Vue3 Element Plus TypeScript 模版" value="element-plus-typescript" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item prop="packageName">
|
||||
<span slot="label">
|
||||
生成包路径
|
||||
<el-tooltip content="生成在哪个java包下,例如 com.roomroot.system" placement="top">
|
||||
<i class="el-icon-question"></i>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
<el-input v-model="info.packageName" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12">
|
||||
<el-form-item prop="moduleName">
|
||||
<span slot="label">
|
||||
生成模块名
|
||||
<el-tooltip content="可理解为子系统名,例如 system" placement="top">
|
||||
<i class="el-icon-question"></i>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
<el-input v-model="info.moduleName" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12">
|
||||
<el-form-item prop="businessName">
|
||||
<span slot="label">
|
||||
生成业务名
|
||||
<el-tooltip content="可理解为功能英文名,例如 user" placement="top">
|
||||
<i class="el-icon-question"></i>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
<el-input v-model="info.businessName" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12">
|
||||
<el-form-item prop="functionName">
|
||||
<span slot="label">
|
||||
生成功能名
|
||||
<el-tooltip content="用作类描述,例如 用户" placement="top">
|
||||
<i class="el-icon-question"></i>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
<el-input v-model="info.functionName" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12">
|
||||
<el-form-item prop="formColNum">
|
||||
<span slot="label">
|
||||
表单布局
|
||||
<el-tooltip content="选择表单的栅格布局方式" placement="top">
|
||||
<i class="el-icon-question"></i>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
<el-select v-model="info.formColNum">
|
||||
<el-option label="单列" :value="1" />
|
||||
<el-option label="双列" :value="2" />
|
||||
<el-option label="三列" :value="3" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12">
|
||||
<el-form-item prop="genView">
|
||||
<span slot="label">扩展功能</span>
|
||||
<el-checkbox v-model="info.view">生成详情页</el-checkbox>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12">
|
||||
<el-form-item prop="genType">
|
||||
<span slot="label">
|
||||
生成代码方式
|
||||
<el-tooltip content="默认为zip压缩包下载,也可以自定义生成路径" placement="top">
|
||||
<i class="el-icon-question"></i>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
<el-radio v-model="info.genType" label="0">zip压缩包</el-radio>
|
||||
<el-radio v-model="info.genType" label="1">自定义路径</el-radio>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12">
|
||||
<el-form-item>
|
||||
<span slot="label">
|
||||
上级菜单
|
||||
<el-tooltip content="分配到指定菜单下,例如 系统管理" placement="top">
|
||||
<i class="el-icon-question"></i>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
<treeselect
|
||||
:append-to-body="true"
|
||||
v-model="info.parentMenuId"
|
||||
:options="menus"
|
||||
:normalizer="normalizer"
|
||||
:show-count="true"
|
||||
placeholder="请选择系统菜单"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="24" v-if="info.genType == '1'">
|
||||
<el-form-item prop="genPath">
|
||||
<span slot="label">
|
||||
自定义路径
|
||||
<el-tooltip content="填写磁盘绝对路径,若不填写,则生成到当前Web项目下" placement="top">
|
||||
<i class="el-icon-question"></i>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
<el-input v-model="info.genPath">
|
||||
<el-dropdown slot="append">
|
||||
<el-button type="primary">
|
||||
最近路径快速选择
|
||||
<i class="el-icon-arrow-down el-icon--right"></i>
|
||||
</el-button>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<el-dropdown-item @click.native="info.genPath = '/'">恢复默认的生成基础路径</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row v-show="info.tplCategory == 'tree'">
|
||||
<h4 class="form-header">其他信息</h4>
|
||||
<el-col :span="12">
|
||||
<el-form-item>
|
||||
<span slot="label">
|
||||
树编码字段
|
||||
<el-tooltip content="树显示的编码字段名, 如:dept_id" placement="top">
|
||||
<i class="el-icon-question"></i>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
<el-select v-model="info.treeCode" placeholder="请选择">
|
||||
<el-option
|
||||
v-for="(column, index) in info.columns"
|
||||
:key="index"
|
||||
:label="column.columnName + ':' + column.columnComment"
|
||||
:value="column.columnName"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item>
|
||||
<span slot="label">
|
||||
树父编码字段
|
||||
<el-tooltip content="树显示的父编码字段名, 如:parent_Id" placement="top">
|
||||
<i class="el-icon-question"></i>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
<el-select v-model="info.treeParentCode" placeholder="请选择">
|
||||
<el-option
|
||||
v-for="(column, index) in info.columns"
|
||||
:key="index"
|
||||
:label="column.columnName + ':' + column.columnComment"
|
||||
:value="column.columnName"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item>
|
||||
<span slot="label">
|
||||
树名称字段
|
||||
<el-tooltip content="树节点的显示名称字段名, 如:dept_name" placement="top">
|
||||
<i class="el-icon-question"></i>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
<el-select v-model="info.treeName" placeholder="请选择">
|
||||
<el-option
|
||||
v-for="(column, index) in info.columns"
|
||||
:key="index"
|
||||
:label="column.columnName + ':' + column.columnComment"
|
||||
:value="column.columnName"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row v-show="info.tplCategory == 'sub'">
|
||||
<h4 class="form-header">关联信息</h4>
|
||||
<el-col :span="12">
|
||||
<el-form-item>
|
||||
<span slot="label">
|
||||
关联子表的表名
|
||||
<el-tooltip content="关联子表的表名, 如:sys_user" placement="top">
|
||||
<i class="el-icon-question"></i>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
<el-select v-model="info.subTableName" placeholder="请选择" @change="subSelectChange">
|
||||
<el-option
|
||||
v-for="(table, index) in tables"
|
||||
:key="index"
|
||||
:label="table.tableName + ':' + table.tableComment"
|
||||
:value="table.tableName"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item>
|
||||
<span slot="label">
|
||||
子表关联的外键名
|
||||
<el-tooltip content="子表关联的外键名, 如:user_id" placement="top">
|
||||
<i class="el-icon-question"></i>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
<el-select v-model="info.subTableFkName" placeholder="请选择">
|
||||
<el-option
|
||||
v-for="(column, index) in subColumns"
|
||||
:key="index"
|
||||
:label="column.columnName + ':' + column.columnComment"
|
||||
:value="column.columnName"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Treeselect from "@riophae/vue-treeselect"
|
||||
import "@riophae/vue-treeselect/dist/vue-treeselect.css"
|
||||
|
||||
export default {
|
||||
components: { Treeselect },
|
||||
props: {
|
||||
info: {
|
||||
type: Object,
|
||||
default: null
|
||||
},
|
||||
tables: {
|
||||
type: Array,
|
||||
default: null
|
||||
},
|
||||
menus: {
|
||||
type: Array,
|
||||
default: []
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
subColumns: [],
|
||||
rules: {
|
||||
tplCategory: [
|
||||
{ required: true, message: "请选择生成模板", trigger: "blur" }
|
||||
],
|
||||
packageName: [
|
||||
{ required: true, message: "请输入生成包路径", trigger: "blur" }
|
||||
],
|
||||
moduleName: [
|
||||
{ required: true, message: "请输入生成模块名", trigger: "blur" }
|
||||
],
|
||||
businessName: [
|
||||
{ required: true, message: "请输入生成业务名", trigger: "blur" }
|
||||
],
|
||||
functionName: [
|
||||
{ required: true, message: "请输入生成功能名", trigger: "blur" }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'info.subTableName': function(val) {
|
||||
this.setSubTableColumns(val)
|
||||
},
|
||||
'info.tplWebType': function(val) {
|
||||
if (val === '') {
|
||||
this.info.tplWebType = "element-ui"
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/** 转换菜单数据结构 */
|
||||
normalizer(node) {
|
||||
if (node.children && !node.children.length) {
|
||||
delete node.children
|
||||
}
|
||||
return {
|
||||
id: node.menuId,
|
||||
label: node.menuName,
|
||||
children: node.children
|
||||
}
|
||||
},
|
||||
/** 选择子表名触发 */
|
||||
subSelectChange(value) {
|
||||
this.info.subTableFkName = ''
|
||||
},
|
||||
/** 选择生成模板触发 */
|
||||
tplSelectChange(value) {
|
||||
if (value !== 'sub') {
|
||||
this.info.subTableName = ''
|
||||
this.info.subTableFkName = ''
|
||||
}
|
||||
},
|
||||
/** 设置关联外键 */
|
||||
setSubTableColumns(value) {
|
||||
for (var item in this.tables) {
|
||||
const name = this.tables[item].tableName
|
||||
if (value === name) {
|
||||
this.subColumns = this.tables[item].columns
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,120 @@
|
||||
<template>
|
||||
<!-- 导入表 -->
|
||||
<el-dialog title="导入表" :visible.sync="visible" width="800px" top="5vh" append-to-body>
|
||||
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true">
|
||||
<el-form-item label="表名称" prop="tableName">
|
||||
<el-input
|
||||
v-model="queryParams.tableName"
|
||||
placeholder="请输入表名称"
|
||||
clearable
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="表描述" prop="tableComment">
|
||||
<el-input
|
||||
v-model="queryParams.tableComment"
|
||||
placeholder="请输入表描述"
|
||||
clearable
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-row>
|
||||
<el-table @row-click="clickRow" ref="table" :data="dbTableList" @selection-change="handleSelectionChange" height="260px">
|
||||
<el-table-column type="selection" width="55"></el-table-column>
|
||||
<el-table-column prop="tableName" label="表名称" :show-overflow-tooltip="true"></el-table-column>
|
||||
<el-table-column prop="tableComment" label="表描述" :show-overflow-tooltip="true"></el-table-column>
|
||||
<el-table-column prop="createTime" label="创建时间"></el-table-column>
|
||||
<el-table-column prop="updateTime" label="更新时间"></el-table-column>
|
||||
</el-table>
|
||||
<pagination
|
||||
v-show="total>0"
|
||||
:total="total"
|
||||
:page.sync="queryParams.pageNum"
|
||||
:limit.sync="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</el-row>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button type="primary" @click="handleImportTable">确 定</el-button>
|
||||
<el-button @click="visible = false">取 消</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listDbTable, importTable } from "@/api/tool/gen"
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
// 遮罩层
|
||||
visible: false,
|
||||
// 选中数组值
|
||||
tables: [],
|
||||
// 总条数
|
||||
total: 0,
|
||||
// 表数据
|
||||
dbTableList: [],
|
||||
// 查询参数
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
tableName: undefined,
|
||||
tableComment: undefined
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 显示弹框
|
||||
show() {
|
||||
this.getList()
|
||||
this.visible = true
|
||||
},
|
||||
clickRow(row) {
|
||||
this.$refs.table.toggleRowSelection(row)
|
||||
},
|
||||
// 多选框选中数据
|
||||
handleSelectionChange(selection) {
|
||||
this.tables = selection.map(item => item.tableName)
|
||||
},
|
||||
// 查询表数据
|
||||
getList() {
|
||||
listDbTable(this.queryParams).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.dbTableList = res.rows
|
||||
this.total = res.total
|
||||
}
|
||||
})
|
||||
},
|
||||
/** 搜索按钮操作 */
|
||||
handleQuery() {
|
||||
this.queryParams.pageNum = 1
|
||||
this.getList()
|
||||
},
|
||||
/** 重置按钮操作 */
|
||||
resetQuery() {
|
||||
this.resetForm("queryForm")
|
||||
this.handleQuery()
|
||||
},
|
||||
/** 导入按钮操作 */
|
||||
handleImportTable() {
|
||||
const tableNames = this.tables.join(",")
|
||||
if (tableNames == "") {
|
||||
this.$modal.msgError("请选择要导入的表")
|
||||
return
|
||||
}
|
||||
importTable({ tables: tableNames, tplWebType: 'element-ui' }).then(res => {
|
||||
this.$modal.msgSuccess(res.msg)
|
||||
if (res.code === 200) {
|
||||
this.visible = false
|
||||
this.$emit("ok")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,386 @@
|
||||
<template>
|
||||
<div class="app-container tool-gen">
|
||||
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
|
||||
<el-form-item label="表名称" prop="tableName">
|
||||
<el-input
|
||||
v-model="queryParams.tableName"
|
||||
placeholder="请输入表名称"
|
||||
clearable
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="表描述" prop="tableComment">
|
||||
<el-input
|
||||
v-model="queryParams.tableComment"
|
||||
placeholder="请输入表描述"
|
||||
clearable
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="创建时间">
|
||||
<el-date-picker
|
||||
v-model="dateRange"
|
||||
style="width: 240px"
|
||||
value-format="yyyy-MM-dd"
|
||||
type="daterange"
|
||||
range-separator="-"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
></el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
icon="el-icon-download"
|
||||
size="mini"
|
||||
:disabled="multiple"
|
||||
@click="handleGenTable"
|
||||
v-hasPermi="['tool:gen:code']"
|
||||
>生成</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
icon="el-icon-plus"
|
||||
size="mini"
|
||||
@click="openCreateTable"
|
||||
v-hasRole="['admin']"
|
||||
>创建</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="info"
|
||||
plain
|
||||
icon="el-icon-upload"
|
||||
size="mini"
|
||||
@click="openImportTable"
|
||||
v-hasPermi="['tool:gen:import']"
|
||||
>导入</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
icon="el-icon-edit"
|
||||
size="mini"
|
||||
:disabled="single"
|
||||
@click="handleEditTable"
|
||||
v-hasPermi="['tool:gen:edit']"
|
||||
>修改</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="danger"
|
||||
plain
|
||||
icon="el-icon-delete"
|
||||
size="mini"
|
||||
:disabled="multiple"
|
||||
@click="handleDelete"
|
||||
v-hasPermi="['tool:gen:remove']"
|
||||
>删除</el-button>
|
||||
</el-col>
|
||||
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
|
||||
<el-table ref="tables" v-loading="loading" :data="tableList" @selection-change="handleSelectionChange" :default-sort="defaultSort" @sort-change="handleSortChange" :height="tableHeight">
|
||||
<el-table-column type="selection" align="center" width="55"></el-table-column>
|
||||
<el-table-column label="序号" type="index" width="50" align="center">
|
||||
<template slot-scope="scope">
|
||||
<span>{{(queryParams.pageNum - 1) * queryParams.pageSize + scope.$index + 1}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="表名称" align="center" prop="tableName" :show-overflow-tooltip="true" width="140" />
|
||||
<el-table-column label="表描述" align="center" prop="tableComment" :show-overflow-tooltip="true" width="140" />
|
||||
<el-table-column label="实体" align="center" prop="className" :show-overflow-tooltip="true" width="140" />
|
||||
<el-table-column label="创建时间" align="center" prop="createTime" sortable="custom" :sort-orders="['descending', 'ascending']" width="160" />
|
||||
<el-table-column label="更新时间" align="center" prop="updateTime" sortable="custom" :sort-orders="['descending', 'ascending']" width="160" />
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
|
||||
<template slot-scope="scope">
|
||||
<el-button
|
||||
type="text"
|
||||
size="small"
|
||||
icon="el-icon-view"
|
||||
@click="handlePreview(scope.row)"
|
||||
v-hasPermi="['tool:gen:preview']"
|
||||
>预览</el-button>
|
||||
<el-button
|
||||
type="text"
|
||||
size="small"
|
||||
icon="el-icon-edit"
|
||||
@click="handleEditTable(scope.row)"
|
||||
v-hasPermi="['tool:gen:edit']"
|
||||
>编辑</el-button>
|
||||
<el-button
|
||||
type="text"
|
||||
size="small"
|
||||
icon="el-icon-delete"
|
||||
@click="handleDelete(scope.row)"
|
||||
v-hasPermi="['tool:gen:remove']"
|
||||
>删除</el-button>
|
||||
<el-button
|
||||
type="text"
|
||||
size="small"
|
||||
icon="el-icon-refresh"
|
||||
@click="handleSynchDb(scope.row)"
|
||||
v-hasPermi="['tool:gen:edit']"
|
||||
>同步</el-button>
|
||||
<el-button
|
||||
type="text"
|
||||
size="small"
|
||||
icon="el-icon-download"
|
||||
@click="handleGenTable(scope.row)"
|
||||
v-hasPermi="['tool:gen:code']"
|
||||
>生成代码</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<pagination
|
||||
v-show="total>0"
|
||||
:total="total"
|
||||
:page.sync="queryParams.pageNum"
|
||||
:limit.sync="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
<!-- 预览界面 -->
|
||||
<el-dialog :title="preview.title" :visible.sync="preview.open" width="80%" top="5vh" append-to-body class="scrollbar">
|
||||
<el-tabs v-model="preview.activeName">
|
||||
<el-tab-pane
|
||||
v-for="(value, key) in preview.data"
|
||||
:label="key.substring(key.lastIndexOf('/')+1,key.indexOf('.vm'))"
|
||||
:name="key.substring(key.lastIndexOf('/')+1,key.indexOf('.vm'))"
|
||||
:key="key"
|
||||
>
|
||||
<el-link :underline="false" icon="el-icon-document-copy" v-clipboard:copy="value" v-clipboard:success="clipboardSuccess" style="float:right">复制</el-link>
|
||||
<pre><code class="hljs" v-html="highlightedCode(value, key)"></code></pre>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-dialog>
|
||||
<import-table ref="import" @ok="handleQuery" />
|
||||
<create-table ref="create" @ok="handleQuery" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listTable, previewTable, delTable, genCode, synchDb } from "@/api/tool/gen"
|
||||
import importTable from "./importTable"
|
||||
import createTable from "./createTable"
|
||||
import hljs from "highlight.js/lib/highlight"
|
||||
import "highlight.js/styles/github-gist.css"
|
||||
hljs.registerLanguage("java", require("highlight.js/lib/languages/java"))
|
||||
hljs.registerLanguage("xml", require("highlight.js/lib/languages/xml"))
|
||||
hljs.registerLanguage("html", require("highlight.js/lib/languages/xml"))
|
||||
hljs.registerLanguage("vue", require("highlight.js/lib/languages/xml"))
|
||||
hljs.registerLanguage("javascript", require("highlight.js/lib/languages/javascript"))
|
||||
hljs.registerLanguage("typescript", require("highlight.js/lib/languages/typescript"))
|
||||
hljs.registerLanguage("sql", require("highlight.js/lib/languages/sql"))
|
||||
|
||||
export default {
|
||||
name: "Gen",
|
||||
components: { importTable, createTable },
|
||||
data() {
|
||||
return {
|
||||
// 遮罩层
|
||||
loading: true,
|
||||
// 唯一标识符
|
||||
uniqueId: "",
|
||||
// 选中数组
|
||||
ids: [],
|
||||
// 选中表数组
|
||||
tableNames: [],
|
||||
// 非单个禁用
|
||||
single: true,
|
||||
// 非多个禁用
|
||||
multiple: true,
|
||||
// 显示搜索条件
|
||||
showSearch: true,
|
||||
// 总条数
|
||||
total: 0,
|
||||
// 表数据
|
||||
tableList: [],
|
||||
// 日期范围
|
||||
dateRange: "",
|
||||
// 默认排序
|
||||
defaultSort: { prop: "createTime", order: "descending" },
|
||||
// 表格高度
|
||||
tableHeight: window.innerHeight - 240,
|
||||
// 查询参数
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
tableName: undefined,
|
||||
tableComment: undefined
|
||||
},
|
||||
// 预览参数
|
||||
preview: {
|
||||
open: false,
|
||||
title: "代码预览",
|
||||
data: {},
|
||||
activeName: "domain.java"
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.queryParams.orderByColumn = this.defaultSort.prop
|
||||
this.queryParams.isAsc = this.defaultSort.order
|
||||
this.getList()
|
||||
},
|
||||
activated() {
|
||||
const time = this.$route.query.t
|
||||
if (time != null && time != this.uniqueId) {
|
||||
this.uniqueId = time
|
||||
this.queryParams.pageNum = Number(this.$route.query.pageNum)
|
||||
this.getList()
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.getTableHeight()
|
||||
window.addEventListener('resize', this.getTableHeight)
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.removeEventListener('resize', this.getTableHeight)
|
||||
},
|
||||
watch: {
|
||||
showSearch() {
|
||||
this.$nextTick(() => {
|
||||
this.getTableHeight()
|
||||
})
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getTableHeight() {
|
||||
const offset = this.showSearch ? 240 : 190
|
||||
this.tableHeight = window.innerHeight - offset
|
||||
},
|
||||
/** 查询表集合 */
|
||||
getList() {
|
||||
this.loading = true
|
||||
listTable(this.addDateRange(this.queryParams, this.dateRange)).then(response => {
|
||||
this.tableList = response.rows
|
||||
this.total = response.total
|
||||
this.loading = false
|
||||
}
|
||||
)
|
||||
},
|
||||
/** 搜索按钮操作 */
|
||||
handleQuery() {
|
||||
this.queryParams.pageNum = 1
|
||||
this.getList()
|
||||
},
|
||||
/** 生成代码操作 */
|
||||
handleGenTable(row) {
|
||||
const tableNames = row.tableName || this.tableNames
|
||||
if (tableNames == "") {
|
||||
this.$modal.msgError("请选择要生成的数据")
|
||||
return
|
||||
}
|
||||
if (row.genType === "1") {
|
||||
genCode(row.tableName).then(() => {
|
||||
this.$modal.msgSuccess("成功生成到自定义路径:" + row.genPath)
|
||||
})
|
||||
} else {
|
||||
const zipName = Array.isArray(tableNames) ? "roomroot.zip" : tableNames + ".zip"
|
||||
this.$download.zip("/tool/gen/batchGenCode?tables=" + tableNames, zipName)
|
||||
}
|
||||
},
|
||||
/** 同步数据库操作 */
|
||||
handleSynchDb(row) {
|
||||
const tableName = row.tableName
|
||||
this.$modal.confirm('确认要强制同步"' + tableName + '"表结构吗?').then(function() {
|
||||
return synchDb(tableName)
|
||||
}).then(() => {
|
||||
this.$modal.msgSuccess("同步成功")
|
||||
}).catch(() => {})
|
||||
},
|
||||
/** 打开导入表弹窗 */
|
||||
openImportTable() {
|
||||
this.$refs.import.show()
|
||||
},
|
||||
/** 打开创建表弹窗 */
|
||||
openCreateTable() {
|
||||
this.$refs.create.show()
|
||||
},
|
||||
/** 重置按钮操作 */
|
||||
resetQuery() {
|
||||
this.dateRange = []
|
||||
this.resetForm("queryForm")
|
||||
this.queryParams.pageNum = 1
|
||||
this.$refs.tables.sort(this.defaultSort.prop, this.defaultSort.order)
|
||||
},
|
||||
/** 预览按钮 */
|
||||
handlePreview(row) {
|
||||
previewTable(row.tableId).then(response => {
|
||||
this.preview.data = response.data
|
||||
this.preview.open = true
|
||||
this.preview.activeName = "domain.java"
|
||||
})
|
||||
},
|
||||
/** 高亮显示 */
|
||||
highlightedCode(code, key) {
|
||||
const vmName = key.substring(key.lastIndexOf("/") + 1, key.indexOf(".vm"))
|
||||
var language = vmName.substring(vmName.indexOf(".") + 1, vmName.length)
|
||||
const result = hljs.highlight(language, code || "", true)
|
||||
return result.value || ' '
|
||||
},
|
||||
/** 复制代码成功 */
|
||||
clipboardSuccess() {
|
||||
this.$modal.msgSuccess("复制成功")
|
||||
},
|
||||
// 多选框选中数据
|
||||
handleSelectionChange(selection) {
|
||||
this.ids = selection.map(item => item.tableId)
|
||||
this.tableNames = selection.map(item => item.tableName)
|
||||
this.single = selection.length != 1
|
||||
this.multiple = !selection.length
|
||||
},
|
||||
/** 排序触发事件 */
|
||||
handleSortChange(column, prop, order) {
|
||||
this.queryParams.orderByColumn = column.prop
|
||||
this.queryParams.isAsc = column.order
|
||||
this.getList()
|
||||
},
|
||||
/** 修改按钮操作 */
|
||||
handleEditTable(row) {
|
||||
const tableId = row.tableId || this.ids[0]
|
||||
const tableName = row.tableName || this.tableNames[0]
|
||||
const params = { pageNum: this.queryParams.pageNum }
|
||||
this.$tab.openPage("修改[" + tableName + "]生成配置", '/tool/gen-edit/index/' + tableId, params)
|
||||
},
|
||||
/** 删除按钮操作 */
|
||||
handleDelete(row) {
|
||||
const tableIds = row.tableId || this.ids
|
||||
this.$modal.confirm('是否确认删除表编号为"' + tableIds + '"的数据项?').then(function() {
|
||||
return delTable(tableIds)
|
||||
}).then(() => {
|
||||
this.getList()
|
||||
this.$modal.msgSuccess("删除成功")
|
||||
}).catch(() => {})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tool-gen ::v-deep .el-table ::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
.tool-gen ::v-deep .el-table {
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
.tool-gen ::v-deep .el-table__body-wrapper::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
.tool-gen ::v-deep .el-table__body-wrapper {
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,15 @@
|
||||
<template>
|
||||
<i-frame :src="url" />
|
||||
</template>
|
||||
<script>
|
||||
import iFrame from "@/components/iFrame/index"
|
||||
export default {
|
||||
name: "Swagger",
|
||||
components: { iFrame },
|
||||
data() {
|
||||
return {
|
||||
url: process.env.VUE_APP_BASE_API + "/swagger-ui/index.html"
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user