diff --git a/package.json b/package.json index c5918996..b827103a 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "html2canvas": "^1.4.1", "jquery": "^3.6.1", "json-bigint": "^1.0.0", + "lodash": "^4.17.21", "mitt": "^3.0.0", "moment": "^2.29.4", "pdf-vue3": "^1.0.12", diff --git a/src/api/modules/courseTag.js b/src/api/modules/courseTag.js new file mode 100644 index 00000000..2f1665fd --- /dev/null +++ b/src/api/modules/courseTag.js @@ -0,0 +1,64 @@ +/**课程标签模块的相关处理*/ +import ajax from "./xajax.js"; + +/** + * 分页查询:标签列表 + * @param {Object} query + */ +const portalPageList = function (query) { + return ajax.post("/systemapi/xboe/m/coursetag/page", query); +}; + +//改变标签的公共属性 +const changeTagPublic = function (row) { + // 返回 Promise 的 API 调用 + return ajax.post("/systemapi/xboe/m/coursetag/changePublicStatus", { + id: row.id, + isPublic: row.isPublic, + }); +}; + +//改变标签的热点属性 +const changeTagHot = function (row) { + // 返回 Promise 的 API 调用 + return ajax.post("/systemapi/xboe/m/coursetag/changeHotStatus", { + id: row.id, + isHot: row.isHot, + }); +}; + +//查询指定id的标签关联的所有课程 +const showCourseByTag = function (query) { + return ajax.post("/systemapi/xboe/m/coursetag/showCourseByTag", query); +}; + +//解除指定id的课程和某个标签之间的关联关系 +const unbindCourseTagRelation = function (params) { + return ajax.post("/systemapi/xboe/m/coursetag/unbind", params); +}; + +//编辑课程:标签模糊查询 +const searchTags = function (params) { + return ajax.post("/systemapi/xboe/m/coursetag/searchTags", params); +}; + +//编辑课程:创建标签(与当前课程关联) +const createTag = function (params) { + return ajax.post("/systemapi/xboe/m/coursetag/createTag", params); +}; + +//获取最新前10个热点标签 +const getHotTagList = function (params) { + return ajax.post("/systemapi/xboe/m/coursetag/getHotTagList", params); +}; + +export default { + portalPageList, + changeTagPublic, + changeTagHot, + showCourseByTag, + unbindCourseTagRelation, + searchTags, + createTag, + getHotTagList, +}; diff --git a/src/api/modules/userbasic.js b/src/api/modules/userbasic.js new file mode 100644 index 00000000..11e43697 --- /dev/null +++ b/src/api/modules/userbasic.js @@ -0,0 +1,121 @@ +/**对应用户中心新的接口*/ +import ajax from "./xajax"; +//const baseURL = process.env.VUE_APP_CESOURCE_BASE_API; +const baseURL = "/userbasic"; + +/**【未使用】用于本地测试*/ +const login = function () { + return ajax.post(baseURL + "/org/userParentOrg", {}); +}; + +/** 2023年6月新增加,退出接口*/ +const logout = function () { + return ajax.postJson(baseURL + "/logout", { from: "pc" }); +}; + +/** + * 【此接口已经不再使用】获取用户的组织机构 + * organization_id + */ +const userParentOrg = function () { + return ajax.post(baseURL + "/org/userParentOrg", {}); +}; + +/** + * /userbasic/org/list + * 根据关键字查询机构 + */ +const findOrgsByKeyword = function (keyword) { + console.log(12312); + return ajax.postJson(baseURL + "/org/list", { keyword }); +}; + +/** + * 【此接口已经不再使用】 + */ +const findOrgTreeByOrgId = function (orgId) { + return ajax.postJson(baseURL + "/org/childOrgs", { orgId }); +}; + +/** 获取机构信息 */ +const getOrgInfo = function (orgId) { + return ajax.postJson(baseURL + "/org/info", { orgId }); +}; + +/**【已接口已经不再使用】根据用户id获取用户的信息*/ +const getUserInfoById = function (id) { + return ajax.postJson(baseURL + "/user/list", { id }); +}; + +/** + * https://u-pre.boe.com/userbasic/audience/userAudiences + * 【当前代码中未查询到】获取当前用户受众信息 + */ +const getUserCrowds = function () { + return ajax.postJson(baseURL + "/audience/userAudiences", {}); +}; + +/** + * 获取用户过滤后的受众,只是查询已发布的 + * {"page":1,pageSize:100,"keyword":""} + */ +const getUserAudiences = function (data) { + return ajax.postJson(baseURL + "/audience/userAudiencesFilter", data); +}; + +/** + * 重要接口,获取hrbp数据,课程审核。 + * 此接口中的问题,返回的机构名称,namePath要是orgId的,邮件中体现 + */ +const getOrgHrbpInfo = function (orgId) { + return ajax.postJson(baseURL + "/org/orgHrbpInfo", { orgId }); +}; + +/** + * 修改密码,已转化为userbasic接口 + * {newPassword:'',oldPassword:''} + */ +const modifyPassword = function (data) { + return ajax.postJson(baseURL + "/user/resetPassword", data); +}; + +/**获取加入的受众的id集合*/ +const getInAudienceIds = function () { + return ajax.post(baseURL + "/audience/audienceByUser", {}); +}; + +/** + * 2023年6月新增加 + * 更新用户信息,当前只是列新三个信息,根据aid来更新 + * aid + * avatar + * sign + */ +const updateUser = function (data) { + return ajax.postJson(baseURL + "/user/updateUserMessage", data); +}; + +/** + * 2023年6月新增加 + * 根据用户的id集合,获取用户的姓名,工号,头像,组织机构,签名等信息 + * ids: 用户的id数组集合 + */ +const getUsersByIds = function (ids) { + return ajax.postJson(baseURL + "/user/getUserMessageToDai", ids); +}; + +export default { + userParentOrg, + findOrgsByKeyword, + getOrgInfo, + findOrgTreeByOrgId, + getUserInfoById, + getUserCrowds, + getUserAudiences, + getOrgHrbpInfo, + modifyPassword, + getInAudienceIds, + getUsersByIds, + updateUser, + logout, +}; diff --git a/src/assets/scss/common.scss b/src/assets/scss/common.scss index 00840fd7..1870ffce 100644 --- a/src/assets/scss/common.scss +++ b/src/assets/scss/common.scss @@ -737,6 +737,7 @@ textarea { } .el-select, +.el-select-v2, .el-cascader { width: 100%; } diff --git a/src/hooks/useCourseData.js b/src/hooks/useCourseData.js index 08de4d97..5ea6839b 100644 --- a/src/hooks/useCourseData.js +++ b/src/hooks/useCourseData.js @@ -30,6 +30,13 @@ export function useCourseData() { sectionIndex: "", resType: 0, selectionIndex: null, + // 添加课程操作相关状态 + isPreview: false, + showSettingDialog: false, + isNext: true, // 添加操作的时候 弹窗是否弹出对应类型表单 + showTablePreview: false, + showDialog: false, + classId: "", }); // 课程列表数据 @@ -112,4 +119,4 @@ export function useCourseData() { courseActionButtons, addChapter, }; -} +} \ No newline at end of file diff --git a/src/hooks/useCourseForm.js b/src/hooks/useCourseForm.js index 85f80bb8..54e6d807 100644 --- a/src/hooks/useCourseForm.js +++ b/src/hooks/useCourseForm.js @@ -2,49 +2,47 @@ import { reactive, ref } from "vue"; /** * 课程表单相关hook - * @returns + * @returns */ export function useCourseForm() { - // 表单相关 const formRef = ref(); + // 表单相关 const formState = reactive({ - courseName: "", // 课程名称 - courseCategory: [], // 课程分类 - resourceBelong: undefined, // 资源归属 - lecturer: undefined, // 授课教师 - targetGroup: "", // 目标人群 - courseTags: [], // 课程标签 - audience: undefined, // 受众 - visibility: "Apple", // 可见性 - coverIntro: "", // 封面介绍 - courseValue: "", // 课程价值 - courseIntro: "", // 课程简介 + name: "", + device: 3, + crowds: [], + courseTags: [], + courseCategory: [], + orgName: "", + forUsers: "", + lecturer: [], + coverImg: "", + courseValue: "", + summary: "", }); // 可见性选项 const visibilityOptions = [ - { label: "PC端可见", value: "Apple" }, - { label: "移动端可见", value: "Pear" }, - { label: "多端可见", value: "Orange", disabled: false }, + { label: "PC端可见", value: 1 }, + { label: "移动端可见", value: 2 }, + { label: "多端可见", value: 3 }, ]; // 表单重置 - const resetForm = (courseCoverurl, fileList) => { + const resetForm = (fileList) => { if (formRef.value) { formRef.value.resetFields(); } - if (courseCoverurl) { - courseCoverurl.value = ""; - } + if (fileList) { fileList.value = []; } }; return { - formRef, formState, visibilityOptions, - resetForm + resetForm, + formRef, }; -} \ No newline at end of file +} diff --git a/src/hooks/useFetchCourseList.js b/src/hooks/useFetchCourseList.js new file mode 100644 index 00000000..bda7a387 --- /dev/null +++ b/src/hooks/useFetchCourseList.js @@ -0,0 +1,111 @@ +import { ref, onMounted } from "vue"; +import { getTeacherList } from "@/api/Lecturer"; +import { getClassTree } from "@/api/modules/newApi"; +import apiUserBasic from "@/api/modules/userbasic"; +export function useFetchCourseList() { + const teachersList = ref([]); + const loading = ref(false); + // 分类列表 + const sysTypeListMap = ref([]); + const sysTypeList = ref([]); + const courseTags = ref([]); + const curCourseId = ref(""); + const orgList = ref([]); + const userGroupList = ref([]); + const fetchClassTree = async (data) => { + try { + const res = await getClassTree(data); + sysTypeListMap.value = res.result; + } catch (error) { + sysTypeListMap.value = []; + } + }; + // 获取教师列表 + const fetchTeacherList = async (data) => { + try { + loading.value = true; + const res = await getTeacherList({}); + teachersList.value = res.result?.records || []; + } catch (error) { + console.error("获取讲师列表失败:", error); + teachersList.value = []; + } finally { + loading.value = false; + } + }; + // 资源归属懒加载 + const loadOrgNode = async (node, resolve) => { + try { + // 根节点(level 0):返回虚拟根 + // if (node.level === 0) { + // resolve([{ name: "组织机构树", id: "-1" }]); + // return; + // } + + // 一级节点(level 1):加载所有顶级组织 + if (node.level === 0) { + const res = await apiUserBasic.findOrgsByKeyword(""); + const treeList = (res.result || []).map((item) => ({ + id: item.id, + name: item.name, + hrbpId: item.hrbpId, + children: [], // 假设有子节点(可根据后端字段优化) + })); + resolve(treeList); + return; + } + + // 更深层级:根据 parentId 加载直接子组织 + const parentId = node.data.id; + const res = await apiUserBasic.getOrgInfo(parentId); + + if (res.status === 200 && Array.isArray(res.result?.directChildList)) { + const treeList = res.result.directChildList.map((item) => ({ + id: item.id, + name: item.name, + hrbpId: item.hrbpId, + children: [], // 或根据 item.hasChildren / item.childCount > 0 动态设置 + })); + resolve(treeList); + } else { + resolve([]); // 无子节点 + } + } catch (error) { + console.error("加载组织树失败:", error); + // 出错时返回空数组,避免树组件卡住或报错 + resolve([]); + } + }; + + // 受众列表 + const getUserGroupList = async (data) => { + userGroupList.value = []; + try { + const res = await apiUserBasic.getUserAudiences(data); + + res.result.list.forEach((item) => { + userGroupList.value.push({ + id: item.id, + name: item.audienceName, + disabled: false, + }); + }); + } catch (error) { + console.error("获取用户组列表失败:", error); + userGroupList.value = []; + } + }; + return { + fetchTeacherList, + fetchClassTree, + loadOrgNode, + getUserGroupList, + teachersList, + sysTypeListMap, + courseTags, + orgList, + curCourseId, + userGroupList, + sysTypeList, + }; +} diff --git a/src/hooks/useMediaComponent.js b/src/hooks/useMediaComponent.js index 715cb7ab..851e6284 100644 --- a/src/hooks/useMediaComponent.js +++ b/src/hooks/useMediaComponent.js @@ -21,10 +21,12 @@ export function useMediaComponent(props, emit) { // Update form values and emit changes const updateFormValue = (field, value) => { localDialogVideoForm.value[field] = value; - emit("update:dialogVideoForm", { ...localDialogVideoForm.value }); + if (emit) { + emit("update:dialogVideoForm", { ...localDialogVideoForm.value }); + } }; - const fileBaseUrl = `${process.env.VUE_APP_BOE_API_URL}/upload`; + const fileBaseUrl = `${process.env.VUE_APP_BOE_API_URL}${process.env.VUE_APP_FILE_PATH}`; return { localDialogVideoForm, diff --git a/src/views/courselibrary/components/courseTag.vue b/src/views/courselibrary/components/courseTag.vue new file mode 100644 index 00000000..e073eefe --- /dev/null +++ b/src/views/courselibrary/components/courseTag.vue @@ -0,0 +1,440 @@ + + + + + diff --git a/src/views/courselibrary/components/createCourse.vue b/src/views/courselibrary/components/createCourse.vue index b333ba86..8316aa2b 100644 --- a/src/views/courselibrary/components/createCourse.vue +++ b/src/views/courselibrary/components/createCourse.vue @@ -4,9 +4,7 @@ import { ElButton, ElCheckbox, ElDialog, ElMessageBox } from "element-plus"; import { $message } from "@/utils/useMessage"; import dragTable from "./dragTable.vue"; import { ref, watch } from "vue"; -defineOptions({ - name: "CreateCourse", -}); +import { getType } from "@/hooks/useCreateCourseMaps"; import { useCourseData } from "@/hooks/useCourseData"; import chooseFileList from "@/components/CreatedCourse/chooseFileList.vue"; import VideoComp from "@/components/CreatedCourse/preview/VideoComp.vue"; @@ -18,7 +16,10 @@ import ScormComp from "@/components/CreatedCourse/preview/ScormComp.vue"; import PaperComp from "@/components/CreatedCourse/preview/PaperComp.vue"; import HomeWorkComp from "@/components/CreatedCourse/preview/HomeWorkComp.vue"; import AccessComp from "@/components/CreatedCourse/preview/AccessComp.vue"; -import { getType } from "@/hooks/useCreateCourseMaps"; +defineOptions({ + name: "CreateCourse", +}); +// 组件映射 const mapComponents = [ VideoComp, AudioComp, @@ -34,22 +35,16 @@ const mapComponents = [ // 使用课程数据hook const { courseMetadata, courseList, courseActionButtons, addChapter } = useCourseData(); -const isPreview = ref(false); const chooseItemData = ref({}); -const showSettingDialog = ref(false); -// 添加操作的时候 弹窗是否弹出对应类型表单 -const isNext = ref(true); -const showTablePreview = ref(false); -// 定义表格列 -const showDialog = ref(false); - -const classId = ref(""); - const copyChooseItemData = ref({}); + +// 监听课程索引变化,更新classId watch( () => courseMetadata.chooseIndex, (newVal) => { - classId.value = courseList.value[newVal].id; + if (courseList.value[newVal]) { + courseMetadata.classId = courseList.value[newVal].id || ""; + } } ); @@ -57,46 +52,46 @@ watch( const courseOperations = { addVideo: () => { courseMetadata.resType = 10; - showDialog.value = true; + courseMetadata.showDialog = true; }, addAudio: () => { courseMetadata.resType = 20; - showDialog.value = true; + courseMetadata.showDialog = true; }, addDocument: () => { courseMetadata.resType = 40; - showDialog.value = true; - isNext.value = false; + courseMetadata.showDialog = true; + courseMetadata.isNext = false; }, addImageText: () => { courseMetadata.resType = 41; chooseItemData.value.resType = 41; - showSettingDialog.value = true; + courseMetadata.showSettingDialog = true; }, addExternalLink: () => { courseMetadata.resType = 52; chooseItemData.value.resType = 52; - showSettingDialog.value = true; + courseMetadata.showSettingDialog = true; }, addScorm: () => { courseMetadata.resType = 50; - showDialog.value = true; - isNext.value = false; + courseMetadata.showDialog = true; + courseMetadata.isNext = false; }, addExam: () => { courseMetadata.resType = 61; - showDialog.value = true; - isNext.value = false; + courseMetadata.showDialog = true; + courseMetadata.isNext = false; }, addHomework: () => { courseMetadata.resType = 60; chooseItemData.value.resType = 60; - showSettingDialog.value = true; + courseMetadata.showSettingDialog = true; }, addAssessment: () => { courseMetadata.resType = 62; chooseItemData.value.resType = 62; - showSettingDialog.value = true; + courseMetadata.showSettingDialog = true; }, }; @@ -105,9 +100,10 @@ const executeCourseOperation = (operationName, data) => { courseMetadata.chooseIndex = data; courseMetadata.selectionIndex = null; copyChooseItemData.value = {}; - isPreview.value = false; - isNext.value = true; + courseMetadata.isPreview = false; + courseMetadata.isNext = true; chooseItemData.value = {}; + if (courseOperations[operationName]) { courseOperations[operationName](data); } else { @@ -115,92 +111,130 @@ const executeCourseOperation = (operationName, data) => { } }; +// 选择项目处理 const chooseItem = (data) => { - console.log(data); chooseItemData.value = data; - if (!isNext.value) { + // 如果不需要下一步,则直接保存 + if (!courseMetadata.isNext) { saveContent(); return; } - showSettingDialog.value = true; + courseMetadata.showSettingDialog = true; }; +// 预览项目处理 const choosePreviewItem = (data) => { chooseItemData.value = data; - showSettingDialog.value = true; - isPreview.value = true; + courseMetadata.showSettingDialog = true; + courseMetadata.isPreview = true; }; +// 取消保存 const cancelSave = () => { - showSettingDialog.value = false; + courseMetadata.showSettingDialog = false; + // 恢复原始数据 chooseItemData.value = copyChooseItemData.value; - courseList.value[courseMetadata.chooseIndex].data[ - courseMetadata.selectionIndex - ] = chooseItemData.value; - console.log(chooseItemData); -}; - -// 保存 -const saveContent = () => { - console.log(chooseItemData.value); - - if (courseMetadata.selectionIndex !== null) { + if ( + courseMetadata.chooseIndex !== null && + courseMetadata.selectionIndex !== null && + courseList.value[courseMetadata.chooseIndex] && + courseList.value[courseMetadata.chooseIndex].data[ + courseMetadata.selectionIndex + ] + ) { courseList.value[courseMetadata.chooseIndex].data[ courseMetadata.selectionIndex ] = chooseItemData.value; - } else { - courseList.value[courseMetadata.chooseIndex].data.push({ - resType: courseMetadata.resType, - ...chooseItemData.value, - }); } - showDialog.value = false; - showSettingDialog.value = false; - - // 可以调用保存方法 保存考试 }; +// 保存内容 +const saveContent = () => { + if (courseMetadata.selectionIndex !== null) { + // 更新已有项 + if ( + courseList.value[courseMetadata.chooseIndex] && + courseList.value[courseMetadata.chooseIndex].data[ + courseMetadata.selectionIndex + ] + ) { + courseList.value[courseMetadata.chooseIndex].data[ + courseMetadata.selectionIndex + ] = { + ...courseList.value[courseMetadata.chooseIndex].data[ + courseMetadata.selectionIndex + ], + ...chooseItemData.value, + }; + } + } else { + // 添加新项 + if (courseList.value[courseMetadata.chooseIndex]) { + courseList.value[courseMetadata.chooseIndex].data.push({ + resType: courseMetadata.resType, + ...chooseItemData.value, + }); + } + } + + courseMetadata.showDialog = false; + courseMetadata.showSettingDialog = false; +}; + +// 删除行 const deleteRow = (data) => { courseMetadata.chooseIndex = data.index; courseMetadata.selectionIndex = data.selectionIndex; + ElMessageBox.confirm(`确定删除${data.record.name}吗?`, "删除确认", { confirmButtonText: "确定", cancelButtonText: "取消", type: "error", - }).then(() => { - courseList.value[courseMetadata.chooseIndex].data.splice( - courseMetadata.selectionIndex, - 1 - ); - }); + }) + .then(() => { + if ( + courseList.value[courseMetadata.chooseIndex] && + courseList.value[courseMetadata.chooseIndex].data + ) { + courseList.value[courseMetadata.chooseIndex].data.splice( + courseMetadata.selectionIndex, + 1 + ); + } + }) + .catch(() => { + // 用户取消删除 + }); }; +// 设置行 const settingRow = (data) => { courseMetadata.chooseIndex = data.index; courseMetadata.selectionIndex = data.selectionIndex; - chooseItemData.value = data.record; + chooseItemData.value = { ...data.record }; // 创建副本避免直接引用 copyChooseItemData.value = JSON.parse(JSON.stringify(data.record)); - isPreview.value = false; - showSettingDialog.value = true; + courseMetadata.isPreview = false; + courseMetadata.showSettingDialog = true; }; +// 预览行 const previewRow = (data) => { courseMetadata.chooseIndex = data.index; courseMetadata.selectionIndex = data.selectionIndex; - chooseItemData.value = data.record; + chooseItemData.value = { ...data.record }; // 创建副本避免直接引用 copyChooseItemData.value = JSON.parse(JSON.stringify(data.record)); - isPreview.value = true; - showSettingDialog.value = true; + courseMetadata.isPreview = true; + courseMetadata.showSettingDialog = true; }; // 自定义考试 const chooseCusExam = (data) => { chooseItemData.value = data; - showSettingDialog.value = true; + courseMetadata.showSettingDialog = true; }; +// 下一步处理 const handleNext = () => { - console.log($message); $message.success("213"); }; @@ -212,7 +246,7 @@ const handleNext = () => { 创建时间:{{ courseMetadata.createTime }}
-
+
添加章 顺序学习
@@ -220,73 +254,85 @@ const handleNext = () => {
- +
- - + + + /> - + -
-