|
|
@@ -3,35 +3,48 @@
|
|
|
<!-- 搜索栏 -->
|
|
|
<div class="search-bar">
|
|
|
<a-input
|
|
|
- v-model:value="searchForm.keyword"
|
|
|
+ v-model:value="searchForm.queryInfo"
|
|
|
placeholder="姓名/学号/手机"
|
|
|
style="width: 180px; margin-right: 12px"
|
|
|
/>
|
|
|
- <a-select v-model:value="searchForm.type" placeholder="选择学生类型" style="width: 150px; margin-right: 12px">
|
|
|
- <a-select-option value="">全部类型</a-select-option>
|
|
|
- <a-select-option value="普通">普通</a-select-option>
|
|
|
- <a-select-option value="VIP">VIP</a-select-option>
|
|
|
+ <a-select v-model:value="searchForm.gender" placeholder="选择学生性别" style="width: 150px; margin-right: 12px">
|
|
|
+ <a-select-option value="男">男</a-select-option>
|
|
|
+ <a-select-option value="女">女</a-select-option>
|
|
|
</a-select>
|
|
|
- <a-select v-model:value="searchForm.status" placeholder="状态选项" style="width: 120px; margin-right: 12px">
|
|
|
- <a-select-option value="">全部状态</a-select-option>
|
|
|
- <a-select-option value="启用">启用</a-select-option>
|
|
|
- <a-select-option value="禁用">禁用</a-select-option>
|
|
|
+ <a-select v-model:value="searchForm.userStatus" placeholder="请选择状态" style="width: 120px; margin-right: 12px">
|
|
|
+ <a-select-option v-for="(item, index) in statusOptions" :key="index" :value="item.value">{{
|
|
|
+ item.label
|
|
|
+ }}</a-select-option>
|
|
|
</a-select>
|
|
|
- <a-range-picker v-model:value="searchForm.date" style="margin-right: 12px; width: 240px" />
|
|
|
+ <a-range-picker
|
|
|
+ @change="handleDateChange"
|
|
|
+ v-model:value="searchForm.date"
|
|
|
+ style="margin-right: 12px; width: 240px"
|
|
|
+ />
|
|
|
<a-button type="primary" @click="onSearch" style="margin-right: 8px">查询</a-button>
|
|
|
<a-button @click="onReset">重置</a-button>
|
|
|
</div>
|
|
|
<!-- 操作按钮 -->
|
|
|
<div class="action-bar">
|
|
|
<a-button type="primary" @click="onAddStudent" style="margin-right: 8px">+ 新增学员</a-button>
|
|
|
- <a-button>导入导出</a-button>
|
|
|
+ <a-upload
|
|
|
+ name="file"
|
|
|
+ :showUploadList="false"
|
|
|
+ :beforeUpload="beforeUpload"
|
|
|
+ :customRequest="customRequest"
|
|
|
+ accept=".xlsx,.xls"
|
|
|
+ >
|
|
|
+ <a-button>导入</a-button>
|
|
|
+ </a-upload>
|
|
|
</div>
|
|
|
<!-- 表格 -->
|
|
|
<a-table
|
|
|
:columns="columns"
|
|
|
- :data-source="pagedData"
|
|
|
+ :row-key="rowKey"
|
|
|
+ :data-source="pagedDatas"
|
|
|
:pagination="false"
|
|
|
:row-selection="rowSelection"
|
|
|
+ :scroll="{ x: 1000, y: 320 }"
|
|
|
bordered
|
|
|
class="student-table"
|
|
|
>
|
|
|
@@ -51,8 +64,8 @@
|
|
|
<template v-else-if="column.dataIndex === 'actions'">
|
|
|
<a-space>
|
|
|
<a-button size="small" @click="onDetail(record)">详情</a-button>
|
|
|
- <a-button size="small" @click="onEdit(record)">编辑</a-button>
|
|
|
- <a-button size="small" @click="onSetting(record)">设置</a-button>
|
|
|
+ <!-- <a-button size="small" @click="onEdit(record)">编辑</a-button> -->
|
|
|
+ <!-- <a-button size="small" @click="onSetting(record)">设置</a-button> -->
|
|
|
<a-button size="small" danger @click="onDelete(record)">删除</a-button>
|
|
|
</a-space>
|
|
|
</template>
|
|
|
@@ -77,7 +90,7 @@
|
|
|
<a-pagination
|
|
|
:current="currentPage"
|
|
|
:page-size="pageSize"
|
|
|
- :total="filteredData.length"
|
|
|
+ :total="total"
|
|
|
@change="onPageChange"
|
|
|
show-quick-jumper
|
|
|
:show-total="(total) => `共${total}条`"
|
|
|
@@ -97,53 +110,11 @@
|
|
|
:footer="null"
|
|
|
@cancel="handleAddStudentCancel"
|
|
|
>
|
|
|
- <div class="add-student-modal">
|
|
|
- <!-- 左侧部门成员树 -->
|
|
|
- <div class="left-panel">
|
|
|
- <a-input
|
|
|
- v-model:value="searchDeptMember"
|
|
|
- placeholder="输入部门或成员名称"
|
|
|
- allow-clear
|
|
|
- class="search-input"
|
|
|
- @input="onDeptMemberSearch"
|
|
|
- />
|
|
|
- <div class="dept-list">
|
|
|
- <template v-for="dept in filteredDepartments" :key="dept.id">
|
|
|
- <div class="dept-name" @click="toggleDept(dept.id)">
|
|
|
- <DownOutlined v-if="expandedDeptIds.includes(dept.id)" style="margin-right: 4px" />
|
|
|
- <RightOutlined v-else style="margin-right: 4px" />
|
|
|
- {{ dept.name }}
|
|
|
- </div>
|
|
|
- <div v-if="expandedDeptIds.includes(dept.id)" class="member-list">
|
|
|
- <a-checkbox-group :value="checkedMemberIds" @change="onMemberCheckChange">
|
|
|
- <div v-for="member in dept.members" :key="member.id" class="member-item">
|
|
|
- <a-checkbox :value="member.id" :disabled="isCheckedLimit(member.id)">
|
|
|
- <UserOutlined style="margin-right: 4px" />{{ member.name }}
|
|
|
- </a-checkbox>
|
|
|
- </div>
|
|
|
- </a-checkbox-group>
|
|
|
- </div>
|
|
|
- </template>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <!-- 右侧已选成员列表 -->
|
|
|
- <div class="right-panel">
|
|
|
- <div class="selected-header">
|
|
|
- 已选 {{ selectedMembers.length }}/30
|
|
|
- <a @click="clearSelected" style="float: right; color: #1890ff">清空</a>
|
|
|
- </div>
|
|
|
- <div class="selected-list">
|
|
|
- <div v-for="member in selectedMembers" :key="member.id" class="selected-item">
|
|
|
- <UserOutlined style="margin-right: 4px" />{{ member.name }}
|
|
|
- <CloseOutlined class="remove-icon" @click="removeSelected(member.id)" />
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <div class="modal-footer">
|
|
|
- <a-button @click="handleAddStudentCancel">取消</a-button>
|
|
|
- <a-button type="primary" @click="handleAddStudentOk">确定</a-button>
|
|
|
- </div>
|
|
|
+ <studentSelection
|
|
|
+ :studentIds="studentIdss"
|
|
|
+ @close="handleAddStudentCancel"
|
|
|
+ @confirm="confirmStudent"
|
|
|
+ ></studentSelection>
|
|
|
</a-modal>
|
|
|
<!-- 详情弹窗 -->
|
|
|
<a-modal
|
|
|
@@ -210,11 +181,13 @@
|
|
|
</template>
|
|
|
|
|
|
<script setup>
|
|
|
- import { ref, computed, onMounted } from 'vue'
|
|
|
+ import { ref, reactive, computed, onMounted, unref} from 'vue'
|
|
|
import { DownOutlined } from '@ant-design/icons-vue'
|
|
|
import { getDepartmentMembers } from '@/api/course/courseDetail'
|
|
|
import studentDetailsApi from '@/api/courseCenter/studentDetails.js'
|
|
|
import { getStudentDetail } from '@/api/course/courseDetail'
|
|
|
+ import studentSelection from './studentSelection.vue'
|
|
|
+ import tool from '@/utils/tool'
|
|
|
|
|
|
// mock数据
|
|
|
const allStudents = ref([
|
|
|
@@ -224,33 +197,37 @@
|
|
|
studentNo: 'xy' + String(100000 + i),
|
|
|
name: '张小刚',
|
|
|
gender: i % 2 === 0 ? '男' : '女',
|
|
|
- status: '启用',
|
|
|
+ userStatus: '启用',
|
|
|
phone: '18088889999',
|
|
|
online: true,
|
|
|
lastLogin: '2020-11-25 23:26:08'
|
|
|
}))
|
|
|
])
|
|
|
|
|
|
- const searchForm = ref({
|
|
|
- keyword: '',
|
|
|
- type: '',
|
|
|
- status: '',
|
|
|
- date: []
|
|
|
+ const searchForm = reactive({
|
|
|
+ queryInfo: null,
|
|
|
+ gender: null,
|
|
|
+ userStatus: null,
|
|
|
+ date: [],
|
|
|
+ startTime: null, // 新增开始时间字段
|
|
|
+ endTime: null // 新增结束时间字段
|
|
|
})
|
|
|
|
|
|
const columns = [
|
|
|
- { title: '学号', dataIndex: 'studentNo', align: 'center' },
|
|
|
- { title: '姓名', dataIndex: 'name', align: 'center' },
|
|
|
+ { title: '学员编号', dataIndex: 'phone', align: 'center' },
|
|
|
+ { title: '姓名', dataIndex: 'userIdName', align: 'center' },
|
|
|
{ title: '性别', dataIndex: 'gender', align: 'center' },
|
|
|
- { title: '账号状态', dataIndex: 'status', align: 'center' },
|
|
|
- { title: '手机', dataIndex: 'phone', align: 'center' },
|
|
|
- { title: '在线状态', dataIndex: 'online', align: 'center' },
|
|
|
- { title: '最后登录', dataIndex: 'lastLogin', align: 'center' },
|
|
|
- { title: '操作', dataIndex: 'actions', align: 'center', width: 220 }
|
|
|
+ { title: '账号状态', dataIndex: 'userStatusName', align: 'center' },
|
|
|
+ { title: '手机号', dataIndex: 'phone', align: 'center' },
|
|
|
+ { title: '在线状态', dataIndex: 'isLoginName', align: 'center' },
|
|
|
+ { title: '最后登录', dataIndex: 'latestLoginTime', align: 'center' },
|
|
|
+ { title: '操作', dataIndex: 'actions', fixed: 'right', align: 'center', width: 220 }
|
|
|
]
|
|
|
-
|
|
|
+ const studentIdss = ref([]) //学员回显id
|
|
|
+ const statusOptions = tool.dictList('COMMON_STATUS') //学员状态
|
|
|
const currentPage = ref(1)
|
|
|
const pageSize = ref(10)
|
|
|
+ const total = ref(0)
|
|
|
const selectedRowKeys = ref([])
|
|
|
|
|
|
const addStudentVisible = ref(false)
|
|
|
@@ -262,19 +239,21 @@
|
|
|
|
|
|
const detailVisible = ref(false)
|
|
|
const detailData = ref({})
|
|
|
+ const pagedDatas = ref([])
|
|
|
+ const rowKey = (record) => record.userId
|
|
|
|
|
|
const filteredData = computed(() => {
|
|
|
let data = allStudents.value
|
|
|
- if (searchForm.value.keyword) {
|
|
|
+ if (searchForm.queryInfo) {
|
|
|
data = data.filter(
|
|
|
(item) =>
|
|
|
- item.name.includes(searchForm.value.keyword) ||
|
|
|
- item.studentNo.includes(searchForm.value.keyword) ||
|
|
|
- item.phone.includes(searchForm.value.keyword)
|
|
|
+ item.name.includes(searchForm.queryInfo) ||
|
|
|
+ item.studentNo.includes(searchForm.queryInfo) ||
|
|
|
+ item.phone.includes(searchForm.queryInfo)
|
|
|
)
|
|
|
}
|
|
|
- if (searchForm.value.status) {
|
|
|
- data = data.filter((item) => item.status === searchForm.value.status)
|
|
|
+ if (searchForm.status) {
|
|
|
+ data = data.filter((item) => item.status === searchForm.status)
|
|
|
}
|
|
|
// 这里可以加更多筛选条件
|
|
|
return data
|
|
|
@@ -285,18 +264,47 @@
|
|
|
return filteredData.value.slice(start, start + pageSize.value)
|
|
|
})
|
|
|
|
|
|
- const rowSelection = {
|
|
|
- selectedRowKeys: selectedRowKeys.value,
|
|
|
- onChange: (keys) => {
|
|
|
- selectedRowKeys.value = keys
|
|
|
+ const rowSelection = computed(() => {
|
|
|
+ return {
|
|
|
+ selectedRowKeys: unref(selectedRowKeys),
|
|
|
+ onChange: (changeRowKeys) => {
|
|
|
+ console.log(changeRowKeys, 'changeRowKeyschangeRowKeys')
|
|
|
+ selectedRowKeys.value = changeRowKeys
|
|
|
+ },
|
|
|
+ hideDefaultSelections: true,
|
|
|
+ }
|
|
|
+ })
|
|
|
+ //添加学员确认
|
|
|
+ const confirmStudent = (StudentIds) => {
|
|
|
+ console.log(StudentIds, '学员id')
|
|
|
+ addStudentVisible.value = false
|
|
|
+ searchForm.StudentIds = StudentIds.join(',')
|
|
|
+ let params = {
|
|
|
+ userIds: searchForm.StudentIds
|
|
|
+ // courseId: pageSize.value
|
|
|
}
|
|
|
+ studentDetailsApi
|
|
|
+ .add(params)
|
|
|
+ .then((res) => {
|
|
|
+ console.log(res, '学员添加')
|
|
|
+ getList()
|
|
|
+ })
|
|
|
+ .catch((err) => {
|
|
|
+ console.log(err)
|
|
|
+ })
|
|
|
}
|
|
|
-
|
|
|
function onSearch() {
|
|
|
currentPage.value = 1
|
|
|
+ console.log(searchForm, '搜索参数')
|
|
|
+ getList()
|
|
|
}
|
|
|
const getList = () => {
|
|
|
- let params = {
|
|
|
+ const params = {
|
|
|
+ queryInfo: searchForm.queryInfo,
|
|
|
+ gender: searchForm.gender,
|
|
|
+ userStatus: searchForm.userStatus,
|
|
|
+ latestLoginStartTime: searchForm.startTime,
|
|
|
+ latestLoginEndTime: searchForm.endTime,
|
|
|
current: currentPage.value,
|
|
|
size: pageSize.value
|
|
|
}
|
|
|
@@ -304,14 +312,66 @@
|
|
|
.queryList(params)
|
|
|
.then((res) => {
|
|
|
console.log(res, '学院列表')
|
|
|
+ pagedDatas.value = res.data.records
|
|
|
+ total.value = res.data.total
|
|
|
})
|
|
|
.catch((err) => {
|
|
|
console.log(err)
|
|
|
})
|
|
|
}
|
|
|
+ function handleDateChange(dates, dateStrings) {
|
|
|
+ // dates 是 Moment 对象数组,dateStrings 是格式化后的字符串数组
|
|
|
+ if (dates && dates.length === 2) {
|
|
|
+ searchForm.startTime = dateStrings[0] // 开始时间字符串
|
|
|
+ searchForm.endTime = dateStrings[1] // 结束时间字符串
|
|
|
+ } else {
|
|
|
+ searchForm.startTime = null
|
|
|
+ searchForm.endTime = null
|
|
|
+ }
|
|
|
+ }
|
|
|
+ const importLoading = ref(false)
|
|
|
+ // 文件上传前校验
|
|
|
+ const beforeUpload = (file) => {
|
|
|
+ const isExcel = file.type.includes('excel') || file.name.endsWith('.xlsx') || file.name.endsWith('.xls')
|
|
|
+ if (!isExcel) {
|
|
|
+ message.error('只能上传Excel文件!')
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ return true
|
|
|
+ }
|
|
|
+ // 自定义上传实现
|
|
|
+ const customRequest = async (options) => {
|
|
|
+ const { file, onSuccess, onError } = options
|
|
|
+ importLoading.value = true
|
|
|
+ try {
|
|
|
+ // const formData = new FormData()
|
|
|
+ // formData.append('file', file)
|
|
|
+ let params = {
|
|
|
+ courseId: courseId.value,
|
|
|
+ file
|
|
|
+ }
|
|
|
+ // 调用API接口
|
|
|
+ const res = await studentDetailsApi.importStudents(params)
|
|
|
+ console.log(res)
|
|
|
+ message.success(`成功导入 ${res.data.count} 条数据`)
|
|
|
+ onSuccess(res, file)
|
|
|
+ getList() // 刷新列表
|
|
|
+ } catch (error) {
|
|
|
+ message.error(`导入失败: ${error.message}`)
|
|
|
+ onError(error)
|
|
|
+ } finally {
|
|
|
+ importLoading.value = false
|
|
|
+ }
|
|
|
+ }
|
|
|
function onReset() {
|
|
|
- searchForm.value = { keyword: '', type: '', status: '', date: [] }
|
|
|
+ searchForm.queryInfo = null
|
|
|
+ searchForm.gender = null
|
|
|
+ searchForm.userStatus = null
|
|
|
+ searchForm.startTime = null
|
|
|
+ searchForm.date = null
|
|
|
+ searchForm.endTime = null
|
|
|
currentPage.value = 1
|
|
|
+ getList()
|
|
|
}
|
|
|
function onPageChange(page) {
|
|
|
currentPage.value = page
|
|
|
@@ -321,17 +381,17 @@
|
|
|
currentPage.value = 1
|
|
|
}
|
|
|
function onSelectAll() {
|
|
|
- console.log(pagedData.value)
|
|
|
- selectedRowKeys.value = pagedData.value.map((item) => item.id)
|
|
|
+ console.log(pagedDatas.value)
|
|
|
+ selectedRowKeys.value = pagedDatas.value.map((item) => item.userId)
|
|
|
}
|
|
|
function onInvertSelect() {
|
|
|
- const currentIds = pagedData.value.map((item) => item.id)
|
|
|
+ const currentIds = pagedDatas.value.map((item) => item.userId)
|
|
|
selectedRowKeys.value = currentIds.filter((id) => !selectedRowKeys.value.includes(id))
|
|
|
}
|
|
|
function onAddStudent() {
|
|
|
addStudentVisible.value = true
|
|
|
// 默认展开所有部门
|
|
|
- expandedDeptIds.value = departments.value.map((d) => d.id)
|
|
|
+ // expandedDeptIds.value = departments.value.map((d) => d.id)
|
|
|
}
|
|
|
function handleAddStudentCancel() {
|
|
|
addStudentVisible.value = false
|
|
|
@@ -400,10 +460,19 @@
|
|
|
getList()
|
|
|
})
|
|
|
function onDetail(record) {
|
|
|
- getStudentDetail().then((data) => {
|
|
|
- detailData.value = data
|
|
|
- detailVisible.value = true
|
|
|
- })
|
|
|
+ detailVisible.value = true
|
|
|
+ let params = {
|
|
|
+ id: record.userId
|
|
|
+ }
|
|
|
+ studentDetailsApi
|
|
|
+ .detail(params)
|
|
|
+ .then((res) => {
|
|
|
+ console.log(res, '学员详情')
|
|
|
+ detailData.value = res.data.records
|
|
|
+ })
|
|
|
+ .catch((err) => {
|
|
|
+ console.log(err)
|
|
|
+ })
|
|
|
}
|
|
|
function onEdit(record) {
|
|
|
// TODO: 编辑弹窗
|