mirror of
https://codeup.aliyun.com/67762337eccfc218f6110e0e/vue/fe-manage.git
synced 2025-12-06 09:26:44 +08:00
feat):(course 实现课程创建功能及文件云组件
- 添加创建课程页面,支持章节与节的嵌套结构 - 实现可折叠章节组件(dragCollapse),支持展开/收起与删除操作 - 实现可拖拽表格组件(dragTable),支持跨表格拖拽排序与编辑 - 引入文件云API模块,支持文件夹与文件的基本操作 - 添加文件类型图标样式文件(filetypes.css) - 新增文件选择弹窗组件(FileCloud),支持文件浏览与选择 - 优化common.scss样式文件,调整选择器缩进与渐变背景配置
This commit is contained in:
87
src/api/modules/filecloud.js
Normal file
87
src/api/modules/filecloud.js
Normal file
@@ -0,0 +1,87 @@
|
||||
import ajax from "./xajax";
|
||||
|
||||
/**
|
||||
* @param {文件夹} folder
|
||||
*/
|
||||
const list = function (folder) {
|
||||
return ajax.post("/systemapi/api/m/xfile/base/all/list", { folder });
|
||||
};
|
||||
|
||||
const findByName = function (name) {
|
||||
return ajax.post("/systemapi/api/m/xfile/base/all/find", { name });
|
||||
};
|
||||
|
||||
/**
|
||||
* 文件夹树
|
||||
*/
|
||||
const folderTree = function () {
|
||||
return ajax.get("/systemapi/api/m/xfile/base/folder/tree");
|
||||
};
|
||||
|
||||
/**
|
||||
* 创建文件夹
|
||||
* @param {*} data
|
||||
*/
|
||||
const folderCreate = function (data) {
|
||||
return ajax.post("/systemapi/api/m/xfile/base/folder/create", data);
|
||||
};
|
||||
|
||||
/**
|
||||
* 重命名
|
||||
* @param {*} id
|
||||
* @param {*} name
|
||||
*/
|
||||
const folderRename = function (id, name) {
|
||||
return ajax.post("/systemapi/api/m/xfile/base/folder/rename", { id, name });
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除文件夹
|
||||
* @param {*} id
|
||||
*/
|
||||
const folderDelete = function (id) {
|
||||
return ajax.del("/systemapi/api/m/xfile/base/folder/delete?id=" + id);
|
||||
};
|
||||
|
||||
/**
|
||||
* 文章分页查询
|
||||
* @param {*} data
|
||||
*/
|
||||
const filePageList = function (data) {
|
||||
return ajax.post("/systemapi/api/m/xfile/base/file/pagelist", data);
|
||||
};
|
||||
|
||||
const fileRename = function (id, name) {
|
||||
return ajax.post("/systemapi/api/m/xfile/base/file/rename", { id, name });
|
||||
};
|
||||
|
||||
const fileDelete = function (id, path) {
|
||||
return ajax.post("/systemapi/api/m/xfile/base/file/delete", { id, path });
|
||||
};
|
||||
|
||||
const fileMove = function (id, folderId) {
|
||||
return ajax.post("/systemapi/api/m/xfile/base/file/rename", { id, folderId });
|
||||
};
|
||||
|
||||
const fileDetail = function (id) {
|
||||
return ajax.get("/systemapi/api/m/xfile/base/file/detail?id=" + id);
|
||||
};
|
||||
|
||||
const fileSetDelete = function (id) {
|
||||
// return ajax.del("/systemapi/api/m/xfile/base/file/setdelete?id=" + id);
|
||||
};
|
||||
|
||||
export default {
|
||||
list,
|
||||
folderTree,
|
||||
folderCreate,
|
||||
folderRename,
|
||||
folderDelete,
|
||||
filePageList,
|
||||
fileRename,
|
||||
fileDelete,
|
||||
fileMove,
|
||||
fileDetail,
|
||||
fileSetDelete,
|
||||
findByName,
|
||||
};
|
||||
8
src/api/modules/newApi.js
Normal file
8
src/api/modules/newApi.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import http from "./newConfig";
|
||||
|
||||
// 课程内容
|
||||
export const getClassTree = function (sysResType) {
|
||||
return http.get(
|
||||
`/systemapi/xboe/type/tree-list?sysResType=${sysResType}&status=1`
|
||||
);
|
||||
};
|
||||
107
src/api/modules/newConfig.js
Normal file
107
src/api/modules/newConfig.js
Normal file
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* @Author: lixg lixg@dongwu-inc.com
|
||||
* @Date: 2022-11-21 14:32:52
|
||||
* @LastEditors: lixg lixg@dongwu-inc.com
|
||||
* @LastEditTime: 2023-01-04 13:49:54
|
||||
* @FilePath: /fe-manage/src/api/config.js
|
||||
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
|
||||
*/
|
||||
import { message } from "ant-design-vue";
|
||||
import axios from "axios";
|
||||
import router from "@/router";
|
||||
import { REFRESH_TOKEN_API } from "@/api/ThirdApi";
|
||||
import { boeRequest } from "@/api/request";
|
||||
// import { getCookie } from '../api/method'
|
||||
// const Qs = require("qs");
|
||||
|
||||
// axios.defaults.headers.post["Content-Type"] =
|
||||
// "application/x-www-form-urlencoded";
|
||||
export const FILE_UPLOAD_URL = process.env.VUE_APP_BASE_API + "/file/upload";
|
||||
export const BATCH_IMPORT_SCORE =
|
||||
process.env.VUE_APP_BASE_API + "/admin/offcourse/batchImportScore";
|
||||
axios.defaults.withCredentials = true;
|
||||
const http = axios.create({
|
||||
// baseURL: process.env.VUE_APP_BASE_API,
|
||||
timeout: 1000 * 15,
|
||||
// headers: { "Content-Type": "multipart/form-data" },
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
|
||||
http.interceptors.request.use(
|
||||
(config) => {
|
||||
// console.log("config", config);
|
||||
// const token = localStorage.getItem("token");
|
||||
// // const token = getCookie('token')
|
||||
// // console.log('token', token)
|
||||
// if (token) {
|
||||
// config.headers.token = token; //测试1111
|
||||
// } else {
|
||||
// console.log("当前请求页面无token,请执行操作!!!");
|
||||
|
||||
// // 此处测试默认配置token
|
||||
// config.headers.token =
|
||||
// "eyJ0eXBlIjoidG9rZW4iLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC91LmJvZS5jb20iLCJpYXQiOjE2NzAxNTMxMDMsImV4cCI6MTY3MDE2MDMwMywiR2l2ZW5OYW1lIjoiYm9ldSIsInVzZXJJZCI6IjZCMDQ5RkFGLUMzMTQtN0NDRi0wRDI4LTBEMjNGNEM0MjUzMSIsInVJZCI6Ijk2NTM0MjAyNzQ5NzYwNzE2OCIsInBlcm1pc3Npb24iOiIifQ==.c937b2d3a59cbab2136fdde55fd38f06bdff041212aab0fa6741bc4be41e28a7";
|
||||
// // }
|
||||
return config;
|
||||
},
|
||||
(err) => {
|
||||
console.log("登陆前拦截", err);
|
||||
return Promise.reject(err);
|
||||
}
|
||||
);
|
||||
|
||||
http.interceptors.response.use(
|
||||
(response) => {
|
||||
// console.log('response', response)
|
||||
let {
|
||||
data: { code, msg, show, status },
|
||||
} = response;
|
||||
|
||||
if (!code && status) {
|
||||
code = status;
|
||||
}
|
||||
if (code === 0 || code === 200) {
|
||||
return response.data;
|
||||
}
|
||||
if (code === 1000) {
|
||||
window.location.href =
|
||||
process.env.VUE_APP_LOGIN_URL +
|
||||
encodeURIComponent(
|
||||
window.location.protocol +
|
||||
process.env.VUE_APP_BOE_API_URL +
|
||||
process.env.VUE_APP_BASE +
|
||||
router.currentRoute.value.fullPath
|
||||
);
|
||||
// TODO token过期后退出登录 清空当前用户标记 - 为了刷新页面使用
|
||||
localStorage.removeItem("refreshPage");
|
||||
return Promise.reject(response);
|
||||
}
|
||||
if (code === 1001) {
|
||||
window.location.href =
|
||||
process.env.VUE_APP_LOGIN_URL +
|
||||
encodeURIComponent(
|
||||
window.location.protocol +
|
||||
process.env.VUE_APP_BOE_API_URL +
|
||||
process.env.VUE_APP_BASE +
|
||||
router.currentRoute.value.fullPath
|
||||
);
|
||||
return Promise.reject(response);
|
||||
}
|
||||
show ? message.error(msg) : message.error("系统接口数据异常,请联系管理员");
|
||||
console.log("api %o", msg);
|
||||
return Promise.reject(response);
|
||||
},
|
||||
function (error) {
|
||||
if (error.message == "timeout of 1ms exceeded") {
|
||||
message.destroy();
|
||||
message.error("请求超时");
|
||||
}
|
||||
console.log("api error %o", error);
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
export default http;
|
||||
export function setHttpTimeout(newTimeout) {
|
||||
http.defaults.timeout = newTimeout;
|
||||
}
|
||||
187
src/api/modules/xajax.js
Normal file
187
src/api/modules/xajax.js
Normal file
@@ -0,0 +1,187 @@
|
||||
import axios from "axios";
|
||||
import qs from "qs";
|
||||
import { notification, Modal, message } from "ant-design-vue";
|
||||
|
||||
// 登录重定向 URL(可根据环境变量配置)
|
||||
const ReLoginUrl = process.env.VUE_APP_LOGIN_URL || "/login";
|
||||
const TokenName = "XBOE-Access-Token";
|
||||
|
||||
// JSON 请求实例(Content-Type: application/json)
|
||||
const jsonRequest = axios.create({
|
||||
headers: { "Content-Type": "application/json;charset=utf-8" },
|
||||
// baseURL: process.env.VUE_APP_BASE_API,
|
||||
timeout: 60000,
|
||||
});
|
||||
|
||||
// 请求拦截器 - 不再携带 token(你要求移除 getToken)
|
||||
jsonRequest.interceptors.request.use(
|
||||
(config) => {
|
||||
// ⚠️ 已移除 getToken 相关逻辑
|
||||
// 如果后续需要手动加 token,可在此处添加:
|
||||
// config.headers[TokenName] = 'your-token';
|
||||
return config;
|
||||
},
|
||||
(error) => {
|
||||
console.error("Request error:", error);
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
// 响应拦截器
|
||||
jsonRequest.interceptors.response.use(
|
||||
(res) => {
|
||||
const code = res.data.status || 200;
|
||||
|
||||
if (code === 200) {
|
||||
return res.data;
|
||||
} else {
|
||||
if (code === 6001 || code === 401 || code === 402) {
|
||||
Modal.warning({
|
||||
title: "登录失效",
|
||||
content: "您已被登出,可以取消继续留在该页面,或者重新登录",
|
||||
okText: "重新登录",
|
||||
onOk() {
|
||||
window.location.href = ReLoginUrl;
|
||||
},
|
||||
});
|
||||
} else if (code === 403) {
|
||||
message.error("当前操作没有权限");
|
||||
} else if (code === 302) {
|
||||
window.location.href = ReLoginUrl;
|
||||
} else {
|
||||
// 其他业务错误,直接返回数据供调用方处理
|
||||
return res.data; // 不 throw 错误,方便前端自定义处理
|
||||
}
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
console.error("Response error:", error);
|
||||
let msg = "未知错误,请稍后重试";
|
||||
|
||||
if (error.message === "Network Error") {
|
||||
msg = "网络异常,请检查网络连接";
|
||||
} else if (error.message.includes("timeout")) {
|
||||
msg = "系统接口请求超时";
|
||||
} else if (error.message.includes("Request failed with status code")) {
|
||||
const statusCode = error.message.substr(-3);
|
||||
msg = `系统接口 ${statusCode} 异常`;
|
||||
if (statusCode === "500") {
|
||||
notification.error({
|
||||
message: "服务错误",
|
||||
description: "服务器内部错误,请联系管理员。",
|
||||
duration: 5,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
message.error(msg, 5);
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
// Form 请求实例(x-www-form-urlencoded)
|
||||
const formRequest = axios.create({
|
||||
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
||||
// baseURL: process.env.VUE_APP_BASE_API,
|
||||
timeout: 10000,
|
||||
});
|
||||
|
||||
// 请求拦截器(form)
|
||||
formRequest.interceptors.request.use(
|
||||
(config) => {
|
||||
// 同样不带 token
|
||||
return config;
|
||||
},
|
||||
(error) => {
|
||||
console.error("Form request error:", error);
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
// 响应拦截器(form)
|
||||
formRequest.interceptors.response.use(
|
||||
(res) => {
|
||||
const code = res.data.status || 200;
|
||||
|
||||
if (code === 200) {
|
||||
return res.data;
|
||||
} else {
|
||||
if (code === 6001 || code === 401 || code === 402) {
|
||||
Modal.warning({
|
||||
title: "登录已过期",
|
||||
content: "登录状态无效,即将跳转至登录页",
|
||||
okText: "确认",
|
||||
onOk() {
|
||||
window.location.href = ReLoginUrl;
|
||||
},
|
||||
});
|
||||
} else if (code === 403) {
|
||||
message.error("暂无权限执行此操作");
|
||||
} else if (code === 302) {
|
||||
window.location.href = ReLoginUrl;
|
||||
} else {
|
||||
return res.data; // 返回原始数据供业务判断
|
||||
}
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
console.error("Form response error:", error);
|
||||
let msg = "请求失败";
|
||||
|
||||
if (error.message === "Network Error") {
|
||||
msg = "网络连接失败";
|
||||
} else if (error.message.includes("timeout")) {
|
||||
msg = "请求超时";
|
||||
} else if (error.message.includes("Request failed with status code")) {
|
||||
msg = `服务端异常 (${error.message.slice(-3)})`;
|
||||
}
|
||||
|
||||
message.error(msg, 5);
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
// ================== API 方法封装 ==================
|
||||
|
||||
const requestJson = jsonRequest.request;
|
||||
|
||||
const get = (url, params, config) => {
|
||||
const finalConfig = { ...config, params };
|
||||
return formRequest.get(url, finalConfig);
|
||||
};
|
||||
|
||||
const post = (url, data, config) => {
|
||||
return formRequest.post(url, qs.stringify(data), config);
|
||||
};
|
||||
|
||||
const postForm = (url, data, config) => {
|
||||
return formRequest.post(url, data, config); // 不 stringify,用于上传文件等
|
||||
};
|
||||
|
||||
const postJson = jsonRequest.post;
|
||||
const put = (url, data, config) => {
|
||||
return formRequest.put(url, qs.stringify(data), config);
|
||||
};
|
||||
const putJson = jsonRequest.put;
|
||||
const patch = (url, data, config) => {
|
||||
return formRequest.patch(url, qs.stringify(data), config);
|
||||
};
|
||||
const patchJson = jsonRequest.patch;
|
||||
const del = (url, config) => {
|
||||
return formRequest.delete(url, config);
|
||||
};
|
||||
|
||||
// 导出统一接口
|
||||
export default {
|
||||
request: jsonRequest.request, // 通用 request 方法
|
||||
requestJson,
|
||||
get,
|
||||
post,
|
||||
postForm,
|
||||
postJson,
|
||||
put,
|
||||
putJson,
|
||||
patch,
|
||||
patchJson,
|
||||
del,
|
||||
};
|
||||
BIN
src/assets/images/filetypes.png
Normal file
BIN
src/assets/images/filetypes.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 110 KiB |
@@ -299,8 +299,8 @@ textarea {
|
||||
display: inline-block;
|
||||
|
||||
.ant-select:not(.ant-select-customize-input)
|
||||
.ant-select-selector
|
||||
.ant-select-selection-search-input {
|
||||
.ant-select-selector
|
||||
.ant-select-selection-search-input {
|
||||
background-color: rgba(255, 255, 255, 0);
|
||||
border: none;
|
||||
}
|
||||
@@ -317,7 +317,7 @@ textarea {
|
||||
.ant-select-focused:not(.ant-select-disabled).ant-select:not(
|
||||
.ant-select-customize-input
|
||||
)
|
||||
.ant-select-selector {
|
||||
.ant-select-selector {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
@@ -561,8 +561,8 @@ textarea {
|
||||
}
|
||||
|
||||
.ant-table-tbody
|
||||
> tr:hover:not(.ant-table-expanded-row):not(.ant-table-row-selected)
|
||||
> td {
|
||||
> tr:hover:not(.ant-table-expanded-row):not(.ant-table-row-selected)
|
||||
> td {
|
||||
background: #f6f9fd;
|
||||
}
|
||||
|
||||
@@ -678,9 +678,9 @@ textarea {
|
||||
width: 100%;
|
||||
height: 68px;
|
||||
background: linear-gradient(
|
||||
0deg,
|
||||
rgba(78, 166, 255, 0) 0%,
|
||||
rgba(78, 166, 255, 0.2) 100%
|
||||
0deg,
|
||||
rgba(78, 166, 255, 0) 0%,
|
||||
rgba(78, 166, 255, 0.2) 100%
|
||||
);
|
||||
}
|
||||
|
||||
@@ -697,25 +697,25 @@ textarea {
|
||||
.ant-modal {
|
||||
.modalHeader {
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(103, 64, 255, 0.2) 0%,
|
||||
rgba(166, 168, 255, 0) 100%
|
||||
180deg,
|
||||
rgba(103, 64, 255, 0.2) 0%,
|
||||
rgba(166, 168, 255, 0) 100%
|
||||
) !important;
|
||||
}
|
||||
|
||||
.top {
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(103, 64, 255, 0.2) 0%,
|
||||
rgba(166, 168, 255, 0) 100%
|
||||
180deg,
|
||||
rgba(103, 64, 255, 0.2) 0%,
|
||||
rgba(166, 168, 255, 0) 100%
|
||||
) !important;
|
||||
}
|
||||
|
||||
.del_header {
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(103, 64, 255, 0.2) 0%,
|
||||
rgba(166, 168, 255, 0) 100%
|
||||
180deg,
|
||||
rgba(103, 64, 255, 0.2) 0%,
|
||||
rgba(166, 168, 255, 0) 100%
|
||||
) !important;
|
||||
}
|
||||
}
|
||||
@@ -736,4 +736,33 @@ textarea {
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
//loading--------------------------------------------------------
|
||||
.el-select,
|
||||
.el-cascader {
|
||||
width: 100%;
|
||||
}
|
||||
////loading--------------------------------------------------------
|
||||
//
|
||||
//.default-form {
|
||||
// .ant-form-item {
|
||||
// .ant-form-item-label {
|
||||
// line-height: 40px;
|
||||
// }
|
||||
// .ant-form-item-control {
|
||||
// .ant-input,
|
||||
// .ant-select-selector {
|
||||
// width: 100%;
|
||||
// line-height: 40px;
|
||||
//
|
||||
// height: 40px;
|
||||
// //border-radius: 8px;
|
||||
// }
|
||||
// .ant-form-item-control-input-content {
|
||||
// line-height: 40px;
|
||||
// }
|
||||
// .ant-checkbox-group,
|
||||
// .ant-radio-group {
|
||||
// line-height: 40px;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
280
src/assets/scss/filetypes.css
Normal file
280
src/assets/scss/filetypes.css
Normal file
@@ -0,0 +1,280 @@
|
||||
.ft {
|
||||
display: block;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
background: url(../images/filetypes.png) no-repeat 0 0;
|
||||
}
|
||||
.ft_small {
|
||||
display: block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
float: left;
|
||||
background: url(../images/filetypes.png) no-repeat 0 0;
|
||||
margin-right: 3px;
|
||||
}
|
||||
|
||||
.ft_null {
|
||||
background-position: 0 0px;
|
||||
}
|
||||
.ft_small_null {
|
||||
background-position: -164px -48px;
|
||||
}
|
||||
.ft_folder {
|
||||
background-position: 0 -124px;
|
||||
}
|
||||
.ft_small_folder {
|
||||
background-position: -164px -172px;
|
||||
}
|
||||
.ft_ai {
|
||||
background-position: 0 -248px;
|
||||
}
|
||||
.ft_small_ai {
|
||||
background-position: -164px -296px;
|
||||
}
|
||||
.ft_aif {
|
||||
background-position: 0 -372px;
|
||||
}
|
||||
.ft_small_aif {
|
||||
background-position: -164px -420px;
|
||||
}
|
||||
.ft_aiff {
|
||||
background-position: 0 -496px;
|
||||
}
|
||||
.ft_small_aiff {
|
||||
background-position: -164px -544px;
|
||||
}
|
||||
.ft_asp {
|
||||
background-position: 0 -620px;
|
||||
}
|
||||
.ft_small_asp {
|
||||
background-position: -164px -668px;
|
||||
}
|
||||
.ft_apk {
|
||||
background-position: 0 -744px;
|
||||
}
|
||||
.ft_small_apk {
|
||||
background-position: -164px -792px;
|
||||
}
|
||||
.ft_avi {
|
||||
background-position: 0 -868px;
|
||||
}
|
||||
.ft_small_avi {
|
||||
background-position: -164px -916px;
|
||||
}
|
||||
.ft_bmp {
|
||||
background-position: 0 -992px;
|
||||
}
|
||||
.ft_small_bmp {
|
||||
background-position: -164px -1040px;
|
||||
}
|
||||
.ft_doc {
|
||||
background-position: 0 -1116px;
|
||||
}
|
||||
.ft_small_doc {
|
||||
background-position: -164px -1164px;
|
||||
}
|
||||
.ft_docx {
|
||||
background-position: 0 -1116px;
|
||||
}
|
||||
.ft_small_docx {
|
||||
background-position: -164px -1164px;
|
||||
}
|
||||
.ft_dvd {
|
||||
background-position: 0 -1240px;
|
||||
}
|
||||
.ft_small_dvd {
|
||||
background-position: -164px -1288px;
|
||||
}
|
||||
.ft_dwf {
|
||||
background-position: 0 -1364px;
|
||||
}
|
||||
.ft_small_dwf {
|
||||
background-position: -164px -1412px;
|
||||
}
|
||||
.ft_exe {
|
||||
background-position: 0 -1488px;
|
||||
}
|
||||
.ft_small_exe {
|
||||
background-position: -164px -1536px;
|
||||
}
|
||||
.ft_fla {
|
||||
background-position: 0 -1612px;
|
||||
}
|
||||
.ft_small_fla {
|
||||
background-position: -164px -1660px;
|
||||
}
|
||||
.ft_gif {
|
||||
background-position: 0 -1736px;
|
||||
}
|
||||
.ft_small_gif {
|
||||
background-position: -164px -1784px;
|
||||
}
|
||||
.ft_htc {
|
||||
background-position: 0 -1860px;
|
||||
}
|
||||
.ft_small_htc {
|
||||
background-position: -164px -1908px;
|
||||
}
|
||||
.ft_html {
|
||||
background-position: 0 -1984px;
|
||||
}
|
||||
.ft_small_html {
|
||||
background-position: -164px -2032px;
|
||||
}
|
||||
.ft_htm {
|
||||
background-position: 0 -1984px;
|
||||
}
|
||||
.ft_small_htm {
|
||||
background-position: -164px -2032px;
|
||||
}
|
||||
|
||||
.ft_ics {
|
||||
background-position: 0 -2108px;
|
||||
}
|
||||
.ft_small_ics {
|
||||
background-position: -164px -2156px;
|
||||
}
|
||||
.ft_ico {
|
||||
background-position: 0 -2232px;
|
||||
}
|
||||
.ft_small_ico {
|
||||
background-position: -164px -2280px;
|
||||
}
|
||||
.ft_java {
|
||||
background-position: 0 -2357px;
|
||||
}
|
||||
.ft_small_java {
|
||||
background-position: -164px -2404px;
|
||||
}
|
||||
.ft_jpg {
|
||||
background-position: 0 -2480px;
|
||||
}
|
||||
.ft_small_jpg {
|
||||
background-position: -164px -2528px;
|
||||
}
|
||||
.ft_png {
|
||||
background-position: 0 -2480px;
|
||||
}
|
||||
.ft_small_png {
|
||||
background-position: -164px -2528px;
|
||||
}
|
||||
.ft_jsp {
|
||||
background-position: 0 -2604px;
|
||||
}
|
||||
.ft_small_jsp {
|
||||
background-position: -164px -2652px;
|
||||
}
|
||||
.ft_mov {
|
||||
background-position: 0 -2728px;
|
||||
}
|
||||
.ft_small_mov {
|
||||
background-position: -164px -2776px;
|
||||
}
|
||||
.ft_mp3 {
|
||||
background-position: 0 -2852px;
|
||||
}
|
||||
.ft_small_mp3 {
|
||||
background-position: -164px -2900px;
|
||||
}
|
||||
.ft_mp4 {
|
||||
background-position: 0 -2976px;
|
||||
}
|
||||
.ft_small_mp4 {
|
||||
background-position: -164px -3024px;
|
||||
}
|
||||
.ft_pdf {
|
||||
background-position: 0 -3100px;
|
||||
}
|
||||
.ft_small_pdf {
|
||||
background-position: -164px -3148px;
|
||||
}
|
||||
.ft_ppt {
|
||||
background-position: 0 -3224px;
|
||||
}
|
||||
.ft_small_ppt {
|
||||
background-position: -164px -3272px;
|
||||
}
|
||||
.ft_pptx {
|
||||
background-position: 0 -3224px;
|
||||
}
|
||||
.ft_small_pptx {
|
||||
background-position: -164px -3272px;
|
||||
}
|
||||
.ft_psd {
|
||||
background-position: 0 -3348px;
|
||||
}
|
||||
.ft_small_psd {
|
||||
background-position: -164px -3396px;
|
||||
}
|
||||
.ft_rm {
|
||||
background-position: 0 -3472px;
|
||||
}
|
||||
.ft_small_rm {
|
||||
background-position: -164px -3520px;
|
||||
}
|
||||
.ft_rif {
|
||||
background-position: 0 -3596px;
|
||||
}
|
||||
.ft_small_rif {
|
||||
background-position: -164px -3644px;
|
||||
}
|
||||
.ft_rar {
|
||||
background-position: 0 -3719px;
|
||||
}
|
||||
.ft_small_rar {
|
||||
background-position: -164px -3768px;
|
||||
}
|
||||
.ft_swf {
|
||||
background-position: 0 -3844px;
|
||||
}
|
||||
.ft_small_swf {
|
||||
background-position: -164px -3892px;
|
||||
}
|
||||
.ft_tif {
|
||||
background-position: 0 -3968px;
|
||||
}
|
||||
.ft_small_tif {
|
||||
background-position: -164px -4016px;
|
||||
}
|
||||
.ft_txt {
|
||||
background-position: 0 -4092px;
|
||||
}
|
||||
.ft_small_txt {
|
||||
background-position: -164px -4140px;
|
||||
}
|
||||
.ft_wma {
|
||||
background-position: 0 -4216px;
|
||||
}
|
||||
.ft_small_wma {
|
||||
background-position: -164px -4264px;
|
||||
}
|
||||
.ft_wri {
|
||||
background-position: 0 -4339px;
|
||||
}
|
||||
.ft_small_wri {
|
||||
background-position: -164px -4387px;
|
||||
}
|
||||
.ft_xls {
|
||||
background-position: 0 -4464px;
|
||||
}
|
||||
.ft_small_xls {
|
||||
background-position: -164px -4512px;
|
||||
}
|
||||
.ft_xlsx {
|
||||
background-position: 0 -4464px;
|
||||
}
|
||||
.ft_small_xlsx {
|
||||
background-position: -164px -4512px;
|
||||
}
|
||||
.ft_xsl {
|
||||
background-position: 0 -4587px;
|
||||
}
|
||||
.ft_small_xsl {
|
||||
background-position: -164px -4635px;
|
||||
}
|
||||
.ft_zip {
|
||||
background-position: 0 -4712px;
|
||||
}
|
||||
.ft_small_zip {
|
||||
background-position: -164px -4760px;
|
||||
}
|
||||
301
src/components/FileCloud/index.vue
Normal file
301
src/components/FileCloud/index.vue
Normal file
@@ -0,0 +1,301 @@
|
||||
<template>
|
||||
<div style="min-width: 700px">
|
||||
<a-modal
|
||||
title="选择图片"
|
||||
:visible="show"
|
||||
width="60%"
|
||||
:footer="null"
|
||||
:mask-closable="false"
|
||||
@cancel="chose"
|
||||
>
|
||||
<!-- Header with breadcrumb and search -->
|
||||
<div>
|
||||
<div
|
||||
style="
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 5px;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
"
|
||||
>
|
||||
<div style="padding-top: 10px">
|
||||
<a-breadcrumb>
|
||||
<a-breadcrumb-item>
|
||||
<a @click.stop="toRootPath">根目录</a>
|
||||
</a-breadcrumb-item>
|
||||
<a-breadcrumb-item
|
||||
v-for="(fpath, fidx) in folderPath"
|
||||
:key="fidx"
|
||||
>
|
||||
<a @click.stop="toPath(fpath)">{{ fpath.name }}</a>
|
||||
</a-breadcrumb-item>
|
||||
</a-breadcrumb>
|
||||
</div>
|
||||
<div>
|
||||
<a-input-search
|
||||
placeholder="文件名"
|
||||
style="width: 200px"
|
||||
v-model:value="findName"
|
||||
@search="findByName"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- File/Folder List -->
|
||||
<div
|
||||
style="
|
||||
margin-top: 5px;
|
||||
height: 500px;
|
||||
overflow: auto;
|
||||
position: relative;
|
||||
"
|
||||
>
|
||||
<div
|
||||
class="fitem"
|
||||
:class="item.id === sub.id ? 'fitem_tips' : ''"
|
||||
v-for="(item, idx) in folderData"
|
||||
:key="idx"
|
||||
>
|
||||
<!-- Image Item -->
|
||||
<div
|
||||
v-if="!item.folder"
|
||||
class="fitem-image"
|
||||
@click.stop="chooseItem(item)"
|
||||
>
|
||||
<img
|
||||
:style="{ 'object-fit': 'scale-down' }"
|
||||
class="fitem-image"
|
||||
:src="fileBasePath + item.path"
|
||||
alt=""
|
||||
/>
|
||||
<div class="fitem-buttons" v-if="itemChecked.id === item.id">
|
||||
<CheckCircleOutlined
|
||||
style="font-size: 30px; cursor: pointer; color: #0055ff"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Folder Item -->
|
||||
<div
|
||||
v-else
|
||||
class="fitem-image"
|
||||
style="padding-left: 50px"
|
||||
@click.stop="clickFolder(item)"
|
||||
>
|
||||
<i
|
||||
class="ft"
|
||||
:class="[item.icon === '' ? 'ft_null' : 'ft_' + item.icon]"
|
||||
></i>
|
||||
</div>
|
||||
|
||||
<!-- Filename -->
|
||||
<div class="fitem-name">
|
||||
<span :title="item.name">{{ item.name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer Buttons -->
|
||||
<div class="dialog-footer" style="text-align: right; padding: 10px">
|
||||
<a-button
|
||||
type="primary"
|
||||
:disabled="btnDisabled"
|
||||
@click="saveChoose"
|
||||
style="margin-right: 8px"
|
||||
>
|
||||
确认
|
||||
</a-button>
|
||||
<a-button @click="chose">取消</a-button>
|
||||
</div>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, computed } from "vue";
|
||||
import apiFilecloud from "@/api/modules/filecloud";
|
||||
import { CheckCircleOutlined } from "@ant-design/icons-vue";
|
||||
import { message } from "ant-design-vue";
|
||||
|
||||
// Props
|
||||
const props = defineProps({
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
// Emit events
|
||||
const emit = defineEmits(["close", "choose"]);
|
||||
|
||||
// State
|
||||
const findName = ref("");
|
||||
const btnDisabled = ref(true);
|
||||
const folderPath = ref([]);
|
||||
const folderData = ref([]);
|
||||
const loading = ref(false);
|
||||
const fileBasePath = ref(process.env.VUE_APP_FILE_BASE_URL || "/files/");
|
||||
const itemChecked = ref({});
|
||||
const sub = ref({});
|
||||
|
||||
// Computed: 当前文件夹 ID
|
||||
const getCurrentFloderId = () => {
|
||||
const len = folderPath.value.length;
|
||||
return len > 0 ? folderPath.value[len - 1].id : "0";
|
||||
};
|
||||
|
||||
// Methods
|
||||
const loadFolderData = () => {
|
||||
if (loading.value) return;
|
||||
loading.value = true;
|
||||
|
||||
const folderId = getCurrentFloderId();
|
||||
if (folderId === "-1") {
|
||||
loading.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
apiFilecloud
|
||||
.list(folderId)
|
||||
.then((rs) => {
|
||||
if (rs.status === 200) {
|
||||
console.log(rs, 32);
|
||||
folderData.value = rs.result;
|
||||
} else {
|
||||
message.error(rs.message);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
message.error("加载失败:" + err.message);
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
const findByName = () => {
|
||||
if (!findName.value.trim()) return;
|
||||
|
||||
apiFilecloud
|
||||
.findByName(findName.value)
|
||||
.then((rs) => {
|
||||
if (rs.status === 200) {
|
||||
folderPath.value = [
|
||||
{
|
||||
id: "-1",
|
||||
name: `${findName.value} 搜索结果(${rs.result.length})`,
|
||||
},
|
||||
];
|
||||
folderData.value = rs.result;
|
||||
} else {
|
||||
message.error(rs.message);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
message.error("搜索失败");
|
||||
});
|
||||
};
|
||||
|
||||
const toRootPath = () => {
|
||||
if (folderPath.value.length > 0) {
|
||||
folderPath.value = [];
|
||||
loadFolderData();
|
||||
}
|
||||
};
|
||||
|
||||
const toPath = (pathInfo) => {
|
||||
const newPaths = [];
|
||||
for (const p of folderPath.value) {
|
||||
newPaths.push(p);
|
||||
if (p.id === pathInfo.id) break;
|
||||
}
|
||||
folderPath.value = newPaths;
|
||||
loadFolderData();
|
||||
};
|
||||
|
||||
const clickFolder = (row) => {
|
||||
sub.value = row;
|
||||
if (loading.value) return;
|
||||
|
||||
const pathLen = folderPath.value.length;
|
||||
if (pathLen === 0 || folderPath.value[pathLen - 1].id !== row.id) {
|
||||
folderPath.value.push({
|
||||
id: row.id,
|
||||
name: row.name,
|
||||
});
|
||||
}
|
||||
loadFolderData();
|
||||
};
|
||||
|
||||
const chooseItem = (item) => {
|
||||
itemChecked.value = item;
|
||||
btnDisabled.value = false;
|
||||
};
|
||||
|
||||
const chose = () => {
|
||||
emit("close");
|
||||
};
|
||||
|
||||
const saveChoose = () => {
|
||||
if (!itemChecked.value.id) return;
|
||||
emit("choose", itemChecked.value);
|
||||
// Reset selection after choose
|
||||
itemChecked.value = {};
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
loadFolderData();
|
||||
});
|
||||
</script>
|
||||
|
||||
<script>
|
||||
// 引入样式(保持原样)
|
||||
export default {
|
||||
name: "ImageSelectorModal",
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import url("../../assets/scss/filetypes.css");
|
||||
|
||||
.fitem {
|
||||
text-align: center;
|
||||
position: relative;
|
||||
padding: 5px;
|
||||
width: 170px;
|
||||
height: 130px;
|
||||
display: inline-block;
|
||||
margin: 5px;
|
||||
.fitem-image {
|
||||
width: 160px;
|
||||
height: 90px;
|
||||
line-height: 90px;
|
||||
.fitem-buttons {
|
||||
position: absolute;
|
||||
height: 45px;
|
||||
display: block;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
line-height: 45px;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
.fitem-name {
|
||||
text-align: center;
|
||||
padding: 5px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
.fitem_tips {
|
||||
border: 1px solid #dadada;
|
||||
background-color: #edf6ff;
|
||||
}
|
||||
.fitem:hover {
|
||||
outline: 1px solid #dadada;
|
||||
background-color: #edf6ff;
|
||||
}
|
||||
</style>
|
||||
142
src/hooks/useCourseData.js
Normal file
142
src/hooks/useCourseData.js
Normal file
@@ -0,0 +1,142 @@
|
||||
import { ref, reactive } from "vue";
|
||||
import { message } from "ant-design-vue";
|
||||
|
||||
/**
|
||||
* 课程数据管理hook
|
||||
* @returns
|
||||
*/
|
||||
export function useCourseData() {
|
||||
// 课程元数据
|
||||
const courseMetadata = reactive({
|
||||
courseName: "",
|
||||
createTime: "",
|
||||
});
|
||||
|
||||
// 课程列表数据
|
||||
const courseList = ref([
|
||||
{
|
||||
title: "课程1",
|
||||
data: [
|
||||
{ key: "1-1", name: "视频课件名称", type: "视频" },
|
||||
{ key: "1-2", name: "音频课件名称", type: "音频" },
|
||||
{ key: "1-3", name: "文档课件名称", type: "文档" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "课程2",
|
||||
data: [
|
||||
{ key: "2-1", name: "图文课件名称", type: "图文" },
|
||||
{ key: "2-2", name: "外部链接", type: "链接" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "课程3",
|
||||
data: [
|
||||
{ key: "3-1", name: "SCORM", type: "SCORM" },
|
||||
{ key: "3-2", name: "考试名称", type: "考试" },
|
||||
{ key: "3-3", name: "自定义考试名称", type: "考试" },
|
||||
{ key: "3-4", name: "作业名称", type: "作业" },
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
// 课程操作映射
|
||||
const courseOperations = {
|
||||
addVideo: (index) => {
|
||||
message.error("功能开发中");
|
||||
console.log("添加视频功能调用,索引:", index);
|
||||
if (index !== undefined && courseList.value[index]) {
|
||||
courseList.value[index].title = "课程4";
|
||||
}
|
||||
},
|
||||
addAudio: () => {
|
||||
console.log("添加音频功能调用");
|
||||
},
|
||||
addDocument: () => {
|
||||
console.log("添加文档功能调用");
|
||||
},
|
||||
addImageText: () => {
|
||||
console.log("添加图文功能调用");
|
||||
},
|
||||
addExternalLink: () => {
|
||||
console.log("添加外部链接功能调用");
|
||||
},
|
||||
addScorm: () => {
|
||||
console.log("添加SCORM功能调用");
|
||||
},
|
||||
addExam: () => {
|
||||
console.log("添加考试功能调用");
|
||||
},
|
||||
addHomework: () => {
|
||||
console.log("添加作业功能调用");
|
||||
},
|
||||
addAssessment: () => {
|
||||
console.log("添加评估功能调用");
|
||||
},
|
||||
};
|
||||
|
||||
// 执行课程操作
|
||||
const executeCourseOperation = (operationName, data) => {
|
||||
if (courseOperations[operationName]) {
|
||||
courseOperations[operationName](data);
|
||||
} else {
|
||||
console.warn(`未找到操作: ${operationName}`);
|
||||
}
|
||||
};
|
||||
|
||||
// 课程操作按钮
|
||||
const courseActionButtons = [
|
||||
{
|
||||
label: "添加视频",
|
||||
icon: "",
|
||||
fun: "addVideo",
|
||||
},
|
||||
{
|
||||
label: "添加音频",
|
||||
icon: "",
|
||||
fun: "addAudio",
|
||||
},
|
||||
{
|
||||
label: "添加文档",
|
||||
icon: "",
|
||||
fun: "addDocument",
|
||||
},
|
||||
{
|
||||
label: "添加图文",
|
||||
icon: "",
|
||||
fun: "addImageText",
|
||||
},
|
||||
{
|
||||
label: "外部链接",
|
||||
icon: "",
|
||||
fun: "addExternalLink",
|
||||
},
|
||||
{
|
||||
label: "SCORM",
|
||||
icon: "",
|
||||
fun: "addScorm",
|
||||
},
|
||||
{
|
||||
label: "添加考试",
|
||||
icon: "",
|
||||
fun: "addExam",
|
||||
},
|
||||
{
|
||||
label: "添加作业",
|
||||
icon: "",
|
||||
fun: "addHomework",
|
||||
},
|
||||
{
|
||||
label: "添加评估",
|
||||
icon: "",
|
||||
fun: "addAssessment",
|
||||
},
|
||||
];
|
||||
|
||||
return {
|
||||
courseMetadata,
|
||||
courseList,
|
||||
courseActionButtons,
|
||||
executeCourseOperation
|
||||
};
|
||||
}
|
||||
50
src/hooks/useCourseForm.js
Normal file
50
src/hooks/useCourseForm.js
Normal file
@@ -0,0 +1,50 @@
|
||||
import { reactive, ref } from "vue";
|
||||
|
||||
/**
|
||||
* 课程表单相关hook
|
||||
* @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: "", // 课程简介
|
||||
});
|
||||
|
||||
// 可见性选项
|
||||
const visibilityOptions = [
|
||||
{ label: "PC端可见", value: "Apple" },
|
||||
{ label: "移动端可见", value: "Pear" },
|
||||
{ label: "多端可见", value: "Orange", disabled: false },
|
||||
];
|
||||
|
||||
// 表单重置
|
||||
const resetForm = (courseCoverurl, fileList) => {
|
||||
if (formRef.value) {
|
||||
formRef.value.resetFields();
|
||||
}
|
||||
if (courseCoverurl) {
|
||||
courseCoverurl.value = "";
|
||||
}
|
||||
if (fileList) {
|
||||
fileList.value = [];
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
formRef,
|
||||
formState,
|
||||
visibilityOptions,
|
||||
resetForm
|
||||
};
|
||||
}
|
||||
65
src/hooks/useUpload.js
Normal file
65
src/hooks/useUpload.js
Normal file
@@ -0,0 +1,65 @@
|
||||
import { ref } from "vue";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { Loading, Plus } from "@element-plus/icons-vue";
|
||||
|
||||
/**
|
||||
* 文件上传相关hook
|
||||
* @returns
|
||||
*/
|
||||
export function useUpload() {
|
||||
// 上传相关
|
||||
const fileList = ref([]);
|
||||
const loading = ref(false);
|
||||
const courseCoverurl = ref("");
|
||||
|
||||
// 获取图片base64
|
||||
const getBase64 = (img, callback) => {
|
||||
const reader = new FileReader();
|
||||
reader.addEventListener("load", () => callback(reader.result));
|
||||
reader.readAsDataURL(img);
|
||||
};
|
||||
|
||||
// 上传状态变化处理
|
||||
const handleChange = (info) => {
|
||||
if (info.file.status === "uploading") {
|
||||
loading.value = true;
|
||||
return;
|
||||
}
|
||||
if (info.file.status === "done") {
|
||||
// Get this url from response in real world.
|
||||
getBase64(info.file.originFileObj, (base64Url) => {
|
||||
courseCoverurl.value = base64Url;
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
if (info.file.status === "error") {
|
||||
loading.value = false;
|
||||
ElMessage.error("upload error");
|
||||
}
|
||||
};
|
||||
|
||||
// 上传前检查
|
||||
const beforeUpload = (file) => {
|
||||
const isJpgOrPng = file.type === "image/jpeg" || file.type === "image/png";
|
||||
if (!isJpgOrPng) {
|
||||
ElMessage.error("You can only upload JPG file!");
|
||||
}
|
||||
const isLt2M = file.size / 1024 / 1024 < 2;
|
||||
if (!isLt2M) {
|
||||
ElMessage.error("Image must smaller than 2MB!");
|
||||
}
|
||||
return isJpgOrPng && isLt2M;
|
||||
};
|
||||
|
||||
return {
|
||||
// 数据
|
||||
fileList,
|
||||
loading,
|
||||
courseCoverurl,
|
||||
|
||||
// 方法
|
||||
getBase64,
|
||||
handleChange,
|
||||
beforeUpload
|
||||
};
|
||||
}
|
||||
213
src/main.js
213
src/main.js
@@ -6,28 +6,33 @@
|
||||
* @FilePath: /fe-manage/src/main.js
|
||||
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
|
||||
*/
|
||||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import store from './store'
|
||||
import { createApp } from "vue";
|
||||
import App from "./App.vue";
|
||||
import router from "./router";
|
||||
import store from "./store";
|
||||
import { message } from "ant-design-vue";
|
||||
// import ElementPlus from 'element-plus'
|
||||
import 'element-plus/dist/index.css'
|
||||
import "element-plus/dist/index.css";
|
||||
// import zhCn from 'element-plus/es/locale/lang/zh-cn'
|
||||
import "@/assets/scss/common.scss"
|
||||
import Antd from 'ant-design-vue';
|
||||
import 'ant-design-vue/dist/antd.css';
|
||||
import {request} from "@/api/request";
|
||||
import {USER_INFO, USER_PERMISSION, VALIDATE_TOKEN} from "@/api/apis";
|
||||
import "@/assets/scss/common.scss";
|
||||
import Antd from "ant-design-vue";
|
||||
import "ant-design-vue/dist/antd.css";
|
||||
import { request } from "@/api/request";
|
||||
import { USER_INFO, USER_PERMISSION, VALIDATE_TOKEN } from "@/api/apis";
|
||||
import * as api1 from "@/api/index1";
|
||||
import {getCookieForName} from "@/api/method";
|
||||
import components from './components'
|
||||
import axios from 'axios'
|
||||
import { getCookieForName } from "@/api/method";
|
||||
import components from "./components";
|
||||
import axios from "axios";
|
||||
import Cookies from "vue-cookies";
|
||||
// axios.defaults.withCredentials = true;
|
||||
// import zhCN from 'ant-design-vue/es/locale/zh_CN';
|
||||
const app = createApp(App)
|
||||
const app = createApp(App);
|
||||
//全局注册
|
||||
app.use(components)
|
||||
app.use(components);
|
||||
message.config({
|
||||
top: "250px",
|
||||
});
|
||||
|
||||
// 清理控制台warn信息
|
||||
app.config.warnHandler = () => null;
|
||||
// app.use(ElementPlus, {
|
||||
@@ -35,102 +40,112 @@ app.config.warnHandler = () => null;
|
||||
// })
|
||||
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
if (!getCookieForName("token")) {
|
||||
window.location.href = process.env.VUE_APP_LOGIN_URL + encodeURIComponent(window.location.protocol + process.env.VUE_APP_BOE_API_URL + process.env.VUE_APP_BASE + router.currentRoute.value.fullPath)
|
||||
return
|
||||
if (!getCookieForName("token")) {
|
||||
window.location.href =
|
||||
process.env.VUE_APP_LOGIN_URL +
|
||||
encodeURIComponent(
|
||||
window.location.protocol +
|
||||
process.env.VUE_APP_BOE_API_URL +
|
||||
process.env.VUE_APP_BASE +
|
||||
router.currentRoute.value.fullPath
|
||||
);
|
||||
return;
|
||||
}
|
||||
//第一次进入 没有用户信息
|
||||
if (!store.state.userInfo.userId) {
|
||||
try {
|
||||
await request(VALIDATE_TOKEN);
|
||||
await getUserInfo();
|
||||
await getUserPermission();
|
||||
init();
|
||||
} catch (e) {
|
||||
console.log("token失效 跳转到登录页");
|
||||
}
|
||||
//第一次进入 没有用户信息
|
||||
if(!store.state.userInfo.userId){
|
||||
try{
|
||||
await request(VALIDATE_TOKEN)
|
||||
await getUserInfo()
|
||||
await getUserPermission();
|
||||
init()
|
||||
}catch (e){
|
||||
console.log('token失效 跳转到登录页')
|
||||
}
|
||||
}
|
||||
next();
|
||||
})
|
||||
}
|
||||
next();
|
||||
});
|
||||
app.use(Antd);
|
||||
app.use(router);
|
||||
app.use(store);
|
||||
app.mount('#app');
|
||||
app.mount("#app");
|
||||
async function getUserPermission() {
|
||||
return request(USER_PERMISSION, {permissionType: 'PAGE'}).then(res => {
|
||||
store.commit("SET_PERMISSION", res.data?.map(s => s.url));
|
||||
})
|
||||
return request(USER_PERMISSION, { permissionType: "PAGE" }).then((res) => {
|
||||
store.commit(
|
||||
"SET_PERMISSION",
|
||||
res.data?.map((s) => s.url)
|
||||
);
|
||||
});
|
||||
}
|
||||
async function getUserInfo() {
|
||||
const userInfo = await request(USER_INFO);
|
||||
store.commit("SET_USER", userInfo.data);
|
||||
axios({
|
||||
method: "get",
|
||||
url: "/userbasic/orgHrbp/reportOrgs",
|
||||
params: {
|
||||
workNum:userInfo.data.userNo
|
||||
},
|
||||
headers: {
|
||||
"XBOR-Access-token": Cookies.get("token"),
|
||||
},
|
||||
}).then(res=>{
|
||||
store.commit("SET_USER_ORGS", res.data);
|
||||
})
|
||||
const userInfo = await request(USER_INFO);
|
||||
store.commit("SET_USER", userInfo.data);
|
||||
axios({
|
||||
method: "get",
|
||||
url: "/userbasic/orgHrbp/reportOrgs",
|
||||
params: {
|
||||
workNum: userInfo.data.userNo,
|
||||
},
|
||||
headers: {
|
||||
"XBOR-Access-token": Cookies.get("token"),
|
||||
},
|
||||
}).then((res) => {
|
||||
store.commit("SET_USER_ORGS", res.data);
|
||||
});
|
||||
}
|
||||
async function initDict(key) {
|
||||
const list = await getDictList(key);
|
||||
store.commit("SET_DICT", {key, data: list});
|
||||
const list = await getDictList(key);
|
||||
store.commit("SET_DICT", { key, data: list });
|
||||
}
|
||||
const getDictList = (param) => api1.getDictTree({code: param,}).then((res) => res.data.data);
|
||||
const getDictList = (param) =>
|
||||
api1.getDictTree({ code: param }).then((res) => res.data.data);
|
||||
const initDictTree = (key) => {
|
||||
axios({
|
||||
method: "get",
|
||||
url: "/systemapi/xboe/type/tree-list",
|
||||
params: {
|
||||
sysResType: "1",
|
||||
status: "1",
|
||||
},
|
||||
headers: {
|
||||
"XBOR-Access-token": Cookies.get("token"),
|
||||
},
|
||||
}).then(
|
||||
(res) => {
|
||||
console.log(res.data.result,'课程分类接口')
|
||||
store.commit("SET_DICT", {key, data: res.data.result});
|
||||
//转化为map放到状态中
|
||||
let map=new Map();
|
||||
res.data.result.forEach(item=>{
|
||||
map.set(item.id, item.name);
|
||||
if(item.children && item.children!=''){
|
||||
item.children.forEach(child=>{
|
||||
map.set(child.id, child.name);
|
||||
if(child.children && child.children!=''){
|
||||
child.children.forEach(last=>{
|
||||
map.set(last.id, last.name);
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
axios({
|
||||
method: "get",
|
||||
url: "/systemapi/xboe/type/tree-list",
|
||||
params: {
|
||||
sysResType: "1",
|
||||
status: "1",
|
||||
},
|
||||
headers: {
|
||||
"XBOR-Access-token": Cookies.get("token"),
|
||||
},
|
||||
}).then(
|
||||
(res) => {
|
||||
console.log(res.data.result, "课程分类接口");
|
||||
store.commit("SET_DICT", { key, data: res.data.result });
|
||||
//转化为map放到状态中
|
||||
let map = new Map();
|
||||
res.data.result.forEach((item) => {
|
||||
map.set(item.id, item.name);
|
||||
if (item.children && item.children != "") {
|
||||
item.children.forEach((child) => {
|
||||
map.set(child.id, child.name);
|
||||
if (child.children && child.children != "") {
|
||||
child.children.forEach((last) => {
|
||||
map.set(last.id, last.name);
|
||||
});
|
||||
}
|
||||
});
|
||||
store.commit("SET_SYSTYPEMAP", map);
|
||||
},
|
||||
(err) => {
|
||||
message.error(err);
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
store.commit("SET_SYSTYPEMAP", map);
|
||||
},
|
||||
(err) => {
|
||||
message.error(err);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
async function init() {
|
||||
|
||||
// initDict("content_type"); //内容分类
|
||||
initDictTree("content_type"); //内容分类,换成type/tree-list接口
|
||||
initDict("project_level"); //项目级别
|
||||
initDict("project_sys"); //培训分类
|
||||
initDict("project_pic"); //项目封面
|
||||
initDict("router_pic"); //路径图封面
|
||||
initDict("course_pic"); //课程封面
|
||||
initDict("job_type"); //岗位
|
||||
initDict("band"); //band
|
||||
initDict("examine_cover") //讲师认证封面图
|
||||
initDict("project_number") //项目编号
|
||||
}
|
||||
// initDict("content_type"); //内容分类
|
||||
initDictTree("content_type"); //内容分类,换成type/tree-list接口
|
||||
initDict("project_level"); //项目级别
|
||||
initDict("project_sys"); //培训分类
|
||||
initDict("project_pic"); //项目封面
|
||||
initDict("router_pic"); //路径图封面
|
||||
initDict("course_pic"); //课程封面
|
||||
initDict("job_type"); //岗位
|
||||
initDict("band"); //band
|
||||
initDict("examine_cover"); //讲师认证封面图
|
||||
initDict("project_number"); //项目编号
|
||||
}
|
||||
|
||||
@@ -7,118 +7,122 @@
|
||||
<div class="filterItems">
|
||||
<div class="select">
|
||||
<a-input
|
||||
v-model:value="searchParam.name"
|
||||
style="width: 200px; height: 40px; border-radius: 8px"
|
||||
placeholder="请输入课程名称"
|
||||
allowClear
|
||||
showSearch
|
||||
v-model:value="searchParam.name"
|
||||
style="width: 200px; height: 40px; border-radius: 8px"
|
||||
placeholder="请输入课程名称"
|
||||
allowClear
|
||||
showSearch
|
||||
>
|
||||
</a-input>
|
||||
</div>
|
||||
<div class="select">
|
||||
<a-select
|
||||
v-model:value="searchParam.category"
|
||||
style="width: 200px"
|
||||
placeholder="全部课程分类"
|
||||
:options="categoryList"
|
||||
allowClear
|
||||
v-model:value="searchParam.category"
|
||||
style="width: 200px"
|
||||
placeholder="全部课程分类"
|
||||
:options="categoryList"
|
||||
allowClear
|
||||
></a-select>
|
||||
</div>
|
||||
<div class="select">
|
||||
<a-input
|
||||
v-model:value="searchParam.teacher"
|
||||
style="width: 200px; height: 40px; border-radius: 8px"
|
||||
placeholder="授课教师"
|
||||
allowClear
|
||||
showSearch
|
||||
v-model:value="searchParam.teacher"
|
||||
style="width: 200px; height: 40px; border-radius: 8px"
|
||||
placeholder="授课教师"
|
||||
allowClear
|
||||
showSearch
|
||||
>
|
||||
</a-input>
|
||||
</div>
|
||||
<div class="select addTimeBox">
|
||||
<div class="addTime">培训时间:</div>
|
||||
<a-range-picker
|
||||
v-model:value="searchParam.valueDate"
|
||||
style="width:330px"
|
||||
format="YYYY-MM-DD"
|
||||
separator="至"
|
||||
:placeholder="[' 开始时间', ' 结束时间']"
|
||||
v-model:value="searchParam.valueDate"
|
||||
style="width: 330px"
|
||||
format="YYYY-MM-DD"
|
||||
separator="至"
|
||||
:placeholder="[' 开始时间', ' 结束时间']"
|
||||
/>
|
||||
</div>
|
||||
<div class="select">
|
||||
<a-select
|
||||
v-model:value="searchParam.reviewStatus"
|
||||
style="width: 200px"
|
||||
placeholder="全部审核状态"
|
||||
:options="reviewStatusList"
|
||||
allowClear
|
||||
v-model:value="searchParam.reviewStatus"
|
||||
style="width: 200px"
|
||||
placeholder="全部审核状态"
|
||||
:options="reviewStatusList"
|
||||
allowClear
|
||||
></a-select>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="select">
|
||||
<a-select
|
||||
v-model:value="searchParam.publishStatus"
|
||||
style="width: 200px"
|
||||
placeholder="全部发布状态"
|
||||
:options="publishStatusList"
|
||||
allowClear
|
||||
v-model:value="searchParam.publishStatus"
|
||||
style="width: 200px"
|
||||
placeholder="全部发布状态"
|
||||
:options="publishStatusList"
|
||||
allowClear
|
||||
></a-select>
|
||||
</div>
|
||||
<div class="select" style="display: flex; margin-bottom: 20px">
|
||||
<div class="btn btn4" @click="toggleFilter">
|
||||
<div class="search"></div>
|
||||
<div class="btnText">{{ showSecondFilter ? '收起' : '展开' }} ▼</div>
|
||||
<div class="btnText">
|
||||
{{ showSecondFilter ? "收起" : "展开" }} ▼
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 第二排搜索项 (通过 showSecondFilter 控制显隐) -->
|
||||
<div class="filterItems" style="justify-content: space-between;width: 100%">
|
||||
<div
|
||||
class="filterItems"
|
||||
style="justify-content: space-between; width: 100%"
|
||||
>
|
||||
<div class="filterItems">
|
||||
<div class="select" v-show="showSecondFilter">
|
||||
<div class="select" v-show="showSecondFilter">
|
||||
<a-select
|
||||
v-model:value="searchParam.enableStatus"
|
||||
style="width: 200px"
|
||||
placeholder="全部启用状态"
|
||||
:options="enableStatusList"
|
||||
allowClear
|
||||
v-model:value="searchParam.enableStatus"
|
||||
style="width: 200px"
|
||||
placeholder="全部启用状态"
|
||||
:options="enableStatusList"
|
||||
allowClear
|
||||
></a-select>
|
||||
</div>
|
||||
<div class="select" v-show="showSecondFilter" >
|
||||
<div class="select" v-show="showSecondFilter">
|
||||
<a-select
|
||||
v-model:value="searchParam.isPublic"
|
||||
style="width: 200px"
|
||||
placeholder="是否公开课"
|
||||
:options="isPublicList"
|
||||
allowClear
|
||||
v-model:value="searchParam.isPublic"
|
||||
style="width: 200px"
|
||||
placeholder="是否公开课"
|
||||
:options="isPublicList"
|
||||
allowClear
|
||||
></a-select>
|
||||
</div>
|
||||
<div class="select" v-show="showSecondFilter" >
|
||||
<div class="select" v-show="showSecondFilter">
|
||||
<a-select
|
||||
v-model:value="searchParam.resourceOwner"
|
||||
style="width: 200px"
|
||||
placeholder="全部资源归属"
|
||||
:options="resourceOwnerList"
|
||||
allowClear
|
||||
v-model:value="searchParam.resourceOwner"
|
||||
style="width: 200px"
|
||||
placeholder="全部资源归属"
|
||||
:options="resourceOwnerList"
|
||||
allowClear
|
||||
></a-select>
|
||||
</div>
|
||||
<div class="select" v-show="showSecondFilter">
|
||||
<a-input
|
||||
v-model:value="searchParam.teacher"
|
||||
style="width: 200px; height: 40px; border-radius: 8px"
|
||||
placeholder="创建人"
|
||||
allowClear
|
||||
showSearch
|
||||
v-model:value="searchParam.teacher"
|
||||
style="width: 200px; height: 40px; border-radius: 8px"
|
||||
placeholder="创建人"
|
||||
allowClear
|
||||
showSearch
|
||||
>
|
||||
</a-input>
|
||||
</div>
|
||||
<div class="select" v-show="showSecondFilter">
|
||||
<div class="select" v-show="showSecondFilter">
|
||||
<a-select
|
||||
v-model:value="searchParam.createSource"
|
||||
style="width: 200px"
|
||||
placeholder="全部创建来源"
|
||||
:options="createSourceList"
|
||||
allowClear
|
||||
v-model:value="searchParam.createSource"
|
||||
style="width: 200px"
|
||||
placeholder="全部创建来源"
|
||||
:options="createSourceList"
|
||||
allowClear
|
||||
></a-select>
|
||||
</div>
|
||||
</div>
|
||||
@@ -163,22 +167,28 @@
|
||||
<!-- 表格 -->
|
||||
<div style="padding: 10px 35px">
|
||||
<a-table
|
||||
:header-cell-style="{ 'text-align': 'center' }"
|
||||
style="border: 1px solid #f2f6fe"
|
||||
:columns="columns"
|
||||
:data-source="tableData"
|
||||
:loading="tableLoading"
|
||||
:scroll="{ x: 'max-content' }"
|
||||
:pagination="false"
|
||||
:expandable="false"
|
||||
:header-cell-style="{ 'text-align': 'center' }"
|
||||
style="border: 1px solid #f2f6fe"
|
||||
:columns="columns"
|
||||
:data-source="tableData"
|
||||
:loading="tableLoading"
|
||||
:scroll="{ x: 'max-content' }"
|
||||
:pagination="false"
|
||||
:expandable="false"
|
||||
>
|
||||
<template #bodyCell="{ record, column }">
|
||||
<template v-if="column.key === 'operation'">
|
||||
<a-space>
|
||||
<a-button @click="handleEdit(record)" type="link">编辑</a-button>
|
||||
<a-button @click="editMaterial(record)" type="link">编辑课件</a-button>
|
||||
<a-button @click="grantPermission(record)" type="link">授权</a-button>
|
||||
<a-button @click="deleteRecord(record)" type="link" danger>删除</a-button>
|
||||
<a-button @click="editMaterial(record)" type="link"
|
||||
>编辑课件</a-button
|
||||
>
|
||||
<a-button @click="grantPermission(record)" type="link"
|
||||
>授权</a-button
|
||||
>
|
||||
<a-button @click="deleteRecord(record)" type="link" danger
|
||||
>删除</a-button
|
||||
>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
@@ -186,15 +196,15 @@
|
||||
<div class="tableBox">
|
||||
<div class="pa">
|
||||
<a-pagination
|
||||
v-if="tableDataTotal > 10"
|
||||
:showSizeChanger="false"
|
||||
:showQuickJumper="true"
|
||||
:hideOnSinglePage="true"
|
||||
:pageSize="pageSize"
|
||||
v-model:current="searchParam.pageNo"
|
||||
:total="tableDataTotal"
|
||||
class="pagination"
|
||||
@change="changePagination"
|
||||
v-if="tableDataTotal > 10"
|
||||
:showSizeChanger="false"
|
||||
:showQuickJumper="true"
|
||||
:hideOnSinglePage="true"
|
||||
:pageSize="pageSize"
|
||||
v-model:current="searchParam.pageNo"
|
||||
:total="tableDataTotal"
|
||||
class="pagination"
|
||||
@change="changePagination"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -211,330 +221,330 @@ export default {
|
||||
// 控制第二排搜索项的显隐
|
||||
showSecondFilter: false,
|
||||
searchParam: {
|
||||
name: '',
|
||||
name: "",
|
||||
category: undefined,
|
||||
teacher: '',
|
||||
teacher: "",
|
||||
valueDate: [],
|
||||
reviewStatus: undefined,
|
||||
publishStatus: undefined,
|
||||
enableStatus: undefined, // 新增:启用状态
|
||||
isPublic: undefined, // 新增:是否公开课
|
||||
isPublic: undefined, // 新增:是否公开课
|
||||
resourceOwner: undefined, // 新增:资源归属
|
||||
createSource: undefined, // 新增:创建来源
|
||||
pageNo: 1,
|
||||
pageSize: 10
|
||||
pageSize: 10,
|
||||
},
|
||||
tableData: [
|
||||
{
|
||||
key: '1',
|
||||
name: '《少走弯路的职场法则》-2021公开课',
|
||||
teacher: '张三',
|
||||
key: "1",
|
||||
name: "《少走弯路的职场法则》-2021公开课",
|
||||
teacher: "张三",
|
||||
courseDuration: 10,
|
||||
studyDuration: 10,
|
||||
studentCount: 1,
|
||||
rating: 5.0,
|
||||
reviewStatus: '-',
|
||||
publishStatus: '未发布'
|
||||
reviewStatus: "-",
|
||||
publishStatus: "未发布",
|
||||
},
|
||||
{
|
||||
key: '2',
|
||||
name: 'PDCA循环工作法',
|
||||
teacher: '张三',
|
||||
key: "2",
|
||||
name: "PDCA循环工作法",
|
||||
teacher: "张三",
|
||||
courseDuration: 20,
|
||||
studyDuration: 20,
|
||||
studentCount: 2,
|
||||
rating: 4.0,
|
||||
reviewStatus: '审核中',
|
||||
publishStatus: '未发布'
|
||||
reviewStatus: "审核中",
|
||||
publishStatus: "未发布",
|
||||
},
|
||||
{
|
||||
key: '3',
|
||||
name: 'BOE端到端的流程体系',
|
||||
teacher: '张三',
|
||||
key: "3",
|
||||
name: "BOE端到端的流程体系",
|
||||
teacher: "张三",
|
||||
courseDuration: 30,
|
||||
studyDuration: 30,
|
||||
studentCount: 3,
|
||||
rating: 3.0,
|
||||
reviewStatus: '审核驳回',
|
||||
publishStatus: '未发布'
|
||||
reviewStatus: "审核驳回",
|
||||
publishStatus: "未发布",
|
||||
},
|
||||
{
|
||||
key: '4',
|
||||
name: '结构性思维与表达-2023年公开课',
|
||||
teacher: '张三',
|
||||
key: "4",
|
||||
name: "结构性思维与表达-2023年公开课",
|
||||
teacher: "张三",
|
||||
courseDuration: 40,
|
||||
studyDuration: 40,
|
||||
studentCount: 4,
|
||||
rating: 5.0,
|
||||
reviewStatus: '审核通过',
|
||||
publishStatus: '已发布'
|
||||
reviewStatus: "审核通过",
|
||||
publishStatus: "已发布",
|
||||
},
|
||||
{
|
||||
key: '5',
|
||||
name: '标准化异常处理流程',
|
||||
teacher: '张三',
|
||||
key: "5",
|
||||
name: "标准化异常处理流程",
|
||||
teacher: "张三",
|
||||
courseDuration: 10,
|
||||
studyDuration: 10,
|
||||
studentCount: 6,
|
||||
rating: 4.0,
|
||||
reviewStatus: '审核通过',
|
||||
publishStatus: '已发布'
|
||||
reviewStatus: "审核通过",
|
||||
publishStatus: "已发布",
|
||||
},
|
||||
{
|
||||
key: '6',
|
||||
name: '企业经营法则',
|
||||
teacher: '张三',
|
||||
key: "6",
|
||||
name: "企业经营法则",
|
||||
teacher: "张三",
|
||||
courseDuration: 20,
|
||||
studyDuration: 20,
|
||||
studentCount: 6,
|
||||
rating: 3.0,
|
||||
reviewStatus: '审核通过',
|
||||
publishStatus: '已发布'
|
||||
reviewStatus: "审核通过",
|
||||
publishStatus: "已发布",
|
||||
},
|
||||
{
|
||||
key: '7',
|
||||
name: '京东方战略实践学习',
|
||||
teacher: '张三',
|
||||
key: "7",
|
||||
name: "京东方战略实践学习",
|
||||
teacher: "张三",
|
||||
courseDuration: 30,
|
||||
studyDuration: 30,
|
||||
studentCount: 7,
|
||||
rating: 5.0,
|
||||
reviewStatus: '审核通过',
|
||||
publishStatus: '已发布'
|
||||
reviewStatus: "审核通过",
|
||||
publishStatus: "已发布",
|
||||
},
|
||||
{
|
||||
key: '8',
|
||||
name: '市场营销精要之《如何在新环境下做好新产品整合营销上市》',
|
||||
teacher: '张三',
|
||||
key: "8",
|
||||
name: "市场营销精要之《如何在新环境下做好新产品整合营销上市》",
|
||||
teacher: "张三",
|
||||
courseDuration: 40,
|
||||
studyDuration: 40,
|
||||
studentCount: 89,
|
||||
rating: 4.0,
|
||||
reviewStatus: '审核中',
|
||||
publishStatus: '已发布'
|
||||
reviewStatus: "审核中",
|
||||
publishStatus: "已发布",
|
||||
},
|
||||
{
|
||||
key: '9',
|
||||
name: '2024热点论坛第三期:国际格局的基本结构及其相关问题',
|
||||
teacher: '张三',
|
||||
key: "9",
|
||||
name: "2024热点论坛第三期:国际格局的基本结构及其相关问题",
|
||||
teacher: "张三",
|
||||
courseDuration: 10,
|
||||
studyDuration: 10,
|
||||
studentCount: 1,
|
||||
rating: 3.0,
|
||||
reviewStatus: '审核中',
|
||||
publishStatus: '已发布'
|
||||
reviewStatus: "审核中",
|
||||
publishStatus: "已发布",
|
||||
},
|
||||
{
|
||||
key: '10',
|
||||
name: '2024热点论坛2-《新“人机”时代的生存与发展》',
|
||||
teacher: '张三',
|
||||
key: "10",
|
||||
name: "2024热点论坛2-《新“人机”时代的生存与发展》",
|
||||
teacher: "张三",
|
||||
courseDuration: 20,
|
||||
studyDuration: 20,
|
||||
studentCount: 2,
|
||||
rating: 5.0,
|
||||
reviewStatus: '审核驳回',
|
||||
publishStatus: '已发布'
|
||||
}
|
||||
reviewStatus: "审核驳回",
|
||||
publishStatus: "已发布",
|
||||
},
|
||||
],
|
||||
tableDataTotal: 100,
|
||||
tableLoading: false,
|
||||
categoryList: [
|
||||
{ value: 'all', label: '全部课程分类' },
|
||||
{ value: 'tech', label: '技术类' },
|
||||
{ value: 'manage', label: '管理类' },
|
||||
{ value: 'sale', label: '销售类' }
|
||||
{ value: "all", label: "全部课程分类" },
|
||||
{ value: "tech", label: "技术类" },
|
||||
{ value: "manage", label: "管理类" },
|
||||
{ value: "sale", label: "销售类" },
|
||||
],
|
||||
reviewStatusList: [
|
||||
{ value: 'all', label: '全部审核状态' },
|
||||
{ value: 'pending', label: '审核中' },
|
||||
{ value: 'approved', label: '审核通过' },
|
||||
{ value: 'rejected', label: '审核驳回' }
|
||||
{ value: "all", label: "全部审核状态" },
|
||||
{ value: "pending", label: "审核中" },
|
||||
{ value: "approved", label: "审核通过" },
|
||||
{ value: "rejected", label: "审核驳回" },
|
||||
],
|
||||
publishStatusList: [
|
||||
{ value: 'all', label: '全部发布状态' },
|
||||
{ value: 'draft', label: '未发布' },
|
||||
{ value: 'published', label: '已发布' }
|
||||
{ value: "all", label: "全部发布状态" },
|
||||
{ value: "draft", label: "未发布" },
|
||||
{ value: "published", label: "已发布" },
|
||||
],
|
||||
// 新增的下拉选项列表
|
||||
enableStatusList: [
|
||||
{ value: 'all', label: '全部启用状态' },
|
||||
{ value: 'enabled', label: '已启用' },
|
||||
{ value: 'disabled', label: '已禁用' }
|
||||
{ value: "all", label: "全部启用状态" },
|
||||
{ value: "enabled", label: "已启用" },
|
||||
{ value: "disabled", label: "已禁用" },
|
||||
],
|
||||
isPublicList: [
|
||||
{ value: 'all', label: '是否公开课' },
|
||||
{ value: 'yes', label: '是' },
|
||||
{ value: 'no', label: '否' }
|
||||
{ value: "all", label: "是否公开课" },
|
||||
{ value: "yes", label: "是" },
|
||||
{ value: "no", label: "否" },
|
||||
],
|
||||
resourceOwnerList: [
|
||||
{ value: 'all', label: '全部资源归属' },
|
||||
{ value: 'self', label: '自有' },
|
||||
{ value: 'third', label: '第三方' }
|
||||
{ value: "all", label: "全部资源归属" },
|
||||
{ value: "self", label: "自有" },
|
||||
{ value: "third", label: "第三方" },
|
||||
],
|
||||
createSourceList: [
|
||||
{ value: 'all', label: '全部创建来源' },
|
||||
{ value: 'manual', label: '手动创建' },
|
||||
{ value: 'import', label: '批量导入' },
|
||||
{ value: 'api', label: 'API创建' }
|
||||
{ value: "all", label: "全部创建来源" },
|
||||
{ value: "manual", label: "手动创建" },
|
||||
{ value: "import", label: "批量导入" },
|
||||
{ value: "api", label: "API创建" },
|
||||
],
|
||||
columns: [
|
||||
{
|
||||
title: '课程名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
className: 'h',
|
||||
title: "课程名称",
|
||||
dataIndex: "name",
|
||||
key: "name",
|
||||
className: "h",
|
||||
ellipsis: true,
|
||||
width: 200,
|
||||
sorter: true,
|
||||
fixed: "left"
|
||||
fixed: "left",
|
||||
},
|
||||
{
|
||||
title: '课程分类',
|
||||
dataIndex: 'teacher',
|
||||
className: 'h',
|
||||
key: 'teacher',
|
||||
align: 'center',
|
||||
title: "课程分类",
|
||||
dataIndex: "teacher",
|
||||
className: "h",
|
||||
key: "teacher",
|
||||
align: "center",
|
||||
|
||||
sorter: true
|
||||
sorter: true,
|
||||
},
|
||||
{
|
||||
title: '授课教师',
|
||||
dataIndex: 'teacher',
|
||||
className: 'h',
|
||||
key: 'teacher',
|
||||
align: 'center',
|
||||
title: "授课教师",
|
||||
dataIndex: "teacher",
|
||||
className: "h",
|
||||
key: "teacher",
|
||||
align: "center",
|
||||
|
||||
sorter: true
|
||||
sorter: true,
|
||||
},
|
||||
{
|
||||
title: '课程时长',
|
||||
dataIndex: 'courseDuration',
|
||||
className: 'h',
|
||||
key: 'courseDuration',
|
||||
align: 'center',
|
||||
title: "课程时长",
|
||||
dataIndex: "courseDuration",
|
||||
className: "h",
|
||||
key: "courseDuration",
|
||||
align: "center",
|
||||
|
||||
sorter: true
|
||||
sorter: true,
|
||||
},
|
||||
{
|
||||
title: '学习时长',
|
||||
dataIndex: 'studyDuration',
|
||||
className: 'h',
|
||||
key: 'studyDuration',
|
||||
align: 'center',
|
||||
title: "学习时长",
|
||||
dataIndex: "studyDuration",
|
||||
className: "h",
|
||||
key: "studyDuration",
|
||||
align: "center",
|
||||
|
||||
sorter: true
|
||||
sorter: true,
|
||||
},
|
||||
{
|
||||
title: '学习人数',
|
||||
dataIndex: 'studentCount',
|
||||
className: 'h',
|
||||
key: 'studentCount',
|
||||
align: 'center',
|
||||
title: "学习人数",
|
||||
dataIndex: "studentCount",
|
||||
className: "h",
|
||||
key: "studentCount",
|
||||
align: "center",
|
||||
|
||||
sorter: true
|
||||
sorter: true,
|
||||
},
|
||||
{
|
||||
title: '课程评分',
|
||||
dataIndex: 'rating',
|
||||
className: 'h',
|
||||
key: 'rating',
|
||||
align: 'center',
|
||||
title: "课程评分",
|
||||
dataIndex: "rating",
|
||||
className: "h",
|
||||
key: "rating",
|
||||
align: "center",
|
||||
|
||||
sorter: true
|
||||
sorter: true,
|
||||
},
|
||||
{
|
||||
title: '审核状态',
|
||||
dataIndex: 'reviewStatus',
|
||||
className: 'h',
|
||||
key: 'reviewStatus',
|
||||
align: 'center',
|
||||
title: "审核状态",
|
||||
dataIndex: "reviewStatus",
|
||||
className: "h",
|
||||
key: "reviewStatus",
|
||||
align: "center",
|
||||
|
||||
sorter: true
|
||||
sorter: true,
|
||||
},
|
||||
{
|
||||
title: '发布状态',
|
||||
dataIndex: 'publishStatus',
|
||||
className: 'h',
|
||||
key: 'publishStatus',
|
||||
align: 'center',
|
||||
title: "发布状态",
|
||||
dataIndex: "publishStatus",
|
||||
className: "h",
|
||||
key: "publishStatus",
|
||||
align: "center",
|
||||
|
||||
sorter: true
|
||||
sorter: true,
|
||||
},
|
||||
{
|
||||
title: '启停用状态',
|
||||
dataIndex: 'publishStatus',
|
||||
className: 'h',
|
||||
key: 'publishStatus',
|
||||
align: 'center',
|
||||
title: "启停用状态",
|
||||
dataIndex: "publishStatus",
|
||||
className: "h",
|
||||
key: "publishStatus",
|
||||
align: "center",
|
||||
|
||||
sorter: true
|
||||
sorter: true,
|
||||
},
|
||||
{
|
||||
title: '排序值',
|
||||
dataIndex: 'publishStatus',
|
||||
className: 'h',
|
||||
key: 'publishStatus',
|
||||
align: 'center',
|
||||
title: "排序值",
|
||||
dataIndex: "publishStatus",
|
||||
className: "h",
|
||||
key: "publishStatus",
|
||||
align: "center",
|
||||
|
||||
sorter: true
|
||||
sorter: true,
|
||||
},
|
||||
{
|
||||
title: '公开课',
|
||||
dataIndex: 'publishStatus',
|
||||
className: 'h',
|
||||
key: 'publishStatus',
|
||||
align: 'center',
|
||||
title: "公开课",
|
||||
dataIndex: "publishStatus",
|
||||
className: "h",
|
||||
key: "publishStatus",
|
||||
align: "center",
|
||||
|
||||
sorter: true
|
||||
sorter: true,
|
||||
},
|
||||
{
|
||||
title: '资源归属',
|
||||
dataIndex: 'publishStatus',
|
||||
className: 'h',
|
||||
key: 'publishStatus',
|
||||
align: 'center',
|
||||
title: "资源归属",
|
||||
dataIndex: "publishStatus",
|
||||
className: "h",
|
||||
key: "publishStatus",
|
||||
align: "center",
|
||||
|
||||
sorter: true
|
||||
sorter: true,
|
||||
},
|
||||
{
|
||||
title: '创建人',
|
||||
dataIndex: 'publishStatus',
|
||||
className: 'h',
|
||||
key: 'publishStatus',
|
||||
align: 'center',
|
||||
title: "创建人",
|
||||
dataIndex: "publishStatus",
|
||||
className: "h",
|
||||
key: "publishStatus",
|
||||
align: "center",
|
||||
|
||||
sorter: true
|
||||
sorter: true,
|
||||
},
|
||||
{
|
||||
title: '创建来源',
|
||||
dataIndex: 'publishStatus',
|
||||
className: 'h',
|
||||
key: 'publishStatus',
|
||||
align: 'center',
|
||||
title: "创建来源",
|
||||
dataIndex: "publishStatus",
|
||||
className: "h",
|
||||
key: "publishStatus",
|
||||
align: "center",
|
||||
|
||||
sorter: true
|
||||
sorter: true,
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'publishStatus',
|
||||
className: 'h',
|
||||
key: 'publishStatus',
|
||||
align: 'center',
|
||||
title: "创建时间",
|
||||
dataIndex: "publishStatus",
|
||||
className: "h",
|
||||
key: "publishStatus",
|
||||
align: "center",
|
||||
|
||||
sorter: true
|
||||
sorter: true,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'operation',
|
||||
key: 'operation',
|
||||
className: 'h',
|
||||
align: 'right',
|
||||
fixed: 'right',
|
||||
title: "操作",
|
||||
dataIndex: "operation",
|
||||
key: "operation",
|
||||
className: "h",
|
||||
align: "right",
|
||||
fixed: "right",
|
||||
width: 200,
|
||||
scopedSlots: { customRender: 'action' },
|
||||
sorter: false
|
||||
scopedSlots: { customRender: "action" },
|
||||
sorter: false,
|
||||
},
|
||||
]
|
||||
],
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
@@ -546,13 +556,17 @@ export default {
|
||||
searchSubmit() {},
|
||||
searchReset() {},
|
||||
showModal1() {},
|
||||
handleEdit() {},
|
||||
handleEdit() {
|
||||
this.$router.push({
|
||||
path: "/ProfessionalMode",
|
||||
});
|
||||
},
|
||||
editMaterial() {},
|
||||
grantPermission() {},
|
||||
deleteRecord() {},
|
||||
exportData() {},
|
||||
changePagination() {}
|
||||
}
|
||||
changePagination() {},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
105
src/views/courselibrary/components/createCourse.vue
Normal file
105
src/views/courselibrary/components/createCourse.vue
Normal file
@@ -0,0 +1,105 @@
|
||||
<script setup>
|
||||
import dragCollapse from "./dragCollapse.vue";
|
||||
import { ElButton, ElCheckbox } from "element-plus";
|
||||
import dragTable from "./dragTable.vue";
|
||||
import { message } from "ant-design-vue";
|
||||
defineOptions({
|
||||
name: "CreateCourse",
|
||||
});
|
||||
import { ref, reactive, watch, toRaw, computed } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { useCourseData } from "@/hooks/useCourseData";
|
||||
|
||||
// 使用课程数据hook
|
||||
const { courseMetadata, courseList, courseActionButtons, executeCourseOperation } = useCourseData();
|
||||
|
||||
// 定义表格列
|
||||
const tableColumns = [
|
||||
{
|
||||
title: "序号",
|
||||
key: "index",
|
||||
width: 80,
|
||||
align: "center",
|
||||
},
|
||||
{
|
||||
title: "节名称",
|
||||
key: "name",
|
||||
dataIndex: "name",
|
||||
},
|
||||
{
|
||||
title: "类型",
|
||||
key: "type",
|
||||
dataIndex: "type",
|
||||
align: "center",
|
||||
},
|
||||
{
|
||||
title: "操作",
|
||||
key: "action",
|
||||
width: 220,
|
||||
align: "center",
|
||||
},
|
||||
];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="create-course">
|
||||
<div class="course-header">
|
||||
<div class="title">课程名称</div>
|
||||
<span>创建时间:{{ courseMetadata.createTime }}</span>
|
||||
</div>
|
||||
<div class="course-content">
|
||||
<div style="padding: 10px">
|
||||
<el-button>添加章</el-button>
|
||||
<el-checkbox style="margin-left: 10px">顺序学习</el-checkbox>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<dragCollapse v-model:courseList="courseList">
|
||||
<template #title="{ course }">{{ course.title }}</template>
|
||||
<template #desc="{ course }"
|
||||
>若课程只有一个章节,将不在学员端显示该章节名称</template
|
||||
>
|
||||
<template #default="{ course, index }">
|
||||
<div class="drag-course-btn-content">
|
||||
<el-button
|
||||
v-for="btn in courseActionButtons"
|
||||
type="primary"
|
||||
class="btn-item"
|
||||
plain
|
||||
@click="executeCourseOperation(btn.fun, index)"
|
||||
>{{ btn.label }}</el-button
|
||||
>
|
||||
</div>
|
||||
<div>
|
||||
<!-- 修改:添加 groupId 和 tableId 属性以支持跨表格拖拽 -->
|
||||
<dragTable
|
||||
:data="course.data"
|
||||
:columns="tableColumns"
|
||||
:group-id="'course-chapters'"
|
||||
:table-id="'chapter-' + index"
|
||||
></dragTable>
|
||||
</div>
|
||||
</template>
|
||||
</dragCollapse>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.create-course {
|
||||
width: 100%;
|
||||
padding: 10px 20px;
|
||||
.course-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.drag-course-btn-content {
|
||||
padding: 0 10px;
|
||||
.btn-item + .btn-item {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
127
src/views/courselibrary/components/dragCollapse.vue
Normal file
127
src/views/courselibrary/components/dragCollapse.vue
Normal file
@@ -0,0 +1,127 @@
|
||||
<script setup>
|
||||
import { ref, watch } from "vue";
|
||||
import draggable from "vuedraggable";
|
||||
import { ElIcon } from "element-plus";
|
||||
import { Delete, ArrowUp, ArrowDown, Operation } from "@element-plus/icons-vue";
|
||||
|
||||
const props = defineProps({
|
||||
courseList: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
});
|
||||
|
||||
// 定义 emits 用于更新父组件数据
|
||||
const emit = defineEmits(["update:courseList"]);
|
||||
|
||||
// 使用 ref 存储列表数据
|
||||
const dragList = ref(
|
||||
props.courseList.map((item) => ({
|
||||
...item,
|
||||
isCollapsed: false,
|
||||
}))
|
||||
);
|
||||
|
||||
// 监听 props 变化,同步更新 dragList
|
||||
watch(
|
||||
() => props.courseList,
|
||||
(newVal) => {
|
||||
dragList.value = newVal.map((item) => ({
|
||||
...item,
|
||||
isCollapsed: false,
|
||||
}));
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
const toggleCollapse = (element) => {
|
||||
element.isCollapsed = !element.isCollapsed;
|
||||
};
|
||||
|
||||
const moveEnd = (e) => {
|
||||
// 拖拽结束时,更新父组件数据
|
||||
emit("update:courseList", dragList.value);
|
||||
};
|
||||
|
||||
const moveStart = (e) => {
|
||||
// 拖拽开始时,将 isCollapsed 设置为 false
|
||||
dragList.value.forEach((item) => {
|
||||
item.isCollapsed = true;
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<draggable
|
||||
v-model="dragList"
|
||||
item-key="id"
|
||||
handle=".move-icon"
|
||||
animation="500"
|
||||
@end="moveEnd"
|
||||
@start="moveStart"
|
||||
>
|
||||
<template #item="{ element, index }">
|
||||
<div class="drag-collapse">
|
||||
<div class="drag-collapse-header">
|
||||
<div>
|
||||
<span class="drag-collapse-header-title">
|
||||
<slot name="title" :course="element">视频可见名称</slot>
|
||||
</span>
|
||||
<span class="drag-collapse-header-desc">
|
||||
<slot name="desc" :course="element">
|
||||
若课程只有一个章节,将不在学员端显示该章节名称
|
||||
</slot>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<el-icon class="move-icon"><Operation /></el-icon>
|
||||
<el-icon><Delete /></el-icon>
|
||||
<span @click="toggleCollapse(element)">
|
||||
<el-icon v-if="!element.isCollapsed"><ArrowUp /></el-icon>
|
||||
<el-icon v-else><ArrowDown /></el-icon>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="drag-collapse-content" v-show="!element.isCollapsed">
|
||||
<slot :course="element" :index="index"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</draggable>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.drag-collapse {
|
||||
.drag-collapse-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 10px 10px 5px 10px;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
|
||||
.drag-collapse-header-title {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.drag-collapse-header-desc {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
margin-left: 15px;
|
||||
}
|
||||
.move-icon {
|
||||
cursor: move;
|
||||
}
|
||||
}
|
||||
|
||||
.drag-collapse-content {
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
:deep(.el-icon) {
|
||||
margin-left: 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
508
src/views/courselibrary/components/dragTable.vue
Normal file
508
src/views/courselibrary/components/dragTable.vue
Normal file
@@ -0,0 +1,508 @@
|
||||
<template>
|
||||
<div class="drag-table-container">
|
||||
<a-table
|
||||
:columns="processedColumns"
|
||||
:data-source="data"
|
||||
:components="components"
|
||||
:pagination="false"
|
||||
:show-header="true"
|
||||
bordered
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, h, computed } from "vue";
|
||||
import draggable from "vuedraggable";
|
||||
import { createVNode } from "vue";
|
||||
import {
|
||||
MenuOutlined,
|
||||
VideoCameraOutlined,
|
||||
AudioOutlined,
|
||||
FileTextOutlined,
|
||||
PictureOutlined,
|
||||
LinkOutlined,
|
||||
FolderOpenOutlined,
|
||||
BankOutlined,
|
||||
EditOutlined,
|
||||
DeleteOutlined,
|
||||
EyeOutlined,
|
||||
SettingOutlined,
|
||||
} from "@ant-design/icons-vue";
|
||||
|
||||
// 定义 props
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
columns: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
// 新增:用于区分不同的表格组
|
||||
groupId: {
|
||||
type: String,
|
||||
default: "default-group",
|
||||
},
|
||||
// 新增:表格唯一标识
|
||||
tableId: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
// 新增:是否允许拖出
|
||||
allowDragOut: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
// 新增:是否允许拖入
|
||||
allowDragIn: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
});
|
||||
|
||||
// 根据类型返回对应图标组件
|
||||
const getIconComponent = (type) => {
|
||||
switch (type) {
|
||||
case "视频":
|
||||
return VideoCameraOutlined;
|
||||
case "音频":
|
||||
return AudioOutlined;
|
||||
case "文档":
|
||||
return FileTextOutlined;
|
||||
case "图文":
|
||||
return PictureOutlined;
|
||||
case "链接":
|
||||
return LinkOutlined;
|
||||
case "SCORM":
|
||||
return FolderOpenOutlined;
|
||||
case "考试":
|
||||
return BankOutlined;
|
||||
case "作业":
|
||||
return EditOutlined;
|
||||
case "评估":
|
||||
return BankOutlined; // 可替换为更合适的图标
|
||||
default:
|
||||
return FileTextOutlined;
|
||||
}
|
||||
};
|
||||
|
||||
// 当前正在编辑的行的key
|
||||
const editingKey = ref("");
|
||||
// 编辑时的临时值
|
||||
const editValue = ref("");
|
||||
|
||||
// 开始编辑
|
||||
const startEdit = (record) => {
|
||||
editingKey.value = record.key;
|
||||
editValue.value = record.name;
|
||||
};
|
||||
|
||||
// 保存编辑
|
||||
const saveEdit = (record) => {
|
||||
record.name = editValue.value;
|
||||
editingKey.value = "";
|
||||
editValue.value = "";
|
||||
};
|
||||
|
||||
// 删除处理函数
|
||||
const handleDelete = (key) => {
|
||||
const index = props.data.findIndex((item) => item.key === key);
|
||||
if (index > -1) {
|
||||
props.data.splice(index, 1);
|
||||
console.log("删除了:", key);
|
||||
}
|
||||
};
|
||||
|
||||
// 处理列定义,添加自定义渲染逻辑
|
||||
const processedColumns = computed(() => {
|
||||
// 如果传入了 columns prop,则使用它并添加自定义渲染
|
||||
if (props.columns && props.columns.length > 0) {
|
||||
return props.columns.map((col) => {
|
||||
// 克隆列对象以避免修改原始 props
|
||||
const processedCol = { ...col };
|
||||
|
||||
// 为序号列添加自定义渲染
|
||||
if (col.key === "index") {
|
||||
processedCol.customRender = ({ index }) => {
|
||||
return h(
|
||||
"span",
|
||||
{
|
||||
style: {
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
},
|
||||
},
|
||||
[
|
||||
h("span", { class: "drag-handle" }, [
|
||||
createVNode(MenuOutlined, {
|
||||
style: { fontSize: "14px", color: "#666" },
|
||||
}),
|
||||
]),
|
||||
h("span", {}, index + 1),
|
||||
]
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
// 为节名称列添加自定义渲染
|
||||
if (col.key === "name") {
|
||||
processedCol.customRender = ({ record }) => {
|
||||
// 检查当前行是否处于编辑状态
|
||||
const isEditing = record.key === editingKey.value;
|
||||
|
||||
// 如果处于编辑状态,显示输入框和确认按钮
|
||||
if (isEditing) {
|
||||
return h(
|
||||
"span",
|
||||
{ style: { display: "flex", alignItems: "center", gap: "8px" } },
|
||||
[
|
||||
h("input", {
|
||||
value: editValue.value,
|
||||
onInput: (e) => {
|
||||
editValue.value = e.target.value;
|
||||
},
|
||||
style: {
|
||||
border: "1px solid #d9d9d9",
|
||||
borderRadius: "4px",
|
||||
padding: "4px 11px",
|
||||
width: "200px",
|
||||
},
|
||||
}),
|
||||
h(
|
||||
"a",
|
||||
{
|
||||
href: "javascript:void(0)",
|
||||
style: { fontSize: "16px", color: "#52c41a" },
|
||||
onClick: () => saveEdit(record),
|
||||
},
|
||||
"✓"
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
// 否则显示正常文本和编辑图标
|
||||
const Icon = getIconComponent(record.type);
|
||||
return h(
|
||||
"span",
|
||||
{ style: { display: "flex", alignItems: "center", gap: "8px" } },
|
||||
[
|
||||
createVNode(Icon, { style: { color: "#1890ff" } }),
|
||||
h("span", {}, record.name),
|
||||
h(
|
||||
"a",
|
||||
{
|
||||
href: "javascript:void(0)",
|
||||
style: { marginLeft: "4px", fontSize: "12px" },
|
||||
onClick: () => startEdit(record),
|
||||
},
|
||||
"✎"
|
||||
),
|
||||
]
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
// 为操作列添加自定义渲染
|
||||
if (col.key === "action") {
|
||||
processedCol.customRender = ({ record }) => {
|
||||
return h(
|
||||
"span",
|
||||
{
|
||||
style: { display: "flex", justifyContent: "center", gap: "12px" },
|
||||
},
|
||||
[
|
||||
// 设置
|
||||
h("a", { href: "javascript:void(0)" }, [
|
||||
createVNode(SettingOutlined, {
|
||||
style: {
|
||||
fontSize: "14px",
|
||||
color: "#1890ff",
|
||||
paddingRight: "10px",
|
||||
},
|
||||
}),
|
||||
h(
|
||||
"span",
|
||||
{ style: { marginLeft: "4px", fontSize: "12px" } },
|
||||
"设置"
|
||||
),
|
||||
]),
|
||||
// 预览
|
||||
h("a", { href: "javascript:void(0)" }, [
|
||||
createVNode(EyeOutlined, {
|
||||
style: { fontSize: "14px", color: "#1890ff" },
|
||||
}),
|
||||
h(
|
||||
"span",
|
||||
{ style: { marginLeft: "4px", fontSize: "12px" } },
|
||||
"预览"
|
||||
),
|
||||
]),
|
||||
// 删除
|
||||
h(
|
||||
"a",
|
||||
{
|
||||
href: "javascript:void(0)",
|
||||
onClick: () => handleDelete(record.key),
|
||||
},
|
||||
[
|
||||
createVNode(DeleteOutlined, {
|
||||
style: { fontSize: "14px", color: "red" },
|
||||
}),
|
||||
h(
|
||||
"span",
|
||||
{ style: { marginLeft: "4px", fontSize: "12px" } },
|
||||
"删除"
|
||||
),
|
||||
]
|
||||
),
|
||||
]
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
return processedCol;
|
||||
});
|
||||
}
|
||||
|
||||
// 否则使用默认列定义
|
||||
return [
|
||||
{
|
||||
title: "序号",
|
||||
key: "index",
|
||||
width: 80,
|
||||
align: "center",
|
||||
customRender: ({ index }) => {
|
||||
return h(
|
||||
"span",
|
||||
{
|
||||
style: {
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
},
|
||||
},
|
||||
[
|
||||
h("span", { class: "drag-handle" }, [
|
||||
createVNode(MenuOutlined, {
|
||||
style: { fontSize: "14px", color: "#666" },
|
||||
}),
|
||||
]),
|
||||
h("span", {}, index + 1),
|
||||
]
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "节名称",
|
||||
key: "name",
|
||||
dataIndex: "name",
|
||||
customRender: ({ record }) => {
|
||||
// 检查当前行是否处于编辑状态
|
||||
const isEditing = record.key === editingKey.value;
|
||||
|
||||
// 如果处于编辑状态,显示输入框和确认按钮
|
||||
if (isEditing) {
|
||||
return h(
|
||||
"span",
|
||||
{ style: { display: "flex", alignItems: "center", gap: "8px" } },
|
||||
[
|
||||
h("input", {
|
||||
value: editValue.value,
|
||||
onInput: (e) => {
|
||||
editValue.value = e.target.value;
|
||||
},
|
||||
style: {
|
||||
border: "1px solid #d9d9d9",
|
||||
borderRadius: "4px",
|
||||
padding: "4px 11px",
|
||||
width: "200px",
|
||||
},
|
||||
}),
|
||||
h(
|
||||
"a",
|
||||
{
|
||||
href: "javascript:void(0)",
|
||||
style: { fontSize: "16px", color: "#52c41a" },
|
||||
onClick: () => saveEdit(record),
|
||||
},
|
||||
"✓"
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
// 否则显示正常文本和编辑图标
|
||||
const Icon = getIconComponent(record.type);
|
||||
return h(
|
||||
"span",
|
||||
{ style: { display: "flex", alignItems: "center", gap: "8px" } },
|
||||
[
|
||||
createVNode(Icon, { style: { color: "#1890ff" } }),
|
||||
h("span", {}, record.name),
|
||||
h(
|
||||
"a",
|
||||
{
|
||||
href: "javascript:void(0)",
|
||||
style: { marginLeft: "4px", fontSize: "12px" },
|
||||
onClick: () => startEdit(record),
|
||||
},
|
||||
"✎"
|
||||
),
|
||||
]
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "类型",
|
||||
key: "type",
|
||||
dataIndex: "type",
|
||||
align: "center",
|
||||
},
|
||||
{
|
||||
title: "操作",
|
||||
key: "action",
|
||||
width: 220,
|
||||
align: "center",
|
||||
customRender: ({ record }) => {
|
||||
return h(
|
||||
"span",
|
||||
{ style: { display: "flex", justifyContent: "center", gap: "12px" } },
|
||||
[
|
||||
// 设置
|
||||
h("a", { href: "javascript:void(0)" }, [
|
||||
createVNode(SettingOutlined, {
|
||||
style: {
|
||||
fontSize: "14px",
|
||||
color: "#1890ff",
|
||||
paddingRight: "10px",
|
||||
},
|
||||
}),
|
||||
h(
|
||||
"span",
|
||||
{ style: { marginLeft: "4px", fontSize: "12px" } },
|
||||
"设置"
|
||||
),
|
||||
]),
|
||||
// 预览
|
||||
h("a", { href: "javascript:void(0)" }, [
|
||||
createVNode(EyeOutlined, {
|
||||
style: { fontSize: "14px", color: "#1890ff" },
|
||||
}),
|
||||
h(
|
||||
"span",
|
||||
{ style: { marginLeft: "4px", fontSize: "12px" } },
|
||||
"预览"
|
||||
),
|
||||
]),
|
||||
// 删除
|
||||
h(
|
||||
"a",
|
||||
{
|
||||
href: "javascript:void(0)",
|
||||
onClick: () => handleDelete(record.key),
|
||||
},
|
||||
[
|
||||
createVNode(DeleteOutlined, {
|
||||
style: { fontSize: "14px", color: "red" },
|
||||
}),
|
||||
h(
|
||||
"span",
|
||||
{ style: { marginLeft: "4px", fontSize: "12px" } },
|
||||
"删除"
|
||||
),
|
||||
]
|
||||
),
|
||||
]
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
// 构造自定义 body wrapper 支持 vuedraggable v4+ 的插槽要求
|
||||
const components = {
|
||||
body: {
|
||||
wrapper: (bodyProps, { slots }) => {
|
||||
return h(
|
||||
draggable,
|
||||
{
|
||||
tag: "tbody",
|
||||
list: props.data, // 使用传入的 props.data
|
||||
itemKey: "key",
|
||||
handle: ".drag-handle",
|
||||
ghostClass: "drag-ghost",
|
||||
animation: "300",
|
||||
// 新增:配置跨表格拖拽的组
|
||||
group: {
|
||||
name: props.groupId,
|
||||
pull: props.allowDragOut,
|
||||
put: props.allowDragIn,
|
||||
},
|
||||
onStart: () => {},
|
||||
onEnd: (evt) => {
|
||||
console.log("拖拽结束,新顺序:", props.data);
|
||||
},
|
||||
},
|
||||
{
|
||||
item: ({ element, index }) =>
|
||||
h(
|
||||
"tr",
|
||||
{ key: element.key },
|
||||
processedColumns.value.map((col) => {
|
||||
const td = h(
|
||||
"td",
|
||||
{},
|
||||
col.customRender
|
||||
? col.customRender({
|
||||
record: element,
|
||||
index: index,
|
||||
})
|
||||
: element[col.dataIndex]
|
||||
);
|
||||
return td;
|
||||
})
|
||||
),
|
||||
}
|
||||
);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.drag-table-container {
|
||||
padding: 20px;
|
||||
margin: 0 auto;
|
||||
h3 {
|
||||
margin-bottom: 16px;
|
||||
text-align: center;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
:deep(.ant-table-tbody .drag-ghost) {
|
||||
opacity: 0.5;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
:deep(.ant-table-tbody tr:hover) {
|
||||
background-color: #f6ffed !important;
|
||||
}
|
||||
|
||||
.current-order {
|
||||
margin-top: 24px;
|
||||
padding: 16px;
|
||||
border: 1px dashed #d9d9d9;
|
||||
border-radius: 4px;
|
||||
|
||||
h4 {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.order-item {
|
||||
padding: 4px 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
410
src/views/courselibrary/components/professionalmode.vue
Normal file
410
src/views/courselibrary/components/professionalmode.vue
Normal file
@@ -0,0 +1,410 @@
|
||||
<script setup>
|
||||
import { ref, defineOptions, reactive, onMounted } from "vue";
|
||||
import { ElMessage } from "element-plus";
|
||||
import {
|
||||
ElForm,
|
||||
ElFormItem,
|
||||
ElRadio,
|
||||
ElRadioGroup,
|
||||
ElUpload,
|
||||
ElButton,
|
||||
ElInput,
|
||||
ElCascader,
|
||||
ElSelect,
|
||||
} from "element-plus";
|
||||
import { getClassTree } from "@/api/modules/newApi";
|
||||
import filecloud from "@/components/FileCloud/index.vue";
|
||||
import { useUpload } from "@/hooks/useUpload";
|
||||
import { useCourseForm } from "@/hooks/useCourseForm";
|
||||
|
||||
defineOptions({
|
||||
name: "ProfessionalMode",
|
||||
});
|
||||
|
||||
// 使用上传hook
|
||||
const {
|
||||
fileList,
|
||||
loading,
|
||||
courseCoverurl,
|
||||
handleChange,
|
||||
beforeUpload
|
||||
} = useUpload();
|
||||
|
||||
// 使用表单hook
|
||||
const {
|
||||
formRef,
|
||||
formState,
|
||||
visibilityOptions,
|
||||
resetForm
|
||||
} = useCourseForm();
|
||||
|
||||
// 表单相关
|
||||
const labelCol = { style: { width: "80px" } };
|
||||
|
||||
// 数据相关
|
||||
const data = ref({
|
||||
typeOption: [],
|
||||
});
|
||||
|
||||
// 文件选择对话框
|
||||
const dlgFileChoose = ref({
|
||||
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 = () => {
|
||||
dlgFileChoose.value.show = true;
|
||||
};
|
||||
|
||||
const changeCourseImage = (img) => {
|
||||
if (!img.path) {
|
||||
return;
|
||||
}
|
||||
dlgFileChoose.value.show = false;
|
||||
courseInfo.value.coverImg = img.path;
|
||||
courseCoverurl.value = fileUrl + img.path;
|
||||
};
|
||||
|
||||
const choseChoose = () => {
|
||||
dlgFileChoose.value.show = false;
|
||||
};
|
||||
|
||||
// 表单提交
|
||||
const handleSubmit = () => {
|
||||
// formRef.value
|
||||
// .validate()
|
||||
// .then(() => {
|
||||
// console.log("Received values of form:", formState);
|
||||
// ElMessage.success("表单提交成功");
|
||||
// })
|
||||
// .catch((error) => {
|
||||
// console.log("Validate Failed:", error);
|
||||
// ElMessage.error("请检查表单填写内容");
|
||||
// });
|
||||
console.log("Received values of form:", formState);
|
||||
ElMessage.success("表单提交成功");
|
||||
};
|
||||
|
||||
// 表单重置
|
||||
const handleReset = () => {
|
||||
resetForm(courseCoverurl, fileList);
|
||||
};
|
||||
|
||||
// API调用
|
||||
const fetchApi = {
|
||||
getClassTree: () => {
|
||||
return getClassTree(1).then((res) => {
|
||||
data.value.typeOption = res.result;
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
const next = () => {
|
||||
// 注意:这里的路由跳转需要正确引入和使用vue-router
|
||||
// this.$router.push({
|
||||
// path: "/createcourse",
|
||||
// query: {
|
||||
// id: 1,
|
||||
// },
|
||||
// });
|
||||
};
|
||||
onMounted(() => {
|
||||
fetchApi.getClassTree();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="professional-mode">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formState"
|
||||
:label-position="'right'"
|
||||
label-width="80px"
|
||||
class="default-form"
|
||||
@submit.prevent="handleSubmit"
|
||||
>
|
||||
<div class="professional-mode-title">
|
||||
<div class="professional-mode-title-text" style="padding-bottom: 20px">
|
||||
基本信息
|
||||
</div>
|
||||
<div class="professional-mode-form">
|
||||
<el-form-item
|
||||
label="课程名称"
|
||||
prop="courseName"
|
||||
:rules="[{ required: true, message: '请输入课程名称' }]"
|
||||
>
|
||||
<el-input
|
||||
size="large"
|
||||
v-model="formState.courseName"
|
||||
placeholder="请输入课程名称"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item
|
||||
label="课程分类"
|
||||
prop="courseCategory"
|
||||
:rules="[{ required: true, message: '请选择课程分类' }]"
|
||||
>
|
||||
<el-cascader
|
||||
size="large"
|
||||
v-model="formState.courseCategory"
|
||||
placeholder="请选择课程分类"
|
||||
:options="data.typeOption"
|
||||
:props="{
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
}"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item
|
||||
label="资源归属"
|
||||
prop="resourceBelong"
|
||||
:rules="[{ required: true, message: '请选择资源归属' }]"
|
||||
>
|
||||
<el-select
|
||||
size="large"
|
||||
v-model="formState.resourceBelong"
|
||||
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
|
||||
label="授课教师"
|
||||
prop="lecturer"
|
||||
:rules="[{ required: true, message: '请选择授课教师' }]"
|
||||
>
|
||||
<el-select
|
||||
size="large"
|
||||
v-model="formState.lecturer"
|
||||
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
|
||||
label="目标人群"
|
||||
prop="targetGroup"
|
||||
:rules="[{ required: true, message: '请输入目标人群' }]"
|
||||
>
|
||||
<el-input
|
||||
size="large"
|
||||
v-model="formState.targetGroup"
|
||||
placeholder="请输入目标人群"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="课程标签" prop="courseTags">
|
||||
<el-select
|
||||
size="large"
|
||||
v-model="formState.courseTags"
|
||||
multiple
|
||||
filterable
|
||||
allow-create
|
||||
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 label="受众" prop="audience">
|
||||
<el-select
|
||||
size="large"
|
||||
v-model="formState.audience"
|
||||
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 label="可见性" prop="visibility">
|
||||
<el-radio-group v-model="formState.visibility">
|
||||
<el-radio
|
||||
v-for="item in visibilityOptions"
|
||||
:key="item.value"
|
||||
:label="item.value"
|
||||
>
|
||||
<span
|
||||
:style="{
|
||||
color:
|
||||
formState.visibility === item.value ? '#409eff' : '#000',
|
||||
}"
|
||||
>{{ item.label }}</span
|
||||
>
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</div>
|
||||
|
||||
<div class="professional-mode-title-text" style="padding-top: 0">
|
||||
课程介绍
|
||||
</div>
|
||||
|
||||
<div class="professional-mode-form">
|
||||
<el-form-item label="封面介绍" prop="coverIntro">
|
||||
<div style="display: flex; align-items: flex-end">
|
||||
<el-upload
|
||||
v-model:file-list="fileList"
|
||||
name="avatar"
|
||||
list-type="picture-card"
|
||||
class="avatar-uploader"
|
||||
:show-file-list="false"
|
||||
action="https://www.mocky.io/v2/5cc8019d300000980a055e76"
|
||||
:before-upload="beforeUpload"
|
||||
:on-change="handleChange"
|
||||
>
|
||||
<img
|
||||
v-if="courseCoverurl"
|
||||
:src="courseCoverurl"
|
||||
alt="avatar"
|
||||
style="width: 100%"
|
||||
/>
|
||||
<div v-else>
|
||||
<el-icon v-if="loading"><Loading /></el-icon>
|
||||
<el-icon v-else><Plus /></el-icon>
|
||||
<div class="el-upload-text">上传图片</div>
|
||||
</div>
|
||||
</el-upload>
|
||||
<el-button type="primary" link @click="chooseFile"
|
||||
>选择封面</el-button
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="upload-hint">
|
||||
建议上传800px*450px(16:9)的图片,格式为.png或.jpg,大小不超过2M
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item
|
||||
label="课程价值"
|
||||
prop="courseValue"
|
||||
:rules="[{ required: true, message: '请输入课程价值' }]"
|
||||
>
|
||||
<el-input
|
||||
type="textarea"
|
||||
size="large"
|
||||
v-model="formState.courseValue"
|
||||
:rows="3"
|
||||
placeholder="请输入课程价值"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item
|
||||
label="课程简介"
|
||||
prop="courseIntro"
|
||||
:rules="[{ required: true, message: '请输入课程简介' }]"
|
||||
>
|
||||
<el-input
|
||||
type="textarea"
|
||||
size="large"
|
||||
v-model="formState.courseIntro"
|
||||
:rows="4"
|
||||
placeholder="请输入课程简介"
|
||||
/>
|
||||
</el-form-item>
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<el-button
|
||||
style="margin-left: 80px; margin-right: 12px"
|
||||
size="large"
|
||||
@click="handleReset"
|
||||
>存草稿</el-button
|
||||
>
|
||||
<el-button type="primary" @click="next">创建下一步</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-form>
|
||||
<filecloud
|
||||
:show="dlgFileChoose.show"
|
||||
@choose="changeCourseImage"
|
||||
@close="choseChoose"
|
||||
></filecloud>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.professional-mode {
|
||||
width: 70%;
|
||||
|
||||
.default-form {
|
||||
width: 100%;
|
||||
margin-bottom: 30px;
|
||||
padding: 10px 15px;
|
||||
}
|
||||
|
||||
.professional-mode-title {
|
||||
width: 100%;
|
||||
|
||||
.professional-mode-title-text {
|
||||
font-size: 15px;
|
||||
padding: 10px 0;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.professional-mode-form {
|
||||
width: 100%;
|
||||
|
||||
.upload-hint {
|
||||
margin-top: 8px;
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
:deep(.el-form-item) {
|
||||
margin-bottom: 22px;
|
||||
}
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
:deep(.el-upload--picture-card) {
|
||||
width: 200px;
|
||||
height: 112px;
|
||||
}
|
||||
|
||||
:deep(.el-upload-list--picture-card) {
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
:deep(.el-upload-list--picture-card .el-upload-list__item) {
|
||||
width: 200px;
|
||||
height: 112px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user