feat: 增强报名管理功能,新增删除课程学习记录API,优化受众选择弹窗的多选逻辑和搜索功能

This commit is contained in:
huweihang
2025-12-17 17:44:58 +08:00
parent b2395fd3a2
commit 5f518b713a
3 changed files with 239 additions and 19 deletions

View File

@@ -17,7 +17,7 @@ const hasSignup = function(courseId) {
* 课程报名,微课,录播课
* @param {Object} data
* {
courseId:
courseId:
courseName:
courseType 课程类型,对应微课,录播课
signType:1,后名方式默认是1自主报名可以不传
@@ -367,6 +367,15 @@ const deleteSignUp=function(id,courseId){
return ajax.post(`/xboe/school/study/course/delete-signup?id=${id}&couserId=${courseId}`);
}
/*
删除课程学习记录
@param id 学习id
@param courseId 课程id 用于删除课程报名信息和修改课程学习人数
*/
const deleteNewSignUp=function(data){
return ajax.post(`/xboe/school/study/course/delete-signup`, data);
}
/**
* 二次查询 用于个人主页/他人主页
* {
@@ -432,6 +441,7 @@ export default {
signup,
findSignup,
deleteSignup,
deleteNewSignUp,
importSignup,
countSignup,
studyIndex,

View File

@@ -5,16 +5,37 @@
</el-tab-pane>
</el-tabs>
<div class="tab-search">
<el-input
v-model="keyword"
placeholder="姓名"
size="small"
<el-select
class="member-select"
ref="memberSelect"
v-model="memberSelected"
multiple
filterable
remote
clearable
class="input"
@keyup.enter.native="onSearch"
/>
<el-button type="primary" size="small" @click="onSearch">查询</el-button>
<el-button type="primary" size="small" @click="onReset">重置</el-button>
reserve-keyword
placeholder="姓名"
:multiple-limit="5"
:remote-method="remoteSearchMember"
:loading="memberLoading"
@input.native="limitMemberInput"
@visible-change="handleMemberVisibleChange"
@change="handleMemberChange"
@clear="handleMemberClear"
size="small"
>
<el-option
v-for="item in memberOptions"
:key="item.userId"
:label="item.name"
:value="item.userId"
>
<span>{{ item.name }}</span>
<span v-if="item.code" class="option-code">{{ item.code }}</span>
</el-option>
</el-select>
<el-button type="primary" size="small" @click="onSearch" style="margin-left: 8px;">查询</el-button>
<el-button size="small" @click="onReset" style="margin-left: 4px;">重置</el-button>
</div>
<el-table
class="use-table"
@@ -51,6 +72,7 @@
<script>
import { fetchAudienceList, saveStu } from "@/api/signup/commonStudent";
import apiUserbasic from "@/api/boe/userbasic.js";
export default {
name: "AudienceModal",
@@ -74,6 +96,10 @@ export default {
total: 0,
pageNo: 1,
selectedRows: [],
// 成员远程搜索多选
memberSelected: [],
memberOptions: [],
memberLoading: false,
};
},
watch: {
@@ -89,15 +115,18 @@ export default {
},
methods: {
resetAndFetch() {
this.keyword = "";
this.pageNo = 1;
this.selectedRows = [];
this.keyword = "";
this.memberSelected = [];
this.memberOptions = [];
this.fetchList();
},
fetchList() {
this.loading = true;
fetchAudienceList({
keyword: this.keyword,
// memberList 为多选成员 ID 列表
memberList: this.memberSelected || [],
pageNo: this.pageNo,
pageSize: this.pageSize,
audienceIdList: this.audienceIds || [],
@@ -117,6 +146,8 @@ export default {
},
onReset() {
this.keyword = "";
this.memberSelected = [];
this.memberOptions = [];
this.onSearch();
},
onPageChange(page) {
@@ -126,6 +157,79 @@ export default {
onSelectionChange(list) {
this.selectedRows = list;
},
// ===== 成员远程搜索,多选逻辑(参考 ManageListRemote.vue 的创建人筛选) =====
async remoteSearchMember(keyword) {
const limited = (keyword || "").slice(0, 50);
if (this.$refs.memberSelect && this.$refs.memberSelect.query !== limited) {
this.$refs.memberSelect.query = limited;
this.$nextTick(() => {
if (
this.$refs.memberSelect &&
this.$refs.memberSelect.$refs &&
this.$refs.memberSelect.$refs.input
) {
this.$refs.memberSelect.$refs.input.value = limited;
}
});
}
const query = limited.trim();
if (!query || query.length <= 1) {
this.memberOptions = [];
return;
}
this.memberLoading = true;
try {
const res = await apiUserbasic.selectUser(query);
if (res && res.status === 200) {
const resultList = res.result || [];
this.memberOptions = resultList
.map((item) => this.formatMemberItem(item))
.filter((item) => item.userId);
} else {
this.memberOptions = [];
}
} catch (error) {
this.memberOptions = [];
} finally {
this.memberLoading = false;
}
},
formatMemberItem(item = {}) {
return {
userId: item.id,
name: item.realName,
code: item.userNo,
};
},
handleMemberChange(value = []) {
// 限制最多 5 个,保持与 ManageListRemote 中创建人筛选一致
this.memberSelected = (value || []).slice(0, 5);
},
handleMemberClear() {
this.memberSelected = [];
this.memberOptions = [];
},
handleMemberVisibleChange(visible) {
if (!visible) return;
const select = this.$refs.memberSelect;
const query = (select && select.query) || "";
if (!query) {
this.memberOptions = [];
}
},
limitMemberInput(event) {
const limited =
(event && event.target && event.target.value
? event.target.value
: ""
).slice(0, 50);
if (event && event.target && event.target.value !== limited) {
event.target.value = limited;
}
if (this.$refs.memberSelect) {
this.$refs.memberSelect.query = limited;
}
},
handleClose() {
this.visibleSync = false;
},

View File

@@ -35,7 +35,7 @@
<el-button type="primary" size="small" @click="onSearchStu">
搜索
</el-button>
<el-button type="primary" size="small" @click="resetStu">重置</el-button>
<el-button size="small" @click="resetStu">重置</el-button>
</div>
<div class="split">
<div class="left-tree">
@@ -43,8 +43,15 @@
@node-click="onOrgSelect" />
</div>
<div class="table-area">
<el-table class="use-table" border :data="stuTable.list"
@selection-change="onStuSelectionChange" :row-key="row => row.id || row.userId">
<el-table
ref="stuTable"
class="use-table"
border
:data="stuTable.list"
@selection-change="onStuSelectionChange"
:row-key="row => row.id || row.userId"
:reserve-selection="true"
>
<el-table-column type="selection" width="50" />
<el-table-column prop="realName" label="姓名" width="120" />
<el-table-column prop="userNo" label="工号" width="120" />
@@ -83,8 +90,15 @@
</el-button>
<el-button size="small" @click="resetAudienceInfo">重置</el-button>
</div>
<el-table class="use-table" border :data="audienceTable.list" @selection-change="onAudienceSelectionChange"
:row-key="row => row.id">
<el-table
ref="audienceTable"
class="use-table"
border
:data="audienceTable.list"
@selection-change="onAudienceSelectionChange"
:row-key="row => row.id"
:reserve-selection="true"
>
<el-table-column type="selection" width="50" />
<el-table-column prop="audienceName" label="受众名称" min-width="220" />
<el-table-column prop="totalMember" label="总人数" width="100" />
@@ -309,6 +323,8 @@ export default {
visible(val) {
if (val) {
this.initData();
} else {
this.resetState();
}
},
},
@@ -318,6 +334,30 @@ export default {
}
},
methods: {
// 参考 CommonStudent.vue在关闭弹窗时清空各种选中与搜索条件
resetState() {
this.projectParams.studentName = "";
this.nameSearch = { keyword: "", departId: "" };
this.audienceName = { keyword: "" };
this.searchOrgName = { keyword: "", pageNo: 1, pageSize: 200 };
this.projectSelectRows = [];
this.stuSelectRows = [];
this.auditSelectRows = [];
this.deptList = [];
this.selectedOrgKeys = [];
this.member = false;
this.dept = false;
this.person = false;
this.group = false;
// 清空表格勾选
if (this.$refs.stuTable && this.$refs.stuTable.clearSelection) {
this.$refs.stuTable.clearSelection();
}
if (this.$refs.audienceTable && this.$refs.audienceTable.clearSelection) {
this.$refs.audienceTable.clearSelection();
}
},
initData() {
this.courseDetail = JSON.parse(sessionStorage.getItem("courseDetail") || "{}");
this.projectParams = {
@@ -374,6 +414,10 @@ export default {
console.log("res", res);
this.stuTable.list = res.data?.list || [];
this.stuTable.total = res.data?.total || 0;
// 根据右侧已选,恢复当前页的勾选状态
this.$nextTick(() => {
this.syncStuTableSelection();
});
});
},
onStuPageChange(page) {
@@ -381,7 +425,22 @@ export default {
this.onSearchStu();
},
onStuSelectionChange(list) {
this.stuSelectRows = list;
// Element 表格在翻页时会把当前页的 selection 通过 list 传进来
// 为了保留其他页已选,这里做合并而不是直接覆盖
const currentPageIds = this.stuTable.list.map((r) => r.id || r.userId);
// 先保留非当前页的已选
const otherPageSelected = this.stuSelectRows.filter(
(r) => !currentPageIds.includes(r.id || r.userId)
);
const merged = [...otherPageSelected, ...list];
// 根据 id/userId 去重
const seen = new Set();
this.stuSelectRows = merged.filter((r) => {
const key = r.id || r.userId;
if (!key || seen.has(key)) return false;
seen.add(key);
return true;
});
},
resetStu() {
this.nameSearch = { keyword: "", departId: "" };
@@ -444,6 +503,10 @@ export default {
console.log('searchAudience', res);
this.audienceTable.list = res.data?.list || [];
this.audienceTable.total = res.data?.total || 0;
// 根据右侧已选,恢复当前页的受众勾选状态
this.$nextTick(() => {
this.syncAudienceTableSelection();
});
});
},
onAudiencePageChange(page) {
@@ -451,7 +514,50 @@ export default {
this.searchAudience();
},
onAudienceSelectionChange(list) {
this.auditSelectRows = list;
const currentPageIds = this.audienceTable.list.map((r) => r.id);
const otherPageSelected = this.auditSelectRows.filter(
(r) => !currentPageIds.includes(r.id)
);
const merged = [...otherPageSelected, ...list];
const seen = new Set();
this.auditSelectRows = merged.filter((r) => {
const key = r.id;
if (!key || seen.has(key)) return false;
seen.add(key);
return true;
});
},
// 根据 stuSelectRows 恢复当前页表格里的勾选
syncStuTableSelection() {
if (!this.$refs.stuTable) return;
const table = this.$refs.stuTable;
if (!table.clearSelection || !table.toggleRowSelection) return;
const selectedMap = new Set(
(this.stuSelectRows || []).map((r) => r.id || r.userId)
);
table.clearSelection();
this.stuTable.list.forEach((row) => {
const key = row.id || row.userId;
if (key && selectedMap.has(key)) {
table.toggleRowSelection(row, true);
}
});
},
// 根据 auditSelectRows 恢复当前页受众表格里的勾选
syncAudienceTableSelection() {
if (!this.$refs.audienceTable) return;
const table = this.$refs.audienceTable;
if (!table.clearSelection || !table.toggleRowSelection) return;
const selectedMap = new Set(
(this.auditSelectRows || []).map((r) => r.id)
);
table.clearSelection();
this.audienceTable.list.forEach((row) => {
const key = row.id;
if (key && selectedMap.has(key)) {
table.toggleRowSelection(row, true);
}
});
},
resetAudienceInfo() {
this.audienceName.keyword = "";