Merge branch 'merge-20251113-tag' into master-20250627bk

This commit is contained in:
670788339
2025-11-17 17:51:16 +08:00
7 changed files with 2040 additions and 4 deletions

46
src/api/courseTag.js Normal file
View File

@@ -0,0 +1,46 @@
/**课程标签模块的相关处理*/
// import ajax from '@/utils/xajax.js'
// import http from "@/api/configPublic";
// import {getCookieForName} from "@/api/method";
import http from "./config";
/**
* 分页查询:标签列表
* @param obj
*/
// export const portalPageList = (query = {}) => http.get(`/admin/courseTag/list`, {params: query});
export const portalPageList = (obj) => http.get('/admin/courseTag/list', {params: obj})
//改变标签的公共属性
export const changeTagPublic = (id = {}) => http.post(`/admin/courseTag/changePublicStatus`, id);
//改变标签的热点属性
export const changeTagHot = (id = {}) => http.post(`/admin/courseTag/changeHotStatus`, id);
//查询指定id的标签关联的所有课程
export const showCourseByTag = (obj = {}) => http.get(`/admin/courseTag/showCourseByTag`, {params: obj});
//解除指定id的课程和某个标签之间的关联关系
export const unbindCourseTagRelation = (id = {}) => http.post(`/admin/courseTag/unbind`, id);
//编辑课程:标签模糊查询
export const searchTags = (params = {}) => http.post(`/admin/courseTag/searchTags`, params);
//编辑课程:创建标签(与当前课程关联)
export const createTag = (params = {}) => http.post(`/admin/courseTag/createTag`, params);
//导出
export const exportTagFile = (params = {}) => http.get(`/admin/courseTag/exportTag`, params);
/*export default {
portalPageList,
changeTagPublic,
changeTagHot,
showCourseByTag,
unbindCourseTagRelation,
searchTags,
createTag,
getHotTagList
}*/

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -893,6 +893,13 @@
}
];
}
if (n.indexOf("/tagmanage") !== -1 || n.indexOf("/download") !== -1) {
state.list = [
{
name: "标签管理",
},
];
}
}
);

View File

@@ -564,7 +564,18 @@
<router-link to="/trainingnewmanager">新任管理者培训数据</router-link>
</a-menu-item>
</a-sub-menu>
<a-menu-item key="sub16" v-if="checkMenu('OldSystemManage')">
<!-- 标签管理 <a-menu-item key="sub16" v-if="checkMenu('OnlineCourseTagManage')">-->
<a-menu-item key="sub16" v-if="checkMenu('OnlineCourseTagManage')">
<div class="imgBox">
<img
style="width: 22px; height: 22px"
src="../assets/images/navleft/tag.png"
/>
</div>
<router-link to="/tagmanage">标签管理</router-link>
</a-menu-item>
<a-menu-item key="sub17" v-if="checkMenu('OldSystemManage')">
<div class="imgBox">
<img style="width: 22px; height: 22px" src="../assets/images/navleft/review.png" />
</div>
@@ -572,13 +583,13 @@
</a-menu-item>
<!-- <router-link target="_blank" to="/oldsystemmanage">旧版管理员界面</router-link> -->
<a-menu-item key="sub17" v-if="checkMenu('ReadingClubManage')">
<a-menu-item key="sub18" v-if="checkMenu('ReadingClubManage')">
<div class="imgBox">
<img style="width: 22px; height: 22px" src="../assets/images/navleft/review.png" />
</div>
<router-link to="/readingclubmanage">业务支援读书会管理</router-link>
</a-menu-item>
<a-menu-item key="sub18" v-if="checkMenu('dictmanage')">
<a-menu-item key="sub19" v-if="checkMenu('dictmanage')">
<div class="imgBox">
<img style="width: 22px; height: 22px" src="../assets/images/navleft/review.png" />
</div>
@@ -673,7 +684,7 @@ import { reactive, toRefs, onMounted, onUnmounted, watch } from "vue";
import { useRoute } from "vue-router";
import { useStore } from "vuex";
import { checkMenu, checkOrgs, lecturerRoute } from "@/utils/utils";
import { reportOrgs } from "@/api/indexProject";
// import { reportOrgs } from "@/api/indexProject";
export default {
name: "NavLeft",
setup() {
@@ -706,6 +717,7 @@ export default {
"sub13",
"sub14",
"sub15",
"sub16",
"sub17",
"sub18",
"sub19",
@@ -1000,6 +1012,12 @@ export default {
selectedKeys: "sub15",
pagename: "下载中心",
},
{
href: "/tagmanage",
openKeys: "sub16",
selectedKeys: "sub16",
pagename: "标签管理",
},
{
href: "/operational",
openKeys: "sub17",

View File

@@ -0,0 +1,390 @@
<template>
<div class="u-page" style="padding-right:32px">
<div style="width: 100%; margin-left: 12px;padding: 2px 0px 10px 12px;background-color: white">
<el-form :inline="true" style="margin-left: 12px;" :model="pageData" class="demo-form-inline">
<el-form-item label="标签ID:" label-width="60px">
<el-input id="tag-id" placeholder="请输入标签ID" v-model="pageData.id" clearable />
</el-form-item>
<el-form-item label="标签名称:" label-width="80px">
<el-input id="tag-name" placeholder="请输入标签名称" v-model="pageData.tagName" clearable />
</el-form-item>
<el-form-item label="热点标签:" label-width="80px">
<el-select v-model="pageData.isHot" style="width: 120px;" clearable placeholder="请选择状态">
<el-option label="开启" value="true"></el-option>
<el-option label="关闭" value="false"></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button @click="getsearch" icon="el-icon-search" type="primary">查询</el-button>
<!-- 添加重置按钮 -->
<el-button @click="resetSearch" icon="el-icon-refresh">重置</el-button>
</el-form-item>
</el-form>
</div>
<div style="padding: 5px 0px 2px 12px;">
<!-- <el-checkbox label="前台公共显示"></el-checkbox>-->
<!-- <el-checkbox label="热点标签展示"></el-checkbox>-->
</div>
<div style="width: 100%; margin-left: 12px;padding: 2px 0px 10px 12px;background-color: white">
<el-table style="width: 96%; margin:2px 32px 10px 12px;" :data="pageData.list" border stripe
:header-cell-style="{ background: '#E9F0FF' }"
@selection-change="handleSelectionChange"
@sort-change="handleSortChange">
<el-table-column type="selection" width="80px"></el-table-column>
<el-table-column label="标签ID" width="200px" prop="id"></el-table-column>
<el-table-column label="标签名称" width="235px" prop="tagName"></el-table-column>
<el-table-column label="已关联课程" width="220px"
prop="useCount"
sortable="custom"
:sort-orders="['descending', 'ascending']"
>
<template #default="scope">
<a v-if="scope.row && scope.row.useCount > 0"
@click="showCourseByTag(`${scope.row.id}`)"
style="font-weight:bold; color: #409EFF; text-decoration: underline;">
{{ scope.row.useCount }}
</a>
<span style="font-weight:bold; color: #409EFF; text-decoration: underline;" v-else>0</span>
</template>
</el-table-column>
<el-table-column label="前台公共显示" width="220px" prop="isPublic">
<template #default="scope"><!-- 开关状态会直接修改 pageData.list 中的数据 -->
<el-switch
v-model="scope.row.isPublic"
:disabled="scope.row.isHot==1?true:false"
@change="handlePublicChange(scope.row)"
>
</el-switch>
</template>
</el-table-column>
<el-table-column label="热点标签展示" width="220px" prop="isHot">
<template #default="scope">
<el-switch
v-model="scope.row.isHot"
:disabled="scope.row.isPublic==0?true:false"
@change="handleHotChange(scope.row)"
>
</el-switch>
</template>
</el-table-column>
</el-table>
<div v-if="pageData.list.length > 0" style="text-align: center;margin-top: 50px;">
<el-pagination
background
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="pageData.pageIndex"
:page-sizes="[10, 20, 30, 40]"
:page-size="pageData.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
></el-pagination>
</div>
</div>
<!-- 标签关联课程弹窗 -->
<el-dialog custom-class="g-dialog" title="关联课程"
width="850px" top="20px"
v-model="dialogVisible"
:modal-append-to-body="true"
:append-to-body="true">
<div class="dialog-content-container">
<el-table
:data="pageData.list2"
border stripe style="width: 100%"
:header-cell-style="{ background: '#E9F0FF' }"
@sort-change="handleSortChange2">
<el-table-column label="序号" width="60px" align="center">
<template #default="scope">
{{ scope.$index + 1 }}
</template>
</el-table-column>
<el-table-column label="关联课程名称" width="200px" prop="courseName"></el-table-column>
<el-table-column label="关联课程ID" width="100px" prop="courseId"></el-table-column>
<el-table-column label="关联人" width="80px" prop="sysCreateBy"></el-table-column>
<el-table-column label="关联时间" width="110px" prop="sysCreateTime"
:formatter="dateFormat" sortable="custom"
:sort-orders="['descending', 'ascending']"></el-table-column>
<el-table-column label="本课程绑定的其他标签" width="200px" prop="otherTags"></el-table-column>
<el-table-column label="操作" width="60px">
<template #default="scope">
<a @click="unbindCurrentTag(scope.row)"
style="font-weight:bold; color: #409EFF;">
解绑
</a>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<div v-if="pageData.list2.length > 0" class="pagination-container">
<el-pagination
background
@size-change="handleSizeChange2"
@current-change="handleCurrentChange2"
:current-page="pageData.pageIndex2"
:page-sizes="[10, 20, 30, 40]"
:page-size="pageData.pageSize2"
layout="total, sizes, prev, pager, next, jumper"
:total="total2">
</el-pagination>
</div>
</div>
</el-dialog>
</div>
</template>
<script>
import moment from 'moment';
import apiCourseTag from '@/api/courseTag.js'
import { mapGetters } from 'vuex';
export default {
name: 'courseTagItems',
computed: {
...mapGetters(['userInfo'])
},
data() {
return {
pageData: {
pageIndex: 1,
pageIndex2: 1,
pageSize: 10,
pageSize2: 10,
list:[],
list2:[],
orderField: null,
orderAsc: null,
orderField2: null,
orderAsc2: null,
},
total: 0,
total2: 0,
dialogVisible: false,
tagId: null,
}
},
created() {
this.getCourseTagList()
},
methods: {
//重置搜索条件
resetSearch() {
this.pageData.id = '';
this.pageData.tagName = '';
this.pageData.isHot = '';
this.pageData.pageIndex = 1;
this.getCourseTagList(); // 重置后重新加载列表
},
//初始化:课程标签列表
getsearch(){
this.pageData.pageIndex = 1;
this.getCourseTagList()
},
//课程标签列表:排序
handleSortChange({ prop, order }) {
this.pageData.orderField = prop; // 当前排序字段
this.pageData.orderAsc = order === 'ascending'; // 排序方向
this.getCourseTagList(); // 重新获取数据
},
//TODO:课程标签列表:监听选中项变化(批量的设置标签公共显示|热点标签)
handleSelectionChange(selection) {
this.selectedRows = selection; // 更新选中的行数据
},
//课程标签列表:获取课程标签列表数据
getCourseTagList() {
const { pageIndex, pageSize, orderField, orderAsc } = this.pageData
let query = { pageIndex, pageSize, orderField, orderAsc}
//拼接查询条件
if (this.pageData.id) {
const { id } = this.pageData
query.id = id
}
if (this.pageData.tagName) {
const { tagName } = this.pageData
query.tagName = tagName
}
if (this.pageData.isHot) {
const { isHot } = this.pageData
query.isHot = isHot
}
apiCourseTag.portalPageList(query).then((res) => {
if (res.status == 200) {
this.total = res.result?.count || 0
this.pageData.list = res.result?.list || []
} else {
this.$message.error('获取数据失败')
}
}).catch(() => {
this.$message.error('获取数据失败')
})
},
//课程标签列表:改变标签的公共属性
async handlePublicChange(row) {
// 保存原始状态用于回滚
const originalStatus = row.isPublic;
try {
// 调用 API 更新状态
await apiCourseTag.changeTagPublic(row);
this.$message.success('更新成功');
} catch (error) {
// 发生错误时回滚状态
row.isPublic = originalStatus;
this.$message.error('更新失败:' + error.message);
}
},
//课程标签列表:改变标签的热点属性
async handleHotChange(row) {
// const isPublic=row.isPublic;
// 保存原始状态用于回滚
const originalStatus = row.isHot;
try {
// 调用 API 更新状态
await apiCourseTag.changeTagHot(row).then((res)=>{
if (res.status == 200){
this.$message.success(res.message);
}else {
row.isHot=false;
this.$message.warning(res.message);
}
});
} catch (error) {
// 发生错误时回滚状态
row.isHot = originalStatus;
this.$message.error('更新失败:' + error.message);
}
},
//课程标签列表:改变条数的回调
handleSizeChange(value) {
this.pageData.pageIndex = 1;
this.pageData.pageSize = value;
this.getCourseTagList();
},
//课程标签列表:改变页数的回调
handleCurrentChange(value) {
this.pageData.pageIndex = value;
this.getCourseTagList();
},
//标签关联的所有课程弹出框显示指定标签id关联的课程列表
showCourseByTag(tagId) {
this.tagId=tagId;
this.getCourseOfTagList(tagId);
this.dialogVisible=true;
},
//分页查询指定标签关联的所有课程
getCourseOfTagList(){
const { pageIndex2:pageIndex, pageSize2:pageSize, orderField2:orderField, orderAsc2:orderAsc } = this.pageData
let query = { pageIndex, pageSize, orderField, orderAsc }
//拼接查询条件
if (this.tagId) {
query.id = this.tagId
apiCourseTag.showCourseByTag(query).then((res) => {
if (res.status == 200) {
this.total2 = res.result.count
this.pageData.list2 = res.result.list
if (this.total2==0){
this.dialogVisible=false
this.getCourseTagList(); // 重新获取课程标签列表数据
}
}
})
.catch(() => {
this.$message.error('获取数据失败')
});
}
},
//标签关联课程列表:排序
handleSortChange2({ prop, order }) {
this.pageData.orderField2 = prop; // 当前排序字段
this.pageData.orderAsc2 = order === 'ascending'; // 排序方向
this.getCourseOfTagList(); // 重新获取数据
},
//标签关联的所有课程列表:改变条数的回调
handleSizeChange2(value) {
this.pageData.pageIndex2= 1;
this.pageData.pageSize2 = value;
this.getCourseOfTagList();
},
//标签关联的所有课程列表:改变页数的回调
handleCurrentChange2(value) {
this.pageData.pageIndex2 = value;
this.getCourseOfTagList();
},
//关联时间格式化
dateFormat(row, column) {
return row[column.property] ?
moment(row[column.property]).format('YYYY-MM-DD') : '';
},
//解除指定课程和当前标签的关联关系
unbindCurrentTag (row) {
let id = row.id;
let tagId = this.tagId;
let courseId = row.courseId;
//拼接查询条件
if (tagId && courseId) {
let params = { id, tagId, courseId }
apiCourseTag.unbindCourseTagRelation(params).then((res) => {
if (res.status == 200) {
//刷新列表
this.getCourseOfTagList(this.tagId);
}
})
.catch(() => {
this.$message.error('解绑失败!')
});
}
}
}
}
</script>
<style>
.demo-form-inline {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 10px; /* 间距 */
}
.demo-form-inline .el-form-item {
margin-bottom: 0; /* 消除默认底部间距 */
}
.dialog-content-container {
padding: 10px;
border: 1px solid #d9d9d9;
}
.pagination-container {
margin-top: 20px;
text-align: center;
}
.g-dialog .el-dialog__header {
background-color: #409EFF;
padding: 15px 20px;
}
.g-dialog .el-dialog__title {
color: white;
font-weight: bold;
}
.g-dialog .el-dialog__headerbtn .el-dialog__close {
color: white;
}
</style>

1523
src/views/tag/TagManage.vue Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,52 @@
<!--
* @Author: lixg lixg@dongwu-inc.com
* @Date: 2022-11-09 09:26:26
* @LastEditors: lixg lixg@dongwu-inc.com
* @LastEditTime: 2022-12-11 12:01:17
* @FilePath: /fe-manage/src/views/courselibrary/CourseManage.vue
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
-->
<!-- 标签管理页面 -->
<!-- sandbox="allow-forms allow-scripts allow-same-origin allow-popups" -->
<template>
<div class="articleManage">
<iframe
id="iframe"
style="width: 100%; height: 100%"
:src="iframeUrl + '/tag/manages'"
frameborder="0"
name="myframe"
security="restricted"
sandbox="allow-forms allow-downloads allow-scripts allow-same-origin allow-popups"
></iframe>
</div>
</template>
<script>
import { reactive, toRefs, onMounted } from "vue";
import { iframeUrl } from "../../api/method";
export default {
name: "ArticleManage",
setup() {
const state = reactive({
iframeUrl: iframeUrl,
});
onMounted(() => {
// console.log("执行");
});
return {
...toRefs(state),
};
},
};
</script>
<style lang="scss">
.articleManage {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
}
</style>