diff --git a/.env.development b/.env.development index 92b009e..046e90d 100644 --- a/.env.development +++ b/.env.development @@ -2,7 +2,7 @@ ENV = 'development' VITE_CLI_PORT = 8088 VITE_SERVER_PORT = 8888 VITE_BASE_API = /api -VITE_BASE_PATH = http://192.168.1.208 +VITE_BASE_PATH = http://192.168.1.40 VITE_EDITOR = vscode // VITE_EDITOR = webstorm 如果使用webstorm开发且要使用dom定位到代码行功能 请先自定添加 webstorm到环境变量 再将VITE_EDITOR值修改为webstorm // 如果使用docker-compose开发模式,设置为下面的地址或本机主机IP diff --git a/package.json b/package.json index 689df17..d33434d 100644 --- a/package.json +++ b/package.json @@ -1,52 +1,54 @@ { - "name": "gin-vue-admin", - "version": "2.5.5", - "private": true, - "scripts": { - "serve": "vite --host --mode development", - "build": "vite build --mode production", - "limit-build": "npm install increase-memory-limit-fixbug cross-env -g && npm run fix-memory-limit && node ./limit && npm run build", - "preview": "vite preview", - "fix-memory-limit": "cross-env LIMIT=4096 increase-memory-limit" - }, - "dependencies": { - "@element-plus/icons-vue": "^0.2.7", - "axios": "^0.19.2", - "core-js": "^3.6.5", - "echarts": "5.3.2", - "element-plus": "2.2.9", - "highlight.js": "^10.6.0", - "marked": "^2.0.0", - "mitt": "^3.0.0", - "nprogress": "^0.2.0", - "path": "^0.12.7", - "pinia": "^2.0.9", - "qs": "^6.8.0", - "quill": "^1.3.7", - "screenfull": "^5.0.2", - "spark-md5": "^3.0.1", - "vue": "^3.2.25", - "vue-router": "^4.0.0-0" - }, - "devDependencies": { - "@vitejs/plugin-legacy": "^2.0.0", - "@vitejs/plugin-vue": "^3.0.1", - "@vue/cli-plugin-babel": "~4.5.0", - "@vue/cli-plugin-eslint": "~4.5.0", - "@vue/cli-plugin-router": "~4.5.0", - "@vue/cli-plugin-vuex": "~4.5.0", - "@vue/cli-service": "~4.5.0", - "@vue/compiler-sfc": "^3.1.5", - "babel-eslint": "^10.1.0", - "babel-plugin-import": "^1.13.3", - "chalk": "^4.1.2", - "dotenv": "^10.0.0", - "eslint": "^6.7.2", - "eslint-plugin-vue": "^7.0.0", - "sass": "^1.54.0", - "terser": "^5.4.0", - "vite": "^3.0.1", - "vite-plugin-banner": "^0.1.3", - "vite-plugin-importer": "^0.2.5" - } + "name": "gin-vue-admin", + "version": "2.5.5", + "private": true, + "scripts": { + "serve": "vite --host --mode development", + "build": "vite build --mode production", + "limit-build": "npm install increase-memory-limit-fixbug cross-env -g && npm run fix-memory-limit && node ./limit && npm run build", + "preview": "vite preview", + "fix-memory-limit": "cross-env LIMIT=4096 increase-memory-limit" + }, + "dependencies": { + "@element-plus/icons-vue": "^0.2.7", + "ali-oss": "^6.17.1", + "axios": "^0.19.2", + "core-js": "^3.6.5", + "echarts": "5.3.2", + "element-plus": "2.2.9", + "highlight.js": "^10.6.0", + "js-cookie": "^3.0.1", + "marked": "^2.0.0", + "mitt": "^3.0.0", + "nprogress": "^0.2.0", + "path": "^0.12.7", + "pinia": "^2.0.9", + "qs": "^6.8.0", + "quill": "^1.3.7", + "screenfull": "^5.0.2", + "spark-md5": "^3.0.1", + "vue": "^3.2.25", + "vue-router": "^4.0.0-0" + }, + "devDependencies": { + "@vitejs/plugin-legacy": "^2.0.0", + "@vitejs/plugin-vue": "^3.0.1", + "@vue/cli-plugin-babel": "~4.5.0", + "@vue/cli-plugin-eslint": "~4.5.0", + "@vue/cli-plugin-router": "~4.5.0", + "@vue/cli-plugin-vuex": "~4.5.0", + "@vue/cli-service": "~4.5.0", + "@vue/compiler-sfc": "^3.1.5", + "babel-eslint": "^10.1.0", + "babel-plugin-import": "^1.13.3", + "chalk": "^4.1.2", + "dotenv": "^10.0.0", + "eslint": "^6.7.2", + "eslint-plugin-vue": "^7.0.0", + "sass": "^1.54.0", + "terser": "^5.4.0", + "vite": "^3.0.1", + "vite-plugin-banner": "^0.1.3", + "vite-plugin-importer": "^0.2.5" + } } diff --git a/src/App.vue b/src/App.vue index 9eaaf40..51a233d 100644 --- a/src/App.vue +++ b/src/App.vue @@ -24,4 +24,24 @@ export default { .el-button{ font-weight: 400 !important; } +.avatar-uploader .el-upload { + border: 1px dashed var(--el-border-color); + border-radius: 6px; + cursor: pointer; + position: relative; + overflow: hidden; + transition: var(--el-transition-duration-fast); +} + +.avatar-uploader .el-upload:hover { + border-color: var(--el-color-primary); +} + +.el-icon.avatar-uploader-icon { + font-size: 28px; + color: #8c939d; + width: 178px; + height: 178px; + text-align: center; +} diff --git a/src/api/common.js b/src/api/common.js new file mode 100644 index 0000000..df94a5b --- /dev/null +++ b/src/api/common.js @@ -0,0 +1,15 @@ +import service from '@/utils/request' +const headers = { + "Content-Type": "multipart/form-data;binary", +}; +const api = { + upload:data=>{//文件上传 + return service({ + data, + url: '/public/uploadFile', + method: 'POST', + headers + }) + } +} +export default api diff --git a/src/api/course.js b/src/api/course.js index 35d7654..c2f1f90 100644 --- a/src/api/course.js +++ b/src/api/course.js @@ -8,6 +8,33 @@ const api = { params:data }) }, + getCourse : data => { // 获取课程详情 + return service({ + url: '/sys-course/'+data.id, + method: 'get' + }) + }, + addCourse : data => { // 添加课程 + return service({ + url: '/sys-course', + method: 'post', + data + }) + }, + editCourse : data => { // 编辑课程 + return service({ + url: '/sys-course', + method: 'put', + data + }) + }, + delCourse : data => { // 删除课程 + return service({ + url: '/sys-course', + method: 'delete', + data + }) + }, // 课程分类api getSubjectList : data => { return service({ @@ -37,5 +64,33 @@ const api = { data }) }, + // 章节api + getChapter:data => { + return service({ + url: '/sys-course-ware/'+data.id, + method: 'get' + }) + }, + addChapter : data => { + return service({ + url: '/sys-course-ware', + method: 'post', + data + }) + }, + editChapter : data => { + return service({ + url: '/sys-course-ware/update', + method: 'put', + data + }) + }, + delChapter : data => { + return service({ + url: '/sys-course-ware', + method: 'delete', + data + }) + }, } export default api diff --git a/src/utils/auth.js b/src/utils/auth.js new file mode 100644 index 0000000..08a43d6 --- /dev/null +++ b/src/utils/auth.js @@ -0,0 +1,15 @@ +import Cookies from 'js-cookie' + +const TokenKey = 'Admin-Token' + +export function getToken() { + return Cookies.get(TokenKey) +} + +export function setToken(token) { + return Cookies.set(TokenKey, token) +} + +export function removeToken() { + return Cookies.remove(TokenKey) +} diff --git a/src/utils/index.js b/src/utils/index.js new file mode 100644 index 0000000..789dfe3 --- /dev/null +++ b/src/utils/index.js @@ -0,0 +1,431 @@ +/** + * Created by PanJiaChen on 16/11/18. + */ + +import OSS from 'ali-oss' + +/** + * Parse the time to string + * @param {(Object|string|number)} time + * @param {string} cFormat + * @returns {string | null} + */ +export function parseTime(time, cFormat) { + if (arguments.length === 0) { + return null + } + if (time_str.indexOf('01-01-01') > -1) { + return '-' + } + const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}' + let date + if (typeof time === 'object') { + date = time + } else { + if ((typeof time === 'string') && (/^[0-9]+$/.test(time))) { + time = parseInt(time) + } + if ((typeof time === 'number') && (time.toString().length === 10)) { + time = time * 1000 + } + date = new Date(time) + } + const formatObj = { + y: date.getFullYear(), + m: date.getMonth() + 1, + d: date.getDate(), + h: date.getHours(), + i: date.getMinutes(), + s: date.getSeconds(), + a: date.getDay() + } + const time_str = format.replace(/{([ymdhisa])+}/g, (result, key) => { + const value = formatObj[key] + // Note: getDay() returns 0 on Sunday + if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value] } + return value.toString().padStart(2, '0') + }) + + return time_str +} + +/** + * @param {number} time + * @param {string} option + * @returns {string} + */ +export function formatTime(time, option) { + if (('' + time).length === 10) { + time = parseInt(time) * 1000 + } else { + time = +time + } + const d = new Date(time) + const now = Date.now() + + const diff = (now - d) / 1000 + + if (diff < 30) { + return '刚刚' + } else if (diff < 3600) { + // less 1 hour + return Math.ceil(diff / 60) + '分钟前' + } else if (diff < 3600 * 24) { + return Math.ceil(diff / 3600) + '小时前' + } else if (diff < 3600 * 24 * 2) { + return '1天前' + } + if (option) { + return parseTime(time, option) + } else { + return ( + d.getMonth() + + 1 + + '月' + + d.getDate() + + '日' + + d.getHours() + + '时' + + d.getMinutes() + + '分' + ) + } +} + +/** + * @param {string} url + * @returns {Object} + */ +export function getQueryObject(url) { + url = url == null ? window.location.href : url + const search = url.substring(url.lastIndexOf('?') + 1) + const obj = {} + const reg = /([^?&=]+)=([^?&=]*)/g + search.replace(reg, (rs, $1, $2) => { + const name = decodeURIComponent($1) + let val = decodeURIComponent($2) + val = String(val) + obj[name] = val + return rs + }) + return obj +} + +/** + * @param {string} input value + * @returns {number} output value + */ +export function byteLength(str) { + // returns the byte length of an utf8 string + let s = str.length + for (var i = str.length - 1; i >= 0; i--) { + const code = str.charCodeAt(i) + if (code > 0x7f && code <= 0x7ff) s++ + else if (code > 0x7ff && code <= 0xffff) s += 2 + if (code >= 0xDC00 && code <= 0xDFFF) i-- + } + return s +} + +/** + * @param {Array} actual + * @returns {Array} + */ +export function cleanArray(actual) { + const newArray = [] + for (let i = 0; i < actual.length; i++) { + if (actual[i]) { + newArray.push(actual[i]) + } + } + return newArray +} + +/** + * @param {Object} json + * @returns {Array} + */ +export function param(json) { + if (!json) return '' + return cleanArray( + Object.keys(json).map(key => { + if (json[key] === undefined) return '' + return encodeURIComponent(key) + '=' + encodeURIComponent(json[key]) + }) + ).join('&') +} + +/** + * @param {string} url + * @returns {Object} + */ +export function param2Obj(url) { + const search = url.split('?')[1] + if (!search) { + return {} + } + return JSON.parse( + '{"' + + decodeURIComponent(search) + .replace(/"/g, '\\"') + .replace(/&/g, '","') + .replace(/=/g, '":"') + .replace(/\+/g, ' ') + + '"}' + ) +} + +/** + * @param {string} val + * @returns {string} + */ +export function html2Text(val) { + const div = document.createElement('div') + div.innerHTML = val + return div.textContent || div.innerText +} + +/** + * Merges two objects, giving the last one precedence + * @param {Object} target + * @param {(Object|Array)} source + * @returns {Object} + */ +export function objectMerge(target, source) { + if (typeof target !== 'object') { + target = {} + } + if (Array.isArray(source)) { + return source.slice() + } + Object.keys(source).forEach(property => { + const sourceProperty = source[property] + if (typeof sourceProperty === 'object') { + target[property] = objectMerge(target[property], sourceProperty) + } else { + target[property] = sourceProperty + } + }) + return target +} + +/** + * @param {HTMLElement} element + * @param {string} className + */ +export function toggleClass(element, className) { + if (!element || !className) { + return + } + let classString = element.className + const nameIndex = classString.indexOf(className) + if (nameIndex === -1) { + classString += '' + className + } else { + classString = + classString.substr(0, nameIndex) + + classString.substr(nameIndex + className.length) + } + element.className = classString +} + +/** + * @param {string} type + * @returns {Date} + */ +export function getTime(type) { + if (type === 'start') { + return new Date().getTime() - 3600 * 1000 * 24 * 90 + } else { + return new Date(new Date().toDateString()) + } +} + +/** + * @param {Function} func + * @param {number} wait + * @param {boolean} immediate + * @return {*} + */ +export function debounce(func, wait, immediate) { + let timeout, args, context, timestamp, result + + const later = function() { + // 据上一次触发时间间隔 + const last = +new Date() - timestamp + + // 上次被包装函数被调用时间间隔 last 小于设定时间间隔 wait + if (last < wait && last > 0) { + timeout = setTimeout(later, wait - last) + } else { + timeout = null + // 如果设定为immediate===true,因为开始边界已经调用过了此处无需调用 + if (!immediate) { + result = func.apply(context, args) + if (!timeout) context = args = null + } + } + } + + return function(...args) { + context = this + timestamp = +new Date() + const callNow = immediate && !timeout + // 如果延时不存在,重新设定延时 + if (!timeout) timeout = setTimeout(later, wait) + if (callNow) { + result = func.apply(context, args) + context = args = null + } + + return result + } +} + +/** + * This is just a simple version of deep copy + * Has a lot of edge cases bug + * If you want to use a perfect deep copy, use lodash's _.cloneDeep + * @param {Object} source + * @returns {Object} + */ +export function deepClone(source) { + if (!source && typeof source !== 'object') { + throw new Error('error arguments', 'deepClone') + } + const targetObj = source.constructor === Array ? [] : {} + Object.keys(source).forEach(keys => { + if (source[keys] && typeof source[keys] === 'object') { + targetObj[keys] = deepClone(source[keys]) + } else { + targetObj[keys] = source[keys] + } + }) + return targetObj +} + +/** + * @param {Array} arr + * @returns {Array} + */ +export function uniqueArr(arr) { + return Array.from(new Set(arr)) +} + +/** + * @returns {string} + */ +export function createUniqueString() { + const timestamp = +new Date() + '' + const randomNum = parseInt((1 + Math.random()) * 65536) + '' + return (+(randomNum + timestamp)).toString(32) +} + +/** + * Check if an element has a class + * @param {HTMLElement} elm + * @param {string} cls + * @returns {boolean} + */ +export function hasClass(ele, cls) { + return !!ele.className.match(new RegExp('(\\s|^)' + cls + '(\\s|$)')) +} + +/** + * Add class to element + * @param {HTMLElement} elm + * @param {string} cls + */ +export function addClass(ele, cls) { + if (!hasClass(ele, cls)) ele.className += ' ' + cls +} + +/** + * Remove class from element + * @param {HTMLElement} elm + * @param {string} cls + */ +export function removeClass(ele, cls) { + if (hasClass(ele, cls)) { + const reg = new RegExp('(\\s|^)' + cls + '(\\s|$)') + ele.className = ele.className.replace(reg, ' ') + } +} + +/** + * @param {Array} filterVal + * @param {Object} jsonData + * @returns {string} + */ +export function formatJson(filterVal, jsonData) { + return jsonData.map(v => filterVal.map(j => { + if (j === 'timestamp') { + return parseTime(v[j]) + } else { + return v[j] + } + })) +} + +// 格式化价格 +export function prices(n, type = 'except', per = 100) { + if (!n) return '0.00' + return type === 'multiply' ? (n * per) : `${n / per}`.includes('.') ? (n / per) : `${n / per}.00` +} + +export function beforeUpload(file) { + const isLt05M = file.size / 1024 / 1024 < 0.5 + const isJPG = file.type.indexOf('image/') === -1 + if (isJPG) { + this.$message.error('文件格式错误,请上传图片类型,如:JPG,PNG后缀的文件') + } + if (!isLt05M) { + this.$message.error('上传头像图片大小不能超过 500k!') + } + return !isJPG && isLt05M +} + +export function formateCardNumber(str) { + if (!str) return '-' + if (str.length < 8) { + return str + } + const leftString = str.substring(0, 4) + const rightString = str.substring(str.length - 4) + return `${leftString}***${rightString}` +} + +export const formatNumber = (str, decimals = 2) => `${parseFloat(str.toFixed(decimals)).toLocaleString()}` + +// 拍平数组 +export function flatten(arr) { + if (!arr) return [] + let arr1 = [] + arr.forEach((val) => { + arr1.push(val) + if (val?.children) { + arr1 = arr1.concat(flatten(val.children)) + } + }) + return arr1 +} + +export function clients() { + let client = {} + client = new OSS({ + endpoint: 'oss-cn-chengdu.aliyuncs.com', + accessKeyId: 'LTAI5t7K52jxxBCvYtS3CCm3', + accessKeySecret: 'VFLO6JDorP8fFmjZuxsSCxL5zzHMgW', + bucket: 'gwjxb', + timeout: 10 * 60 * 1000 + }) + return client +} + +export function getNowDate() { + const date = new Date() + const year = date.getFullYear() // 年 + const month = date.getMonth() + 1 // 月 + const day = date.getDate() + return `${year}/${month}/${day}` +} diff --git a/src/view/course/addCourse/index.vue b/src/view/course/addCourse/index.vue new file mode 100644 index 0000000..76c2eb2 --- /dev/null +++ b/src/view/course/addCourse/index.vue @@ -0,0 +1,241 @@ + + + diff --git a/src/view/course/components/chapter.vue b/src/view/course/components/chapter.vue new file mode 100644 index 0000000..b1c4aaa --- /dev/null +++ b/src/view/course/components/chapter.vue @@ -0,0 +1,315 @@ + + + + diff --git a/src/view/course/components/mediaPool.vue b/src/view/course/components/mediaPool.vue new file mode 100644 index 0000000..c8d6ce1 --- /dev/null +++ b/src/view/course/components/mediaPool.vue @@ -0,0 +1,141 @@ + + + diff --git a/src/view/course/courseCategory/index.vue b/src/view/course/courseCategory/index.vue index 42a0ce5..ef4c4be 100644 --- a/src/view/course/courseCategory/index.vue +++ b/src/view/course/courseCategory/index.vue @@ -1,9 +1,9 @@ +
+ +
- + diff --git a/src/view/course/index.vue b/src/view/course/index.vue index d3398ea..c08491b 100644 --- a/src/view/course/index.vue +++ b/src/view/course/index.vue @@ -1,10 +1,14 @@