mirror of
https://codeup.aliyun.com/67762337eccfc218f6110e0e/vue/fe-manage.git
synced 2025-12-16 06:16:46 +08:00
feat(course): 实现课程标签管理功能
- 新增课程标签API模块,支持标签分页查询、创建、修改状态等操作 - 开发课程标签组件,支持标签搜索、创建、删除和数量限制 - 集成标签组件到专业模式页面,替换原有标签选择器 - 优化课程创建组件,重构表单状态管理和操作流程 - 升级Element Plus组件版本,支持el-select-v2等新组件 - 添加lodash依赖用于防抖搜索功能 - 调整样式和布局,优化标签显示和交互体验
This commit is contained in:
@@ -26,6 +26,7 @@
|
|||||||
"html2canvas": "^1.4.1",
|
"html2canvas": "^1.4.1",
|
||||||
"jquery": "^3.6.1",
|
"jquery": "^3.6.1",
|
||||||
"json-bigint": "^1.0.0",
|
"json-bigint": "^1.0.0",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
"mitt": "^3.0.0",
|
"mitt": "^3.0.0",
|
||||||
"moment": "^2.29.4",
|
"moment": "^2.29.4",
|
||||||
"pdf-vue3": "^1.0.12",
|
"pdf-vue3": "^1.0.12",
|
||||||
|
|||||||
64
src/api/modules/courseTag.js
Normal file
64
src/api/modules/courseTag.js
Normal file
@@ -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,
|
||||||
|
};
|
||||||
121
src/api/modules/userbasic.js
Normal file
121
src/api/modules/userbasic.js
Normal file
@@ -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,
|
||||||
|
};
|
||||||
@@ -737,6 +737,7 @@ textarea {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.el-select,
|
.el-select,
|
||||||
|
.el-select-v2,
|
||||||
.el-cascader {
|
.el-cascader {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,13 @@ export function useCourseData() {
|
|||||||
sectionIndex: "",
|
sectionIndex: "",
|
||||||
resType: 0,
|
resType: 0,
|
||||||
selectionIndex: null,
|
selectionIndex: null,
|
||||||
|
// 添加课程操作相关状态
|
||||||
|
isPreview: false,
|
||||||
|
showSettingDialog: false,
|
||||||
|
isNext: true, // 添加操作的时候 弹窗是否弹出对应类型表单
|
||||||
|
showTablePreview: false,
|
||||||
|
showDialog: false,
|
||||||
|
classId: "",
|
||||||
});
|
});
|
||||||
|
|
||||||
// 课程列表数据
|
// 课程列表数据
|
||||||
|
|||||||
@@ -5,46 +5,44 @@ import { reactive, ref } from "vue";
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export function useCourseForm() {
|
export function useCourseForm() {
|
||||||
// 表单相关
|
|
||||||
const formRef = ref();
|
const formRef = ref();
|
||||||
|
// 表单相关
|
||||||
const formState = reactive({
|
const formState = reactive({
|
||||||
courseName: "", // 课程名称
|
name: "",
|
||||||
courseCategory: [], // 课程分类
|
device: 3,
|
||||||
resourceBelong: undefined, // 资源归属
|
crowds: [],
|
||||||
lecturer: undefined, // 授课教师
|
courseTags: [],
|
||||||
targetGroup: "", // 目标人群
|
courseCategory: [],
|
||||||
courseTags: [], // 课程标签
|
orgName: "",
|
||||||
audience: undefined, // 受众
|
forUsers: "",
|
||||||
visibility: "Apple", // 可见性
|
lecturer: [],
|
||||||
coverIntro: "", // 封面介绍
|
coverImg: "",
|
||||||
courseValue: "", // 课程价值
|
courseValue: "",
|
||||||
courseIntro: "", // 课程简介
|
summary: "",
|
||||||
});
|
});
|
||||||
|
|
||||||
// 可见性选项
|
// 可见性选项
|
||||||
const visibilityOptions = [
|
const visibilityOptions = [
|
||||||
{ label: "PC端可见", value: "Apple" },
|
{ label: "PC端可见", value: 1 },
|
||||||
{ label: "移动端可见", value: "Pear" },
|
{ label: "移动端可见", value: 2 },
|
||||||
{ label: "多端可见", value: "Orange", disabled: false },
|
{ label: "多端可见", value: 3 },
|
||||||
];
|
];
|
||||||
|
|
||||||
// 表单重置
|
// 表单重置
|
||||||
const resetForm = (courseCoverurl, fileList) => {
|
const resetForm = (fileList) => {
|
||||||
if (formRef.value) {
|
if (formRef.value) {
|
||||||
formRef.value.resetFields();
|
formRef.value.resetFields();
|
||||||
}
|
}
|
||||||
if (courseCoverurl) {
|
|
||||||
courseCoverurl.value = "";
|
|
||||||
}
|
|
||||||
if (fileList) {
|
if (fileList) {
|
||||||
fileList.value = [];
|
fileList.value = [];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
formRef,
|
|
||||||
formState,
|
formState,
|
||||||
visibilityOptions,
|
visibilityOptions,
|
||||||
resetForm
|
resetForm,
|
||||||
|
formRef,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
111
src/hooks/useFetchCourseList.js
Normal file
111
src/hooks/useFetchCourseList.js
Normal file
@@ -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,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -21,10 +21,12 @@ export function useMediaComponent(props, emit) {
|
|||||||
// Update form values and emit changes
|
// Update form values and emit changes
|
||||||
const updateFormValue = (field, value) => {
|
const updateFormValue = (field, value) => {
|
||||||
localDialogVideoForm.value[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 {
|
return {
|
||||||
localDialogVideoForm,
|
localDialogVideoForm,
|
||||||
|
|||||||
440
src/views/courselibrary/components/courseTag.vue
Normal file
440
src/views/courselibrary/components/courseTag.vue
Normal file
@@ -0,0 +1,440 @@
|
|||||||
|
<template>
|
||||||
|
<div class="tag-container" @click="handleContainerClick">
|
||||||
|
<el-select
|
||||||
|
size="large"
|
||||||
|
style="width: 100%"
|
||||||
|
v-model="selectedTags"
|
||||||
|
multiple
|
||||||
|
filterable
|
||||||
|
value-key="id"
|
||||||
|
remote
|
||||||
|
reserve-keyword
|
||||||
|
:remote-method="debouncedSearch"
|
||||||
|
:loading="loading"
|
||||||
|
placeholder="回车创建新标签"
|
||||||
|
:no-data-text="'无此标签,按回车键创建'"
|
||||||
|
@remove-tag="handleTagRemove"
|
||||||
|
@change="handleSelectionChange"
|
||||||
|
@keyup.enter="handleEnterKey"
|
||||||
|
@keyup.delete="handleDeleteKey"
|
||||||
|
@focus="handleFocus"
|
||||||
|
ref="tagSelectRef"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in searchResults"
|
||||||
|
:key="item.id"
|
||||||
|
:label="item.tagName"
|
||||||
|
:value="item"
|
||||||
|
:disabled="isTagDisabled(item)"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
<!-- 添加标签计数显示 -->
|
||||||
|
<div class="tag-count">{{ selectedTags.length }}/5</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, reactive, computed, watch, onMounted, nextTick } from "vue";
|
||||||
|
import { debounce } from "lodash";
|
||||||
|
import { $message } from "@/utils/useMessage";
|
||||||
|
import apiCourseTag from "@/api/modules/courseTag.js";
|
||||||
|
import { useStore } from "vuex";
|
||||||
|
import { ElSelect, ElOption } from "element-plus";
|
||||||
|
|
||||||
|
// Props
|
||||||
|
const props = defineProps({
|
||||||
|
courseId: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
sysTypeList: {
|
||||||
|
type: Array,
|
||||||
|
required: true,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
maxTags: {
|
||||||
|
type: Number,
|
||||||
|
default: 5,
|
||||||
|
},
|
||||||
|
// 添加:接收初始标签数据的props
|
||||||
|
initialTags: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Emits
|
||||||
|
const emit = defineEmits(["change", "focus"]);
|
||||||
|
|
||||||
|
// Store
|
||||||
|
const store = useStore();
|
||||||
|
const userInfo = computed(() => store.getters.userInfo);
|
||||||
|
|
||||||
|
// Refs
|
||||||
|
const tagSelectRef = ref(null);
|
||||||
|
|
||||||
|
// Reactive data
|
||||||
|
const selectedTags = ref([]);
|
||||||
|
const searchResults = ref([]);
|
||||||
|
const loading = ref(false);
|
||||||
|
const tagMap = reactive(new Map());
|
||||||
|
const params = reactive({});
|
||||||
|
const tag = reactive({});
|
||||||
|
// 添加临时存储用于回滚
|
||||||
|
const previousTags = ref([]);
|
||||||
|
|
||||||
|
const displayTags = computed(() => {
|
||||||
|
return selectedTags.value
|
||||||
|
.map((tag) => (typeof tag === "object" ? tag : tagMap.get(tag)))
|
||||||
|
.filter(Boolean);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 创建防抖搜索函数
|
||||||
|
let debouncedSearch = debounce(doSearch, 500);
|
||||||
|
|
||||||
|
// Lifecycle hooks
|
||||||
|
onMounted(() => {
|
||||||
|
console.log(
|
||||||
|
"----------sysTypeList.length---------->" + props.sysTypeList.length
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
"----------sysTypeList.length---------->" + (props.sysTypeList.length === 0)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (props.initialTags && props.initialTags.length > 0) {
|
||||||
|
selectedTags.value = props.initialTags;
|
||||||
|
searchResults.value = props.initialTags;
|
||||||
|
// 将初始标签添加到tagMap中,确保删除功能正常
|
||||||
|
props.initialTags.forEach((tag) => {
|
||||||
|
if (tag.id) {
|
||||||
|
tagMap.set(tag.id, tag);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Watchers
|
||||||
|
watch(
|
||||||
|
() => props.courseId,
|
||||||
|
(newVal) => {
|
||||||
|
resetTagState();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.initialTags,
|
||||||
|
(newVal) => {
|
||||||
|
selectedTags.value = newVal || [];
|
||||||
|
searchResults.value = newVal || [];
|
||||||
|
// 清空旧缓存
|
||||||
|
tagMap.clear();
|
||||||
|
newVal.forEach((tag) => {
|
||||||
|
if (tag.id) tagMap.set(tag.id, tag);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.sysTypeList,
|
||||||
|
() => {
|
||||||
|
// 只有在已选择分类且有焦点时才重新加载
|
||||||
|
if (
|
||||||
|
props.sysTypeList.length > 0 &&
|
||||||
|
tagSelectRef.value &&
|
||||||
|
tagSelectRef.value.visible
|
||||||
|
) {
|
||||||
|
doSearch("");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
// Methods
|
||||||
|
// 新增:检查标签是否应该被禁用
|
||||||
|
function isTagDisabled(tag) {
|
||||||
|
// 如果标签已经被选中,不应该禁用(允许取消选择)
|
||||||
|
const isSelected = selectedTags.value.some(
|
||||||
|
(selectedTag) => selectedTag.id === tag.id
|
||||||
|
);
|
||||||
|
if (isSelected) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// 如果标签未被选中且已达到最大数量,则禁用
|
||||||
|
return selectedTags.value.length >= props.maxTags;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增:处理输入框获得焦点事件
|
||||||
|
async function handleFocus() {
|
||||||
|
previousTags.value = [...selectedTags.value];
|
||||||
|
// 当输入框获得焦点时,加载默认的搜索结果
|
||||||
|
if (props.sysTypeList.length > 0) {
|
||||||
|
await doSearch("");
|
||||||
|
}
|
||||||
|
emit("focus");
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleContainerClick() {
|
||||||
|
// 容器点击时也触发焦点事件
|
||||||
|
emit("focus");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增:重置标签状态的方法
|
||||||
|
function resetTagState() {
|
||||||
|
selectedTags.value = [];
|
||||||
|
searchResults.value = [];
|
||||||
|
tagMap.clear();
|
||||||
|
loading.value = false;
|
||||||
|
// 清空 params 对象
|
||||||
|
Object.keys(params).forEach((key) => delete params[key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleTagRemove(tagId) {
|
||||||
|
selectedTags.value = selectedTags.value.filter((id) => id !== tagId);
|
||||||
|
emit("change", displayTags.value);
|
||||||
|
clearInput();
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeTag(tagId) {
|
||||||
|
handleTagRemove(tagId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增:处理删除键事件
|
||||||
|
function handleDeleteKey(event) {
|
||||||
|
// 如果输入框内容为空,不执行任何搜索
|
||||||
|
if (!event.target.value.trim()) {
|
||||||
|
searchResults.value = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//按回车键,创建新标签
|
||||||
|
function handleEnterKey(event) {
|
||||||
|
const inputVal = event.target.value?.trim();
|
||||||
|
if (!inputVal) return;
|
||||||
|
// 检查是否与已选择的标签重复
|
||||||
|
const isDuplicate = selectedTags.value.some(
|
||||||
|
(tag) => tag.tagName === inputVal
|
||||||
|
);
|
||||||
|
if (isDuplicate) {
|
||||||
|
$message.warning("该标签已存在,无需重复创建");
|
||||||
|
event.target.value = "";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!isDuplicate && inputVal && selectedTags.value.length < props.maxTags) {
|
||||||
|
createNewTag(event.target.value.trim());
|
||||||
|
clearInput();
|
||||||
|
} else if (selectedTags.value.length >= props.maxTags) {
|
||||||
|
$message.warning("最多只能添加5个标签");
|
||||||
|
clearInput();
|
||||||
|
} else {
|
||||||
|
clearInput();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增:处理选择变化事件
|
||||||
|
function handleSelectionChange(newValues) {
|
||||||
|
// 检查每个标签对象是否完整
|
||||||
|
newValues.forEach((tag, index) => {
|
||||||
|
if (!tag.tagName) {
|
||||||
|
console.error(`第${index}个标签缺少tagName:`, tag);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 检查数量限制
|
||||||
|
if (newValues.length > props.maxTags) {
|
||||||
|
$message.warning(`最多只能选择${props.maxTags}个标签`);
|
||||||
|
// 回滚到之前的状态
|
||||||
|
selectedTags.value = [...previousTags.value];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新前保存当前状态
|
||||||
|
previousTags.value = [...newValues];
|
||||||
|
emit("change", displayTags.value);
|
||||||
|
|
||||||
|
clearInput();
|
||||||
|
nextTick(() => {
|
||||||
|
if (tagSelectRef.value) {
|
||||||
|
tagSelectRef.value.visible = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearInput() {
|
||||||
|
if (tagSelectRef.value) {
|
||||||
|
const input = tagSelectRef.value.$refs.input;
|
||||||
|
if (input) {
|
||||||
|
input.value = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//创建新标签
|
||||||
|
async function createNewTag(tagName) {
|
||||||
|
// 标签不能超过八个字
|
||||||
|
if (tagName.length > 8) {
|
||||||
|
$message.error("标签不能超过8个字");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 检查标签是否在下拉框中已存在
|
||||||
|
const isExistInSearch = searchResults.value.some(
|
||||||
|
(tag) => tag.tagName === tagName
|
||||||
|
);
|
||||||
|
if (isExistInSearch) {
|
||||||
|
$message.warning("已存在此标签,请选择");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 首先检查是否与已选择的标签重复
|
||||||
|
const isDuplicate = selectedTags.value.some((tag) => tag.tagName === tagName);
|
||||||
|
if (isDuplicate) {
|
||||||
|
$message.warning("该标签已存在,无需重复创建");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 标签格式验证:仅支持中文、英文、数字、下划线、中横线
|
||||||
|
const tagPattern = /^[\u4e00-\u9fa5a-zA-Z0-9_-]+$/;
|
||||||
|
if (!tagPattern.test(tagName)) {
|
||||||
|
$message.error(
|
||||||
|
"标签名称仅支持中文、英文、数字、下划线(_)和中横线(-),不支持空格、点和特殊字符"
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 添加标签数量限制检查
|
||||||
|
if (selectedTags.value.length >= props.maxTags) {
|
||||||
|
$message.warning("最多只能添加5个标签");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
params.courseId = props.courseId;
|
||||||
|
params.tagName = tagName;
|
||||||
|
// 分类
|
||||||
|
if (props.sysTypeList.length > 0) {
|
||||||
|
params.sysType1 = props.sysTypeList[0]; //一级的id
|
||||||
|
}
|
||||||
|
if (props.sysTypeList.length > 1) {
|
||||||
|
params.sysType2 = props.sysTypeList[1]; //二级的id
|
||||||
|
}
|
||||||
|
if (props.sysTypeList.length > 2) {
|
||||||
|
params.sysType3 = props.sysTypeList[2]; //三级的id
|
||||||
|
}
|
||||||
|
const { result: newTag } = await apiCourseTag.createTag(params);
|
||||||
|
$message.success("标签创建成功");
|
||||||
|
|
||||||
|
selectedTags.value = [...selectedTags.value, newTag];
|
||||||
|
// 更新搜索结果的逻辑保持不变
|
||||||
|
searchResults.value = [newTag, ...searchResults.value];
|
||||||
|
tagMap.set(newTag.id, newTag);
|
||||||
|
emit("change", displayTags.value);
|
||||||
|
|
||||||
|
nextTick(() => {
|
||||||
|
// 强制重新设置selectedTags来触发更新
|
||||||
|
const tempTags = [...selectedTags.value];
|
||||||
|
selectedTags.value = [];
|
||||||
|
nextTick(() => {
|
||||||
|
selectedTags.value = tempTags;
|
||||||
|
});
|
||||||
|
if (tagSelectRef.value) {
|
||||||
|
tagSelectRef.value.visible = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改doSearch方法,添加搜索结果为空时的提示
|
||||||
|
async function doSearch(query) {
|
||||||
|
// 不再在空查询时清空搜索结果
|
||||||
|
// if (!query.trim()) {
|
||||||
|
// searchResults.value = []
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
console.log("---- doSearch ------ query = " + query);
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
// 获取 typeId:取 sysTypeList 最后一个有效的值
|
||||||
|
const typeId =
|
||||||
|
props.sysTypeList.length > 2
|
||||||
|
? props.sysTypeList[2]
|
||||||
|
: props.sysTypeList.length > 1
|
||||||
|
? props.sysTypeList[1]
|
||||||
|
: props.sysTypeList.length > 0
|
||||||
|
? props.sysTypeList[0]
|
||||||
|
: null;
|
||||||
|
console.log(
|
||||||
|
"---- doSearch searchTags ------ query = " +
|
||||||
|
query +
|
||||||
|
" , typeId = " +
|
||||||
|
typeId
|
||||||
|
);
|
||||||
|
const { result: tags } = await apiCourseTag.searchTags({
|
||||||
|
tagName: query,
|
||||||
|
typeId: typeId,
|
||||||
|
});
|
||||||
|
console.log("-- searchTags 查询结果 tags = " + tags);
|
||||||
|
|
||||||
|
tags.forEach((item) => {
|
||||||
|
tagMap.set(item.id, item);
|
||||||
|
});
|
||||||
|
searchResults.value = tags;
|
||||||
|
// 当搜索结果为空时,提示用户可以按回车键创建标签
|
||||||
|
if (tags.length === 0) {
|
||||||
|
// $message.info('无此标签,按回车键创建')
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出方法供外部调用
|
||||||
|
defineExpose({
|
||||||
|
removeTag,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tag-container {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 添加标签计数样式 */
|
||||||
|
.tag-count {
|
||||||
|
position: absolute;
|
||||||
|
right: 10px;
|
||||||
|
top: 47%;
|
||||||
|
transform: translateY(-40%);
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
background: white;
|
||||||
|
padding: 0 5px;
|
||||||
|
pointer-events: none;
|
||||||
|
/* 添加高度限制 */
|
||||||
|
height: 25px;
|
||||||
|
line-height: 25px; /* 垂直居中文字 */
|
||||||
|
box-sizing: border-box; /* 确保padding包含在height内 */
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep(.el-select__tags) {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep(.el-tag) {
|
||||||
|
flex: 1 1 auto; /* 自动调整宽度 */ //min-width: 30%; /* 设置最小宽度 */
|
||||||
|
//max-width: 48%; /* 设置最大宽度,留出边距 */
|
||||||
|
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin-right: 8px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
justify-content: center;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep(.el-select__input) {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 60px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -4,9 +4,7 @@ import { ElButton, ElCheckbox, ElDialog, ElMessageBox } from "element-plus";
|
|||||||
import { $message } from "@/utils/useMessage";
|
import { $message } from "@/utils/useMessage";
|
||||||
import dragTable from "./dragTable.vue";
|
import dragTable from "./dragTable.vue";
|
||||||
import { ref, watch } from "vue";
|
import { ref, watch } from "vue";
|
||||||
defineOptions({
|
import { getType } from "@/hooks/useCreateCourseMaps";
|
||||||
name: "CreateCourse",
|
|
||||||
});
|
|
||||||
import { useCourseData } from "@/hooks/useCourseData";
|
import { useCourseData } from "@/hooks/useCourseData";
|
||||||
import chooseFileList from "@/components/CreatedCourse/chooseFileList.vue";
|
import chooseFileList from "@/components/CreatedCourse/chooseFileList.vue";
|
||||||
import VideoComp from "@/components/CreatedCourse/preview/VideoComp.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 PaperComp from "@/components/CreatedCourse/preview/PaperComp.vue";
|
||||||
import HomeWorkComp from "@/components/CreatedCourse/preview/HomeWorkComp.vue";
|
import HomeWorkComp from "@/components/CreatedCourse/preview/HomeWorkComp.vue";
|
||||||
import AccessComp from "@/components/CreatedCourse/preview/AccessComp.vue";
|
import AccessComp from "@/components/CreatedCourse/preview/AccessComp.vue";
|
||||||
import { getType } from "@/hooks/useCreateCourseMaps";
|
defineOptions({
|
||||||
|
name: "CreateCourse",
|
||||||
|
});
|
||||||
|
// 组件映射
|
||||||
const mapComponents = [
|
const mapComponents = [
|
||||||
VideoComp,
|
VideoComp,
|
||||||
AudioComp,
|
AudioComp,
|
||||||
@@ -34,22 +35,16 @@ const mapComponents = [
|
|||||||
// 使用课程数据hook
|
// 使用课程数据hook
|
||||||
const { courseMetadata, courseList, courseActionButtons, addChapter } =
|
const { courseMetadata, courseList, courseActionButtons, addChapter } =
|
||||||
useCourseData();
|
useCourseData();
|
||||||
const isPreview = ref(false);
|
|
||||||
const chooseItemData = ref({});
|
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({});
|
const copyChooseItemData = ref({});
|
||||||
|
|
||||||
|
// 监听课程索引变化,更新classId
|
||||||
watch(
|
watch(
|
||||||
() => courseMetadata.chooseIndex,
|
() => courseMetadata.chooseIndex,
|
||||||
(newVal) => {
|
(newVal) => {
|
||||||
classId.value = courseList.value[newVal].id;
|
if (courseList.value[newVal]) {
|
||||||
|
courseMetadata.classId = courseList.value[newVal].id || "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -57,46 +52,46 @@ watch(
|
|||||||
const courseOperations = {
|
const courseOperations = {
|
||||||
addVideo: () => {
|
addVideo: () => {
|
||||||
courseMetadata.resType = 10;
|
courseMetadata.resType = 10;
|
||||||
showDialog.value = true;
|
courseMetadata.showDialog = true;
|
||||||
},
|
},
|
||||||
addAudio: () => {
|
addAudio: () => {
|
||||||
courseMetadata.resType = 20;
|
courseMetadata.resType = 20;
|
||||||
showDialog.value = true;
|
courseMetadata.showDialog = true;
|
||||||
},
|
},
|
||||||
addDocument: () => {
|
addDocument: () => {
|
||||||
courseMetadata.resType = 40;
|
courseMetadata.resType = 40;
|
||||||
showDialog.value = true;
|
courseMetadata.showDialog = true;
|
||||||
isNext.value = false;
|
courseMetadata.isNext = false;
|
||||||
},
|
},
|
||||||
addImageText: () => {
|
addImageText: () => {
|
||||||
courseMetadata.resType = 41;
|
courseMetadata.resType = 41;
|
||||||
chooseItemData.value.resType = 41;
|
chooseItemData.value.resType = 41;
|
||||||
showSettingDialog.value = true;
|
courseMetadata.showSettingDialog = true;
|
||||||
},
|
},
|
||||||
addExternalLink: () => {
|
addExternalLink: () => {
|
||||||
courseMetadata.resType = 52;
|
courseMetadata.resType = 52;
|
||||||
chooseItemData.value.resType = 52;
|
chooseItemData.value.resType = 52;
|
||||||
showSettingDialog.value = true;
|
courseMetadata.showSettingDialog = true;
|
||||||
},
|
},
|
||||||
addScorm: () => {
|
addScorm: () => {
|
||||||
courseMetadata.resType = 50;
|
courseMetadata.resType = 50;
|
||||||
showDialog.value = true;
|
courseMetadata.showDialog = true;
|
||||||
isNext.value = false;
|
courseMetadata.isNext = false;
|
||||||
},
|
},
|
||||||
addExam: () => {
|
addExam: () => {
|
||||||
courseMetadata.resType = 61;
|
courseMetadata.resType = 61;
|
||||||
showDialog.value = true;
|
courseMetadata.showDialog = true;
|
||||||
isNext.value = false;
|
courseMetadata.isNext = false;
|
||||||
},
|
},
|
||||||
addHomework: () => {
|
addHomework: () => {
|
||||||
courseMetadata.resType = 60;
|
courseMetadata.resType = 60;
|
||||||
chooseItemData.value.resType = 60;
|
chooseItemData.value.resType = 60;
|
||||||
showSettingDialog.value = true;
|
courseMetadata.showSettingDialog = true;
|
||||||
},
|
},
|
||||||
addAssessment: () => {
|
addAssessment: () => {
|
||||||
courseMetadata.resType = 62;
|
courseMetadata.resType = 62;
|
||||||
chooseItemData.value.resType = 62;
|
chooseItemData.value.resType = 62;
|
||||||
showSettingDialog.value = true;
|
courseMetadata.showSettingDialog = true;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -105,9 +100,10 @@ const executeCourseOperation = (operationName, data) => {
|
|||||||
courseMetadata.chooseIndex = data;
|
courseMetadata.chooseIndex = data;
|
||||||
courseMetadata.selectionIndex = null;
|
courseMetadata.selectionIndex = null;
|
||||||
copyChooseItemData.value = {};
|
copyChooseItemData.value = {};
|
||||||
isPreview.value = false;
|
courseMetadata.isPreview = false;
|
||||||
isNext.value = true;
|
courseMetadata.isNext = true;
|
||||||
chooseItemData.value = {};
|
chooseItemData.value = {};
|
||||||
|
|
||||||
if (courseOperations[operationName]) {
|
if (courseOperations[operationName]) {
|
||||||
courseOperations[operationName](data);
|
courseOperations[operationName](data);
|
||||||
} else {
|
} else {
|
||||||
@@ -115,92 +111,130 @@ const executeCourseOperation = (operationName, data) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 选择项目处理
|
||||||
const chooseItem = (data) => {
|
const chooseItem = (data) => {
|
||||||
console.log(data);
|
|
||||||
chooseItemData.value = data;
|
chooseItemData.value = data;
|
||||||
if (!isNext.value) {
|
// 如果不需要下一步,则直接保存
|
||||||
|
if (!courseMetadata.isNext) {
|
||||||
saveContent();
|
saveContent();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
showSettingDialog.value = true;
|
courseMetadata.showSettingDialog = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 预览项目处理
|
||||||
const choosePreviewItem = (data) => {
|
const choosePreviewItem = (data) => {
|
||||||
chooseItemData.value = data;
|
chooseItemData.value = data;
|
||||||
showSettingDialog.value = true;
|
courseMetadata.showSettingDialog = true;
|
||||||
isPreview.value = true;
|
courseMetadata.isPreview = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 取消保存
|
||||||
const cancelSave = () => {
|
const cancelSave = () => {
|
||||||
showSettingDialog.value = false;
|
courseMetadata.showSettingDialog = false;
|
||||||
|
// 恢复原始数据
|
||||||
chooseItemData.value = copyChooseItemData.value;
|
chooseItemData.value = copyChooseItemData.value;
|
||||||
courseList.value[courseMetadata.chooseIndex].data[
|
if (
|
||||||
courseMetadata.selectionIndex
|
courseMetadata.chooseIndex !== null &&
|
||||||
] = chooseItemData.value;
|
courseMetadata.selectionIndex !== null &&
|
||||||
console.log(chooseItemData);
|
courseList.value[courseMetadata.chooseIndex] &&
|
||||||
};
|
courseList.value[courseMetadata.chooseIndex].data[
|
||||||
|
courseMetadata.selectionIndex
|
||||||
// 保存
|
]
|
||||||
const saveContent = () => {
|
) {
|
||||||
console.log(chooseItemData.value);
|
|
||||||
|
|
||||||
if (courseMetadata.selectionIndex !== null) {
|
|
||||||
courseList.value[courseMetadata.chooseIndex].data[
|
courseList.value[courseMetadata.chooseIndex].data[
|
||||||
courseMetadata.selectionIndex
|
courseMetadata.selectionIndex
|
||||||
] = chooseItemData.value;
|
] = 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) => {
|
const deleteRow = (data) => {
|
||||||
courseMetadata.chooseIndex = data.index;
|
courseMetadata.chooseIndex = data.index;
|
||||||
courseMetadata.selectionIndex = data.selectionIndex;
|
courseMetadata.selectionIndex = data.selectionIndex;
|
||||||
|
|
||||||
ElMessageBox.confirm(`确定删除${data.record.name}吗?`, "删除确认", {
|
ElMessageBox.confirm(`确定删除${data.record.name}吗?`, "删除确认", {
|
||||||
confirmButtonText: "确定",
|
confirmButtonText: "确定",
|
||||||
cancelButtonText: "取消",
|
cancelButtonText: "取消",
|
||||||
type: "error",
|
type: "error",
|
||||||
}).then(() => {
|
})
|
||||||
courseList.value[courseMetadata.chooseIndex].data.splice(
|
.then(() => {
|
||||||
courseMetadata.selectionIndex,
|
if (
|
||||||
1
|
courseList.value[courseMetadata.chooseIndex] &&
|
||||||
);
|
courseList.value[courseMetadata.chooseIndex].data
|
||||||
});
|
) {
|
||||||
|
courseList.value[courseMetadata.chooseIndex].data.splice(
|
||||||
|
courseMetadata.selectionIndex,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
// 用户取消删除
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 设置行
|
||||||
const settingRow = (data) => {
|
const settingRow = (data) => {
|
||||||
courseMetadata.chooseIndex = data.index;
|
courseMetadata.chooseIndex = data.index;
|
||||||
courseMetadata.selectionIndex = data.selectionIndex;
|
courseMetadata.selectionIndex = data.selectionIndex;
|
||||||
chooseItemData.value = data.record;
|
chooseItemData.value = { ...data.record }; // 创建副本避免直接引用
|
||||||
copyChooseItemData.value = JSON.parse(JSON.stringify(data.record));
|
copyChooseItemData.value = JSON.parse(JSON.stringify(data.record));
|
||||||
isPreview.value = false;
|
courseMetadata.isPreview = false;
|
||||||
showSettingDialog.value = true;
|
courseMetadata.showSettingDialog = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 预览行
|
||||||
const previewRow = (data) => {
|
const previewRow = (data) => {
|
||||||
courseMetadata.chooseIndex = data.index;
|
courseMetadata.chooseIndex = data.index;
|
||||||
courseMetadata.selectionIndex = data.selectionIndex;
|
courseMetadata.selectionIndex = data.selectionIndex;
|
||||||
chooseItemData.value = data.record;
|
chooseItemData.value = { ...data.record }; // 创建副本避免直接引用
|
||||||
copyChooseItemData.value = JSON.parse(JSON.stringify(data.record));
|
copyChooseItemData.value = JSON.parse(JSON.stringify(data.record));
|
||||||
isPreview.value = true;
|
courseMetadata.isPreview = true;
|
||||||
showSettingDialog.value = true;
|
courseMetadata.showSettingDialog = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 自定义考试
|
// 自定义考试
|
||||||
const chooseCusExam = (data) => {
|
const chooseCusExam = (data) => {
|
||||||
chooseItemData.value = data;
|
chooseItemData.value = data;
|
||||||
showSettingDialog.value = true;
|
courseMetadata.showSettingDialog = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 下一步处理
|
||||||
const handleNext = () => {
|
const handleNext = () => {
|
||||||
console.log($message);
|
|
||||||
$message.success("213");
|
$message.success("213");
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@@ -212,7 +246,7 @@ const handleNext = () => {
|
|||||||
<span>创建时间:{{ courseMetadata.createTime }}</span>
|
<span>创建时间:{{ courseMetadata.createTime }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="course-content">
|
<div class="course-content">
|
||||||
<div style="padding: 10px">
|
<div class="course-actions">
|
||||||
<el-button @click="addChapter">添加章</el-button>
|
<el-button @click="addChapter">添加章</el-button>
|
||||||
<el-checkbox style="margin-left: 10px">顺序学习</el-checkbox>
|
<el-checkbox style="margin-left: 10px">顺序学习</el-checkbox>
|
||||||
</div>
|
</div>
|
||||||
@@ -220,73 +254,85 @@ const handleNext = () => {
|
|||||||
<div>
|
<div>
|
||||||
<dragCollapse v-model:courseList="courseList">
|
<dragCollapse v-model:courseList="courseList">
|
||||||
<template #title="{ course }">{{ course.title }}</template>
|
<template #title="{ course }">{{ course.title }}</template>
|
||||||
<template #desc="{ course }"
|
<template #desc="{ course }">
|
||||||
>若课程只有一个章节,将不在学员端显示该章节名称</template
|
若课程只有一个章节,将不在学员端显示该章节名称
|
||||||
>
|
</template>
|
||||||
<template #default="{ course, index }">
|
<template #default="{ course, index }">
|
||||||
<div class="drag-course-btn-content">
|
<div class="drag-course-btn-content">
|
||||||
<el-button
|
<el-button
|
||||||
v-for="btn in courseActionButtons"
|
v-for="btn in courseActionButtons"
|
||||||
|
:key="btn.fun"
|
||||||
type="primary"
|
type="primary"
|
||||||
class="btn-item"
|
class="btn-item"
|
||||||
plain
|
plain
|
||||||
@click="executeCourseOperation(btn.fun, index)"
|
@click="executeCourseOperation(btn.fun, index)"
|
||||||
>{{ btn.label }}</el-button
|
|
||||||
>
|
>
|
||||||
|
{{ btn.label }}
|
||||||
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<!-- 修改:添加 groupId 和 tableId 属性以支持跨表格拖拽 -->
|
<!-- 添加 groupId 和 tableId 属性以支持跨表格拖拽 -->
|
||||||
<dragTable
|
<dragTable
|
||||||
:data="course.data"
|
:data="course.data"
|
||||||
:group-id="'course-chapters'"
|
group-id="course-chapters"
|
||||||
:table-id="'chapter-' + index"
|
:table-id="'chapter-' + index"
|
||||||
:index="index"
|
:index="index"
|
||||||
@delete="deleteRow"
|
@delete="deleteRow"
|
||||||
@setting="settingRow"
|
@setting="settingRow"
|
||||||
@preview="previewRow"
|
@preview="previewRow"
|
||||||
></dragTable>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</dragCollapse>
|
</dragCollapse>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 选择文件列表-->
|
<!-- 选择文件列表 -->
|
||||||
<el-dialog v-model="showDialog" :title="getType(courseMetadata.resType)">
|
<el-dialog
|
||||||
|
v-model="courseMetadata.showDialog"
|
||||||
|
:title="getType(courseMetadata.resType)"
|
||||||
|
>
|
||||||
<chooseFileList
|
<chooseFileList
|
||||||
v-if="showDialog"
|
v-if="courseMetadata.showDialog"
|
||||||
@chooseItem="chooseItem"
|
@chooseItem="chooseItem"
|
||||||
@choosePreviewItem="choosePreviewItem"
|
@choosePreviewItem="choosePreviewItem"
|
||||||
:resType="courseMetadata.resType"
|
:resType="courseMetadata.resType"
|
||||||
:showTablePreview="showTablePreview"
|
:showTablePreview="courseMetadata.showTablePreview"
|
||||||
@chooseCusExam="chooseCusExam"
|
@chooseCusExam="chooseCusExam"
|
||||||
></chooseFileList>
|
/>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
<!-- 设置预览弹窗 -->
|
<!-- 设置预览弹窗 -->
|
||||||
<el-dialog
|
<el-dialog
|
||||||
v-model="showSettingDialog"
|
v-model="courseMetadata.showSettingDialog"
|
||||||
:title="isPreview ? '预览' : getType(chooseItemData.resType)"
|
:title="
|
||||||
|
courseMetadata.isPreview ? '预览' : getType(chooseItemData.resType)
|
||||||
|
"
|
||||||
|
:fullscreen="chooseItemData.resType === 41"
|
||||||
>
|
>
|
||||||
<div style="max-height: 600px; overflow: auto">
|
<div class="component-preview">
|
||||||
<template v-for="item in mapComponents">
|
<template v-for="item in mapComponents" :key="item.name">
|
||||||
<component
|
<component
|
||||||
v-if="
|
v-if="
|
||||||
Number(chooseItemData.resType) === item.resType &&
|
Number(chooseItemData.resType) === item.resType &&
|
||||||
showSettingDialog
|
courseMetadata.showSettingDialog
|
||||||
"
|
"
|
||||||
:is="item"
|
:is="item"
|
||||||
v-model:dialogVideoForm="chooseItemData"
|
v-model:dialogVideoForm="chooseItemData"
|
||||||
:isPreview="isPreview"
|
:isPreview="courseMetadata.isPreview"
|
||||||
:classId="classId"
|
:classId="courseMetadata.classId"
|
||||||
></component>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<div class="dialog-footer">
|
<div class="dialog-footer">
|
||||||
<el-button @click="cancelSave()">取消</el-button>
|
<el-button @click="cancelSave()">取消</el-button>
|
||||||
<el-button type="primary" @click="saveContent()" v-if="!isPreview">
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
@click="saveContent()"
|
||||||
|
v-if="!courseMetadata.isPreview"
|
||||||
|
>
|
||||||
保存
|
保存
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
@@ -305,20 +351,32 @@ const handleNext = () => {
|
|||||||
.create-course {
|
.create-course {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 10px 20px;
|
padding: 10px 20px;
|
||||||
|
|
||||||
.course-header {
|
.course-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.course-actions {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.drag-course-btn-content {
|
.drag-course-btn-content {
|
||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
|
|
||||||
.btn-item + .btn-item {
|
.btn-item + .btn-item {
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.component-preview {
|
||||||
|
max-height: 600px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref, defineOptions, reactive, onMounted } from "vue";
|
import { ref, defineOptions, onMounted } from "vue";
|
||||||
|
import { Plus, Loading } from "@element-plus/icons-vue";
|
||||||
|
import courseTag from "./courseTag.vue";
|
||||||
import { $message } from "@/utils/useMessage";
|
import { $message } from "@/utils/useMessage";
|
||||||
import {
|
import {
|
||||||
ElForm,
|
ElForm,
|
||||||
@@ -11,9 +13,11 @@ import {
|
|||||||
ElInput,
|
ElInput,
|
||||||
ElCascader,
|
ElCascader,
|
||||||
ElSelect,
|
ElSelect,
|
||||||
|
ElSelectV2,
|
||||||
|
ElTreeSelect,
|
||||||
|
ElOption,
|
||||||
} from "element-plus";
|
} from "element-plus";
|
||||||
import { getClassTree } from "@/api/modules/newApi";
|
import FieldCloud from "@/components/FileCloud/index.vue";
|
||||||
import filecloud from "@/components/FileCloud/index.vue";
|
|
||||||
import { useUpload } from "@/hooks/useUpload";
|
import { useUpload } from "@/hooks/useUpload";
|
||||||
import { useCourseForm } from "@/hooks/useCourseForm";
|
import { useCourseForm } from "@/hooks/useCourseForm";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
@@ -21,68 +25,58 @@ const router = useRouter();
|
|||||||
defineOptions({
|
defineOptions({
|
||||||
name: "ProfessionalMode",
|
name: "ProfessionalMode",
|
||||||
});
|
});
|
||||||
|
|
||||||
// 使用上传hook
|
// 使用上传hook
|
||||||
const { fileList, loading, courseCoverurl, handleChange, beforeUpload } =
|
const { fileList, loading, handleChange, beforeUpload } = useUpload();
|
||||||
useUpload();
|
|
||||||
|
|
||||||
// 使用表单hook
|
// 使用表单hook
|
||||||
const { formRef, formState, visibilityOptions, resetForm } = useCourseForm();
|
const { formRef, formState, visibilityOptions, resetForm } = useCourseForm();
|
||||||
|
import { useFetchCourseList } from "@/hooks/useFetchCourseList";
|
||||||
|
const {
|
||||||
|
fetchClassTree,
|
||||||
|
getUserGroupList,
|
||||||
|
teachersList,
|
||||||
|
sysTypeListMap,
|
||||||
|
courseTags,
|
||||||
|
loadOrgNode,
|
||||||
|
curCourseId,
|
||||||
|
userGroupList,
|
||||||
|
sysTypeList,
|
||||||
|
} = useFetchCourseList();
|
||||||
|
|
||||||
// 表单相关
|
import { useMediaComponent } from "@/hooks/useMediaComponent";
|
||||||
const labelCol = { style: { width: "80px" } };
|
const { fileBaseUrl } = useMediaComponent({});
|
||||||
|
const handleTagsChange = (tags) => {
|
||||||
// 数据相关
|
console.log("父组件:", tags);
|
||||||
const data = ref({
|
// 限制最多5个标签
|
||||||
typeOption: [],
|
if (tags.length > 5) {
|
||||||
});
|
this.$message.warning("最多只能选择5个标签");
|
||||||
|
// 强制限制为5个
|
||||||
|
tags = tags.slice(0, 5);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let ids = "";
|
||||||
|
tags.forEach((tag) => {
|
||||||
|
console.log("父组件name : ", tag.tagName);
|
||||||
|
ids += tag.id + ",";
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const onTagFocus = () => {};
|
||||||
// 文件选择对话框
|
// 文件选择对话框
|
||||||
const dlgFileChoose = ref({
|
const dlgFileShow = ref(false);
|
||||||
show: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
// 课程信息
|
|
||||||
const courseInfo = ref({
|
|
||||||
id: "",
|
|
||||||
name: "",
|
|
||||||
orderStudy: false,
|
|
||||||
type: 10,
|
|
||||||
orgId: "",
|
|
||||||
coverImg: "",
|
|
||||||
source: 1,
|
|
||||||
forUsers: "",
|
|
||||||
forScene: "",
|
|
||||||
value: "",
|
|
||||||
tags: "",
|
|
||||||
keywords: "",
|
|
||||||
device: 3,
|
|
||||||
status: 1,
|
|
||||||
summary: "",
|
|
||||||
overview: "",
|
|
||||||
visible: true,
|
|
||||||
refId: "",
|
|
||||||
refType: "",
|
|
||||||
});
|
|
||||||
|
|
||||||
const fileUrl = process.env.VUE_APP_BASE_API1 + process.env.VUE_APP_FILE_PATH;
|
|
||||||
|
|
||||||
// 方法定义
|
// 方法定义
|
||||||
const chooseFile = () => {
|
const chooseFile = () => {
|
||||||
dlgFileChoose.value.show = true;
|
dlgFileShow.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const changeCourseImage = (img) => {
|
const changeCourseImage = (img) => {
|
||||||
if (!img.path) {
|
if (!img.path) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
dlgFileChoose.value.show = false;
|
dlgFileShow.value = false;
|
||||||
courseInfo.value.coverImg = img.path;
|
formState.coverImg = fileBaseUrl + img.path;
|
||||||
courseCoverurl.value = fileUrl + img.path;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const choseChoose = () => {
|
const choseChoose = () => {
|
||||||
dlgFileChoose.value.show = false;
|
dlgFileShow.value = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 表单提交
|
// 表单提交
|
||||||
@@ -103,18 +97,8 @@ const handleSubmit = () => {
|
|||||||
|
|
||||||
// 表单重置
|
// 表单重置
|
||||||
const handleReset = () => {
|
const handleReset = () => {
|
||||||
resetForm(courseCoverurl, fileList);
|
resetForm(fileList);
|
||||||
};
|
};
|
||||||
|
|
||||||
// API调用
|
|
||||||
const fetchApi = {
|
|
||||||
getClassTree: () => {
|
|
||||||
return getClassTree(1).then((res) => {
|
|
||||||
data.value.typeOption = res.result;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const next = () => {
|
const next = () => {
|
||||||
// 注意:这里的路由跳转需要正确引入和使用vue-router
|
// 注意:这里的路由跳转需要正确引入和使用vue-router
|
||||||
router.push({
|
router.push({
|
||||||
@@ -126,8 +110,10 @@ const next = () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
// 调用接口
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
fetchApi.getClassTree();
|
fetchClassTree(1);
|
||||||
|
getUserGroupList({ keyword: "", page: 1, pageSize: 500 });
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -148,12 +134,12 @@ onMounted(() => {
|
|||||||
<div class="professional-mode-form">
|
<div class="professional-mode-form">
|
||||||
<el-form-item
|
<el-form-item
|
||||||
label="课程名称"
|
label="课程名称"
|
||||||
prop="courseName"
|
prop="name"
|
||||||
:rules="[{ required: true, message: '请输入课程名称' }]"
|
:rules="[{ required: true, message: '请输入课程名称' }]"
|
||||||
>
|
>
|
||||||
<el-input
|
<el-input
|
||||||
size="large"
|
size="large"
|
||||||
v-model="formState.courseName"
|
v-model="formState.name"
|
||||||
placeholder="请输入课程名称"
|
placeholder="请输入课程名称"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@@ -167,7 +153,7 @@ onMounted(() => {
|
|||||||
size="large"
|
size="large"
|
||||||
v-model="formState.courseCategory"
|
v-model="formState.courseCategory"
|
||||||
placeholder="请选择课程分类"
|
placeholder="请选择课程分类"
|
||||||
:options="data.typeOption"
|
:options="sysTypeListMap"
|
||||||
:props="{
|
:props="{
|
||||||
label: 'name',
|
label: 'name',
|
||||||
value: 'id',
|
value: 'id',
|
||||||
@@ -177,17 +163,19 @@ onMounted(() => {
|
|||||||
|
|
||||||
<el-form-item
|
<el-form-item
|
||||||
label="资源归属"
|
label="资源归属"
|
||||||
prop="resourceBelong"
|
prop="orgName"
|
||||||
:rules="[{ required: true, message: '请选择资源归属' }]"
|
:rules="[{ required: true, message: '请选择资源归属' }]"
|
||||||
>
|
>
|
||||||
<el-select
|
<el-tree-select
|
||||||
size="large"
|
size="large"
|
||||||
v-model="formState.resourceBelong"
|
check-strictly
|
||||||
|
lazy
|
||||||
|
:load="loadOrgNode"
|
||||||
|
:render-after-expand="false"
|
||||||
|
v-model="formState.orgName"
|
||||||
|
:props="{ value: 'id', label: 'name', children: 'children' }"
|
||||||
placeholder="请选择资源归属"
|
placeholder="请选择资源归属"
|
||||||
>
|
/>
|
||||||
<el-option label="选项1" value="1"></el-option>
|
|
||||||
<el-option label="选项2" value="2"></el-option>
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item
|
<el-form-item
|
||||||
@@ -195,67 +183,65 @@ onMounted(() => {
|
|||||||
prop="lecturer"
|
prop="lecturer"
|
||||||
:rules="[{ required: true, message: '请选择授课教师' }]"
|
:rules="[{ required: true, message: '请选择授课教师' }]"
|
||||||
>
|
>
|
||||||
<el-select
|
<el-select-v2
|
||||||
size="large"
|
size="large"
|
||||||
|
filterable
|
||||||
|
multiple
|
||||||
v-model="formState.lecturer"
|
v-model="formState.lecturer"
|
||||||
|
:options="teachersList"
|
||||||
placeholder="请选择授课教师"
|
placeholder="请选择授课教师"
|
||||||
>
|
>
|
||||||
<el-option label="教师1" value="1"></el-option>
|
</el-select-v2>
|
||||||
<el-option label="教师2" value="2"></el-option>
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item
|
<el-form-item
|
||||||
label="目标人群"
|
label="目标人群"
|
||||||
prop="targetGroup"
|
prop="forUsers"
|
||||||
:rules="[{ required: true, message: '请输入目标人群' }]"
|
:rules="[{ required: true, message: '请输入目标人群' }]"
|
||||||
>
|
>
|
||||||
<el-input
|
<el-input
|
||||||
size="large"
|
size="large"
|
||||||
v-model="formState.targetGroup"
|
v-model="formState.forUsers"
|
||||||
placeholder="请输入目标人群"
|
placeholder="请输入目标人群"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item label="课程标签" prop="courseTags">
|
<el-form-item label="课程标签" prop="courseTags">
|
||||||
<el-select
|
<courseTag
|
||||||
size="large"
|
:courseId="curCourseId"
|
||||||
v-model="formState.courseTags"
|
:sysTypeList="sysTypeList"
|
||||||
multiple
|
:initialTags="courseTags"
|
||||||
filterable
|
@change="handleTagsChange"
|
||||||
allow-create
|
@focus="onTagFocus"
|
||||||
placeholder="请选择或输入课程标签"
|
style="width: 100%"
|
||||||
>
|
></courseTag>
|
||||||
<el-option label="标签1" value="标签1"></el-option>
|
|
||||||
<el-option label="标签2" value="标签2"></el-option>
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item label="受众" prop="audience">
|
<el-form-item label="受众" prop="crowds">
|
||||||
<el-select
|
<el-select
|
||||||
size="large"
|
size="large"
|
||||||
v-model="formState.audience"
|
v-model="formState.crowds"
|
||||||
placeholder="请选择受众"
|
placeholder="请选择受众"
|
||||||
|
multiple
|
||||||
|
value-key="id"
|
||||||
>
|
>
|
||||||
<el-option label="受众1" value="1"></el-option>
|
<el-option
|
||||||
<el-option label="受众2" value="2"></el-option>
|
v-for="item in userGroupList"
|
||||||
|
:key="item.id"
|
||||||
|
:label="item.name"
|
||||||
|
:value="item"
|
||||||
|
></el-option>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item label="可见性" prop="visibility">
|
<el-form-item label="观看设置" prop="device">
|
||||||
<el-radio-group v-model="formState.visibility">
|
<el-radio-group v-model="formState.device">
|
||||||
<el-radio
|
<el-radio
|
||||||
v-for="item in visibilityOptions"
|
v-for="item in visibilityOptions"
|
||||||
:key="item.value"
|
:key="item.value"
|
||||||
:label="item.value"
|
:label="item.value"
|
||||||
>
|
>
|
||||||
<span
|
{{ item.label }}
|
||||||
:style="{
|
|
||||||
color:
|
|
||||||
formState.visibility === item.value ? '#409eff' : '#000',
|
|
||||||
}"
|
|
||||||
>{{ item.label }}</span
|
|
||||||
>
|
|
||||||
</el-radio>
|
</el-radio>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@@ -279,18 +265,18 @@ onMounted(() => {
|
|||||||
:on-change="handleChange"
|
:on-change="handleChange"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
v-if="courseCoverurl"
|
v-if="formState.coverImg"
|
||||||
:src="courseCoverurl"
|
:src="formState.coverImg"
|
||||||
alt="avatar"
|
alt="avatar"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
/>
|
/>
|
||||||
<div v-else>
|
<div v-else class="text-center">
|
||||||
<el-icon v-if="loading"><Loading /></el-icon>
|
<Loading v-if="loading" class="w30" />
|
||||||
<el-icon v-else><Plus /></el-icon>
|
<Plus v-else class="w30" />
|
||||||
<div class="el-upload-text">上传图片</div>
|
<div class="el-upload-text">上传图片</div>
|
||||||
</div>
|
</div>
|
||||||
</el-upload>
|
</el-upload>
|
||||||
<el-button type="primary" link @click="chooseFile"
|
<el-button type="primary" link @click="chooseFile" class="ml10"
|
||||||
>选择封面</el-button
|
>选择封面</el-button
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
@@ -316,13 +302,13 @@ onMounted(() => {
|
|||||||
|
|
||||||
<el-form-item
|
<el-form-item
|
||||||
label="课程简介"
|
label="课程简介"
|
||||||
prop="courseIntro"
|
prop="summary"
|
||||||
:rules="[{ required: true, message: '请输入课程简介' }]"
|
:rules="[{ required: true, message: '请输入课程简介' }]"
|
||||||
>
|
>
|
||||||
<el-input
|
<el-input
|
||||||
type="textarea"
|
type="textarea"
|
||||||
size="large"
|
size="large"
|
||||||
v-model="formState.courseIntro"
|
v-model="formState.summary"
|
||||||
:rows="4"
|
:rows="4"
|
||||||
placeholder="请输入课程简介"
|
placeholder="请输入课程简介"
|
||||||
/>
|
/>
|
||||||
@@ -340,11 +326,11 @@ onMounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-form>
|
</el-form>
|
||||||
<filecloud
|
<field-cloud
|
||||||
:show="dlgFileChoose.show"
|
:show="dlgFileShow"
|
||||||
@choose="changeCourseImage"
|
@choose="changeCourseImage"
|
||||||
@close="choseChoose"
|
@close="choseChoose"
|
||||||
></filecloud>
|
></field-cloud>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user