├── .prettierignore ├── public └── favicon.ico ├── src ├── assets │ ├── images │ │ ├── logo.png │ │ ├── weixin.jpg │ │ ├── weixin.png │ │ ├── common │ │ │ ├── 403.png │ │ │ ├── 404.png │ │ │ └── beian.png │ │ ├── check.svg │ │ ├── folder.svg │ │ └── logo.svg │ ├── svg │ │ ├── 课程.svg │ │ ├── 装修.svg │ │ ├── 用户.svg │ │ ├── 概况.svg │ │ ├── 系统.svg │ │ └── 常用.svg │ └── styles │ │ └── index.scss ├── store │ ├── index.js │ └── modules │ │ ├── website.js │ │ ├── upload.js │ │ └── user.js ├── utils │ ├── bus.js │ ├── constants │ │ └── system.js │ ├── permission.js │ ├── cookie.js │ ├── storage.js │ ├── vod │ │ ├── simple.js │ │ └── polyv.js │ ├── base.js │ └── table.js ├── api │ ├── dashboard.js │ ├── login.js │ ├── upload.js │ └── users.js ├── App.vue ├── views │ ├── 404.vue │ ├── 403.vue │ ├── system │ │ ├── config │ │ │ ├── View.vue │ │ │ └── index.vue │ │ ├── log │ │ │ ├── LogView.vue │ │ │ └── index.vue │ │ ├── role │ │ │ ├── RoleForm.vue │ │ │ ├── MenuSet.vue │ │ │ └── index.vue │ │ ├── user │ │ │ ├── Password.vue │ │ │ ├── RoleSet.vue │ │ │ └── UserForm.vue │ │ └── app │ │ │ ├── index.vue │ │ │ └── AppForm.vue │ ├── dashboard │ │ ├── StatLogin.vue │ │ ├── index.vue │ │ └── StatData.vue │ ├── course │ │ ├── record │ │ │ ├── Collect.vue │ │ │ ├── Study.vue │ │ │ ├── Comment.vue │ │ │ ├── Course.vue │ │ │ ├── index.vue │ │ │ └── Order.vue │ │ ├── chapter │ │ │ ├── ChapterForm.vue │ │ │ └── PeriodForm.vue │ │ ├── resource │ │ │ ├── ResourceForm.vue │ │ │ └── MoveModel.vue │ │ └── category │ │ │ ├── CategoryForm.vue │ │ │ └── index.vue │ ├── users │ │ ├── record │ │ │ ├── Study.vue │ │ │ ├── Account.vue │ │ │ ├── Course.vue │ │ │ ├── ConsumeForm.vue │ │ │ ├── index.vue │ │ │ └── Order.vue │ │ ├── log │ │ │ └── index.vue │ │ ├── list │ │ │ └── UsersForm.vue │ │ └── lecturer │ │ │ ├── LecturerForm.vue │ │ │ └── index.vue │ └── common │ │ ├── order │ │ └── OrderForm.vue │ │ ├── navigation │ │ ├── NavigationForm.vue │ │ └── index.vue │ │ ├── zone │ │ └── ZoneForm.vue │ │ ├── link │ │ ├── LinkForm.vue │ │ └── index.vue │ │ └── carousel │ │ └── CarouselForm.vue ├── layout │ ├── components │ │ ├── Navbar │ │ │ ├── Logo.vue │ │ │ ├── index.vue │ │ │ ├── Breadcrumb.vue │ │ │ └── User.vue │ │ └── Mains.vue │ └── index.vue ├── components │ ├── Enum │ │ ├── View │ │ │ └── index.vue │ │ ├── Radio │ │ │ └── index.vue │ │ └── Select │ │ │ └── index.vue │ ├── Cascader │ │ ├── Course │ │ │ └── index.vue │ │ └── Category │ │ │ └── CategoryForm.vue │ ├── Icon │ │ └── index.vue │ ├── Editor │ │ ├── index.js │ │ ├── Custom │ │ │ └── image.js │ │ └── index.vue │ ├── Selector │ │ ├── Svg │ │ │ └── index.vue │ │ ├── Lecturer │ │ │ └── index.vue │ │ ├── Image │ │ │ └── index.vue │ │ └── Course │ │ │ └── index.vue │ ├── Upload │ │ ├── App │ │ │ └── index.vue │ │ └── File │ │ │ └── index.vue │ ├── Pagination │ │ └── index.vue │ └── Preview │ │ └── index.vue ├── main.js └── router │ └── index.js ├── distribution ├── images │ ├── gzh.png │ ├── logo.jpg │ ├── web1.png │ ├── web2.png │ ├── web3.png │ ├── web4.png │ ├── admin1.png │ ├── admin2.png │ ├── admin3.png │ └── admin4.png ├── assembly.xml └── pom.xml ├── jsconfig.json ├── .prettierrc.json ├── .gitignore ├── eslint.config.js ├── zip.mjs ├── index.html ├── LICENSE.txt ├── vite.config.js ├── package.json └── README.md /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roncoo/roncoo-education-admin/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roncoo/roncoo-education-admin/HEAD/src/assets/images/logo.png -------------------------------------------------------------------------------- /distribution/images/gzh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roncoo/roncoo-education-admin/HEAD/distribution/images/gzh.png -------------------------------------------------------------------------------- /distribution/images/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roncoo/roncoo-education-admin/HEAD/distribution/images/logo.jpg -------------------------------------------------------------------------------- /distribution/images/web1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roncoo/roncoo-education-admin/HEAD/distribution/images/web1.png -------------------------------------------------------------------------------- /distribution/images/web2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roncoo/roncoo-education-admin/HEAD/distribution/images/web2.png -------------------------------------------------------------------------------- /distribution/images/web3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roncoo/roncoo-education-admin/HEAD/distribution/images/web3.png -------------------------------------------------------------------------------- /distribution/images/web4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roncoo/roncoo-education-admin/HEAD/distribution/images/web4.png -------------------------------------------------------------------------------- /src/assets/images/weixin.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roncoo/roncoo-education-admin/HEAD/src/assets/images/weixin.jpg -------------------------------------------------------------------------------- /src/assets/images/weixin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roncoo/roncoo-education-admin/HEAD/src/assets/images/weixin.png -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import { createPinia } from 'pinia' 2 | 3 | const store = createPinia() 4 | export default store 5 | -------------------------------------------------------------------------------- /distribution/images/admin1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roncoo/roncoo-education-admin/HEAD/distribution/images/admin1.png -------------------------------------------------------------------------------- /distribution/images/admin2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roncoo/roncoo-education-admin/HEAD/distribution/images/admin2.png -------------------------------------------------------------------------------- /distribution/images/admin3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roncoo/roncoo-education-admin/HEAD/distribution/images/admin3.png -------------------------------------------------------------------------------- /distribution/images/admin4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roncoo/roncoo-education-admin/HEAD/distribution/images/admin4.png -------------------------------------------------------------------------------- /src/utils/bus.js: -------------------------------------------------------------------------------- 1 | import mitt from 'mitt' 2 | 3 | /** 4 | * 创建一个事务总线 5 | */ 6 | const bus = mitt() 7 | export default bus 8 | -------------------------------------------------------------------------------- /src/assets/images/common/403.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roncoo/roncoo-education-admin/HEAD/src/assets/images/common/403.png -------------------------------------------------------------------------------- /src/assets/images/common/404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roncoo/roncoo-education-admin/HEAD/src/assets/images/common/404.png -------------------------------------------------------------------------------- /src/assets/images/common/beian.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roncoo/roncoo-education-admin/HEAD/src/assets/images/common/beian.png -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "ESNext", 5 | "baseUrl": ".", 6 | "paths": { 7 | "@/*": ["src/*"] 8 | } 9 | }, 10 | "include": ["src/**/*.vue", "src/**/*"] 11 | } 12 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/prettierrc", 3 | "semi": false, 4 | "tabWidth": 2, 5 | "singleQuote": true, 6 | "printWidth": 200, 7 | "trailingComma": "none", 8 | "vueIndentScriptAndStyle": true 9 | } 10 | -------------------------------------------------------------------------------- /src/utils/constants/system.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 通用常量 3 | */ 4 | export const PATH = { 5 | URL_LOGIN: '/login', 6 | URL_404: '/404', 7 | URL_403: '/403', 8 | URL_GATEWAY: '/gateway', 9 | URL_DASHBOARD: '/dashboard' 10 | } 11 | export const TOKEN_KEY = 'EDU_OS_ADMIN_TOKEN' 12 | -------------------------------------------------------------------------------- /src/utils/permission.js: -------------------------------------------------------------------------------- 1 | import { useUserStore } from '@/store/modules/user.js' 2 | 3 | export function hasPermission(keyValue) { 4 | const permissions = useUserStore().getPermissionList 5 | if (!permissions?.includes(keyValue)) { 6 | return false 7 | } 8 | return true 9 | } 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | npm-debug.log* 4 | yarn-debug.log* 5 | 6 | .DS_Store 7 | .idea 8 | .vscode 9 | *.suo 10 | *.ntvs* 11 | *.njsproj 12 | *.sln 13 | 14 | *.classpath 15 | *.project 16 | *.settings 17 | *.mvn 18 | *.springBeans 19 | *.factorypath 20 | 21 | package-lock.json 22 | pnpm-lock.yaml -------------------------------------------------------------------------------- /src/api/dashboard.js: -------------------------------------------------------------------------------- 1 | import { getRequest } from '@/utils/request' 2 | 3 | export const statApi = { 4 | // 数据统计 5 | data: () => { 6 | return getRequest('/user/admin/stat/data') 7 | }, 8 | // 用户注册登录统计 9 | login: () => { 10 | return getRequest('/user/admin/stat/login?dates=-14') 11 | }, 12 | // 点播用量统计 13 | vod: () => { 14 | return getRequest('/system/admin/stat/vod') 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 14 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js' 2 | import globals from 'globals' 3 | import pluginVue from 'eslint-plugin-vue' 4 | import { defineConfig } from 'eslint/config' 5 | 6 | export default defineConfig([ 7 | { files: ['**/*.{js,mjs,cjs,vue}'], plugins: { js }, extends: ['js/recommended'] }, 8 | { files: ['**/*.{js,mjs,cjs,vue}'], languageOptions: { globals: globals.browser } }, 9 | pluginVue.configs['flat/essential'] 10 | ]) 11 | -------------------------------------------------------------------------------- /zip.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * 打包工具,方便将打包后的dist进行管理,打包后的文件为:admin.zip 3 | */ 4 | import { createWriteStream } from 'fs' 5 | import path from 'path' 6 | import archiver from 'archiver' 7 | 8 | const output = createWriteStream(path.resolve('./admin.zip')) 9 | const archive = archiver('zip', { zlib: { level: 9 } }) 10 | 11 | archive.pipe(output) 12 | archive.directory(path.resolve('dist'), '', null) 13 | archive.finalize().then(() => { 14 | console.log('打包成功,请查看根目录下的:admin.zip') 15 | }) 16 | -------------------------------------------------------------------------------- /src/api/login.js: -------------------------------------------------------------------------------- 1 | import { getRequest, postRequest } from '@/utils/request' 2 | 3 | export const loginApi = { 4 | getWebsite: () => { 5 | return getRequest('/system/api/sys/config/website') 6 | }, 7 | getCodeImg: () => { 8 | return getRequest('/system/api/common/code') 9 | }, 10 | login: (data) => { 11 | return postRequest('/system/admin/login/password', data) 12 | }, 13 | getUserInfo: () => { 14 | return getRequest('/system/admin/sys/user/current') 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/views/404.vue: -------------------------------------------------------------------------------- 1 | 8 | 18 | -------------------------------------------------------------------------------- /src/store/modules/website.js: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia' 2 | import { loginApi } from '@/api/login.js' 3 | 4 | export const useWebsiteStore = defineStore({ 5 | id: 'website', 6 | state: () => ({ 7 | info: {} 8 | }), 9 | getters: { 10 | getInfo(state) { 11 | return state.info 12 | } 13 | }, 14 | actions: { 15 | // 登录操作 16 | init() { 17 | loginApi.getWebsite().then((res) => { 18 | this.info = res 19 | }) 20 | } 21 | } 22 | }) 23 | -------------------------------------------------------------------------------- /src/views/403.vue: -------------------------------------------------------------------------------- 1 | 8 | 18 | -------------------------------------------------------------------------------- /src/utils/cookie.js: -------------------------------------------------------------------------------- 1 | import Cookies from 'js-cookie' 2 | import { TOKEN_KEY } from '@/utils/constants/system' 3 | 4 | /** 5 | * 单位:天 6 | */ 7 | const TokenExpiresTime = 1 8 | 9 | export function setToken(token) { 10 | return Cookies.set(TOKEN_KEY, token, { 11 | expires: TokenExpiresTime 12 | }) 13 | } 14 | 15 | export function getToken() { 16 | return Cookies.get(TOKEN_KEY) 17 | } 18 | 19 | export function removeToken() { 20 | return Cookies.remove(TOKEN_KEY, { 21 | expires: 0 22 | }) 23 | } 24 | -------------------------------------------------------------------------------- /src/assets/images/check.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/layout/components/Navbar/Logo.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 25 | -------------------------------------------------------------------------------- /src/layout/components/Navbar/index.vue: -------------------------------------------------------------------------------- 1 | 8 | 13 | 14 | 24 | -------------------------------------------------------------------------------- /src/layout/components/Mains.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 13 | 27 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 运营管理后台 9 | 10 | 11 | 12 | 15 |
16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/components/Enum/View/index.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 28 | -------------------------------------------------------------------------------- /src/api/upload.js: -------------------------------------------------------------------------------- 1 | import { getRequest, postRequest, upload } from '@/utils/request' 2 | 3 | export const uploadApi = { 4 | pic: (file) => { 5 | return upload('/system/admin/upload/pic?name=' + file.name, file, 'picFile') 6 | }, 7 | doc: (file, cb, cancelFun) => { 8 | return upload('/system/admin/upload/doc?name=' + file.name, file, 'docFile', cb, cancelFun) 9 | }, 10 | app: (file, cb, cancelFun) => { 11 | return upload('/system/admin/upload/app?name=' + file.name, file, 'appFile', cb, cancelFun) 12 | }, 13 | getVodConfig: () => { 14 | return getRequest('/course/admin/resource/vod/config?t' + Date.now()) 15 | }, 16 | saveResource: (data) => { 17 | return postRequest('/course/admin/resource/save?t=' + Date.now(), data) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/components/Cascader/Course/index.vue: -------------------------------------------------------------------------------- 1 | 4 | 28 | -------------------------------------------------------------------------------- /src/layout/index.vue: -------------------------------------------------------------------------------- 1 | 12 | 24 | 33 | -------------------------------------------------------------------------------- /src/store/modules/upload.js: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia' 2 | 3 | export const useUploadStore = defineStore({ 4 | id: 'upload', 5 | state: () => ({ 6 | fileList: [], 7 | successFileList: [] 8 | }), 9 | getters: { 10 | getFileList(state) { 11 | return state.fileList 12 | }, 13 | getSuccessFileList(state) { 14 | return state.successFileList 15 | } 16 | }, 17 | actions: { 18 | // 添加文件 19 | addFile(data) { 20 | this.fileList.push(data) 21 | }, 22 | addSuccessFile(data) { 23 | this.successFileList.push(data) 24 | }, 25 | // 清空文件 26 | clearFile() { 27 | this.fileList.splice(0, this.fileList.length) 28 | }, 29 | clearSuccessFile() { 30 | this.successFileList.splice(0, this.successFileList.length) 31 | } 32 | } 33 | }) 34 | -------------------------------------------------------------------------------- /src/components/Icon/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 29 | 30 | 40 | -------------------------------------------------------------------------------- /src/components/Editor/index.js: -------------------------------------------------------------------------------- 1 | import { Boot } from '@wangeditor/editor' 2 | import ImageMenu from './Custom/image.js' 3 | 4 | const MenusList = [ 5 | { 6 | index: 20, 7 | key: 'ImageMenu', 8 | class: ImageMenu 9 | } 10 | ] 11 | 12 | const registerMenu = function (editor, toolbarConfig) { 13 | const allRegisterMenu = editor.getAllMenuKeys() // 获取所有已注册的菜单 14 | let keys = [] 15 | for (let item of MenusList) { 16 | if (allRegisterMenu.indexOf(item.key) < 0) { 17 | // 如果未注册,则注册 18 | const menuObj = { 19 | key: item.key, 20 | factory() { 21 | return new item.class() 22 | } 23 | } 24 | Boot.registerMenu(menuObj) 25 | } 26 | keys.push(item.key) 27 | } 28 | toolbarConfig.insertKeys = { 29 | index: MenusList[0].index, 30 | keys: keys 31 | } 32 | } 33 | 34 | export default registerMenu 35 | -------------------------------------------------------------------------------- /src/components/Enum/Radio/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 30 | -------------------------------------------------------------------------------- /src/layout/components/Navbar/Breadcrumb.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 23 | 24 | 33 | -------------------------------------------------------------------------------- /src/views/system/config/View.vue: -------------------------------------------------------------------------------- 1 | 13 | 35 | -------------------------------------------------------------------------------- /src/components/Enum/Select/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 34 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright © 2016 LingKe, Co., Ltd. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import { resolve } from 'path' 3 | import vue from '@vitejs/plugin-vue' 4 | import { createSvgIconsPlugin } from 'vite-plugin-svg-icons' 5 | 6 | export default defineConfig({ 7 | base: './', 8 | telemetry: false, 9 | server: { 10 | port: 9528, // 服务启动端口号 11 | open: true, // 服务启动时是否自动打开浏览器 12 | proxy: { 13 | '/gateway': { 14 | target: 'http://localhost:7700', 15 | changeOrigin: true, 16 | rewrite: (path) => path.replace(/^\/gateway/, '') 17 | } 18 | } 19 | }, 20 | resolve: { 21 | alias: { 22 | '@': resolve(__dirname, '.', 'src') 23 | } 24 | }, 25 | plugins: [ 26 | vue(), 27 | createSvgIconsPlugin({ 28 | iconDirs: [resolve(process.cwd(), 'src/assets/svg')], 29 | symbolId: 'icon-[name]' 30 | }) 31 | ], 32 | build: { 33 | minify: 'terser', 34 | emptyOutDir: true, 35 | chunkSizeWarningLimit: 1500, 36 | terserOptions: { 37 | compress: { 38 | drop_console: true, 39 | drop_debugger: true 40 | } 41 | } 42 | } 43 | }) 44 | -------------------------------------------------------------------------------- /src/assets/images/folder.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /distribution/assembly.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | ${project.version} 19 | true 20 | 21 | 22 | 23 | tar.gz 24 | 25 | 26 | 27 | ${project.basedir}/../dist 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/assets/svg/课程.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/views/system/log/LogView.vue: -------------------------------------------------------------------------------- 1 | 12 | 42 | -------------------------------------------------------------------------------- /src/assets/styles/index.scss: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | word-break: break-all; 4 | } 5 | 6 | a { 7 | text-decoration: none; 8 | color: inherit; 9 | } 10 | 11 | .c-danger { 12 | color: gray; 13 | } 14 | 15 | .c-special { 16 | color: red; 17 | font-weight: bold; 18 | } 19 | 20 | .search_bar { 21 | text-align: left; 22 | } 23 | 24 | .button_bar { 25 | text-align: left; 26 | margin-bottom: 10px; 27 | } 28 | 29 | // 编辑器全屏 30 | .w-e-full-screen-container { 31 | z-index: 9999; 32 | } 33 | 34 | // 文字提示 35 | .tip-info { 36 | margin-left: 5px; 37 | font-size: 12px; 38 | } 39 | 40 | // 弹窗 41 | .el-dialog__body { 42 | max-height: 80vh; 43 | overflow: auto; 44 | padding: 0 20px; 45 | } 46 | 47 | // 分页组件 48 | .el-pagination { 49 | margin-top: 20px; 50 | justify-content: center; 51 | } 52 | 53 | // 表头样式 54 | .el-table .el-table__header th.is-leaf { 55 | background-color: #f9f9f9; 56 | height: 50px; 57 | } 58 | 59 | .el-tree-node__content { 60 | height: 40px; 61 | } 62 | 63 | .el-divider--vertical { 64 | margin: 0; 65 | } 66 | 67 | // 滚动条样式 68 | ::-webkit-scrollbar { 69 | width: 5px; 70 | height: 5px; 71 | border-radius: 5px; 72 | } 73 | 74 | ::-webkit-scrollbar-thumb { 75 | border-radius: 5px; 76 | background: rgba(0, 0, 0, 0.2); 77 | } 78 | 79 | ::-webkit-scrollbar-track { 80 | border-radius: 0; 81 | background: #fff; 82 | } 83 | -------------------------------------------------------------------------------- /src/components/Editor/Custom/image.js: -------------------------------------------------------------------------------- 1 | export default class ImageMenu { 2 | constructor() { 3 | this.title = '选择图片' // 自定义菜单标题 4 | this.iconSvg = 5 | '' // 可选 6 | this.tag = 'button' 7 | } 8 | 9 | // 获取菜单执行时的 value ,用不到则返回空 字符串或 false 10 | getValue(editor) { 11 | return '' 12 | } 13 | 14 | // 菜单是否需要激活(如选中加粗文本,“加粗”菜单会激活),用不到则返回 false 15 | isActive(editor) { 16 | return false 17 | } 18 | 19 | // 菜单是否需要禁用(如选中 H1 ,“引用”菜单被禁用),用不到则返回 false 20 | isDisabled(editor) { 21 | return false 22 | } 23 | 24 | // 点击菜单时触发的函数 25 | exec(editor, value) { 26 | if (this.isDisabled(editor)) return 27 | editor.emit('PicMenuClick', value) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/utils/storage.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 存储localStorage 3 | */ 4 | export function setStore(name, content) { 5 | if (!name) return 6 | if (typeof content !== 'string') { 7 | content = JSON.stringify(content) 8 | } 9 | window.localStorage.setItem(name, content) 10 | } 11 | 12 | /** 13 | * 获取localStorage 14 | */ 15 | export function getStore(name) { 16 | if (!name) return 17 | return window.localStorage.getItem(name) 18 | } 19 | 20 | /** 21 | * 22 | * @param name 23 | * @param content 24 | * @param expireTime 单位:分 25 | */ 26 | export function setSessionStorage(name, content, expireTime) { 27 | if (!name) return 28 | let params = JSON.stringify(content) 29 | if (expireTime) { 30 | expireTime = Date.now() + expireTime * 1000 * 60 31 | params = JSON.stringify({ content: content, expireTime: expireTime }) 32 | } 33 | window.sessionStorage.setItem(name, params) 34 | } 35 | 36 | /** 37 | * 获取sessionStorage 38 | */ 39 | export function getSessionStorage(name) { 40 | if (!name) return null 41 | let data = window.sessionStorage.getItem(name) 42 | if (!data) return null 43 | data = JSON.parse(data) 44 | if (data) { 45 | if (data.expireTime && data.expireTime > 0) { 46 | if (data.expireTime > Date.now()) { 47 | return data.content 48 | } 49 | window.sessionStorage.removeItem(name) 50 | return null 51 | } 52 | return data 53 | } 54 | return null 55 | } 56 | -------------------------------------------------------------------------------- /src/views/dashboard/StatLogin.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 54 | 64 | -------------------------------------------------------------------------------- /src/components/Selector/Svg/index.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 34 | 35 | 53 | -------------------------------------------------------------------------------- /src/assets/svg/装修.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/views/system/config/index.vue: -------------------------------------------------------------------------------- 1 | 10 | 58 | 64 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "roncoo-education-admin", 3 | "version": "25.0.0-RELEASE", 4 | "license": "MIT", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "serve": "vite preview", 10 | "lint": "eslint src --fix --ext .ts,.tsx,.vue,.js,.jsx", 11 | "prettier": "prettier --write src", 12 | "zip": "node zip.mjs" 13 | }, 14 | "engines": { 15 | "node": ">=20.0.0" 16 | }, 17 | "dependencies": { 18 | "@element-plus/icons-vue": "2.3.1", 19 | "@polyv/vod-upload-js-sdk": "1.6.0", 20 | "@wangeditor/editor": "5.1.23", 21 | "@wangeditor/editor-for-vue": "5.1.12", 22 | "archiver": "7.0.1", 23 | "axios": "1.11.0", 24 | "echarts": "5.5.0", 25 | "element-plus": "2.9.9", 26 | "js-cookie": "3.0.5", 27 | "jsencrypt": "3.2.1", 28 | "lodash": "4.17.21", 29 | "mitt": "3.0.1", 30 | "pinia": "2.1.7", 31 | "simple-uploader.js": "0.6.0", 32 | "sortablejs": "1.15.2", 33 | "vue": "3.5.20", 34 | "vue-json-pretty": "2.5.0", 35 | "vue-router": "4.5.1", 36 | "vuedraggable": "4.1.0" 37 | }, 38 | "devDependencies": { 39 | "@eslint/js": "9.34.0", 40 | "@vitejs/plugin-vue": "5.0.4", 41 | "eslint": "9.26.0", 42 | "eslint-config-prettier": "10.1.2", 43 | "eslint-plugin-prettier": "5.4.0", 44 | "eslint-plugin-vue": "10.1.0", 45 | "fast-glob": "3.3.3", 46 | "globals": "16.3.0", 47 | "prettier": "3.2.5", 48 | "sass": "1.75.0", 49 | "terser": "5.31.0", 50 | "vite": "6.3.4", 51 | "vite-plugin-svg-icons": "2.0.1" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/components/Upload/App/index.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 48 | 49 | 54 | -------------------------------------------------------------------------------- /src/views/course/record/Collect.vue: -------------------------------------------------------------------------------- 1 | 24 | 39 | -------------------------------------------------------------------------------- /distribution/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.roncoo 8 | distribution 9 | 25.0.0-RELEASE 10 | pom 11 | distribution 12 | 13 | 14 | 15 | release-education 16 | 17 | true 18 | 19 | 20 | 21 | 22 | org.apache.maven.plugins 23 | maven-assembly-plugin 24 | 3.0.0 25 | 26 | false 27 | 28 | assembly.xml 29 | 30 | 31 | 32 | 33 | make-assembly 34 | package 35 | 36 | single 37 | 38 | 39 | 40 | 41 | 42 | admin 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /src/views/dashboard/index.vue: -------------------------------------------------------------------------------- 1 | 31 | 37 | 54 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import ElementPlus from 'element-plus' 3 | import 'element-plus/dist/index.css' 4 | import '@/assets/styles/index.scss' 5 | import App from '@/App.vue' 6 | import router, { createNewRouter } from '@/router/index.js' 7 | import store from '@/store/index.js' 8 | import { getToken } from '@/utils/cookie.js' 9 | import { loginApi } from '@/api/login.js' 10 | import { useUserStore } from '@/store/modules/user.js' 11 | import * as ElementPlusIconsVue from '@element-plus/icons-vue' 12 | import 'virtual:svg-icons-register' 13 | import Icon from '@/components/Icon/index.vue' 14 | import { hasPermission } from '@/utils/permission.js' 15 | 16 | // 初始化 17 | setupInit() 18 | 19 | async function setupInit() { 20 | if (!getToken()) { 21 | // 用户没登录 22 | init() 23 | } else { 24 | // 用户已登录 25 | const res = await loginApi.getUserInfo() 26 | // 创建动态路由 27 | createNewRouter(res.routerList) 28 | init() 29 | useUserStore().login(res) 30 | } 31 | } 32 | 33 | function init() { 34 | const app = createApp(App) 35 | app.use(router) 36 | app.use(store) 37 | app.use(ElementPlus) 38 | 39 | // 注册图标组件 40 | for (const [key, component] of Object.entries(ElementPlusIconsVue)) { 41 | app.component(key, component) 42 | } 43 | // 注册图标组件 44 | app.component('Icon', Icon) 45 | 46 | // 注册自定义指令(权限使用) 47 | app.directive('permission', (el, binding) => { 48 | if (!hasPermission(binding.value)) { 49 | el.parentNode && el.parentNode.removeChild(el) 50 | } 51 | }) 52 | 53 | // 全局配置:Dialog遮罩层点击不关闭 54 | app._context.components.ElDialog.props.closeOnClickModal.default = false 55 | 56 | // 挂载 57 | app.mount('#app') 58 | } 59 | -------------------------------------------------------------------------------- /src/views/users/record/Study.vue: -------------------------------------------------------------------------------- 1 | 23 | 44 | -------------------------------------------------------------------------------- /src/components/Pagination/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 15 | 65 | -------------------------------------------------------------------------------- /src/views/course/record/Study.vue: -------------------------------------------------------------------------------- 1 | 23 | 44 | -------------------------------------------------------------------------------- /src/utils/vod/simple.js: -------------------------------------------------------------------------------- 1 | // 分片上传 2 | import SimpleUploader from 'simple-uploader.js' 3 | 4 | export const getSimpleClient = (userData) => { 5 | const simpleClient = new SimpleUploader({ 6 | target: userData.uploadUrl, 7 | chunkSize: '2048000', // 分片大小,单位bytes,这里为2M 8 | simultaneousUploads: 5, // 并发上传数,默认为3 9 | maxChunkRetries: 3, // 上传失败重试次数,默认为0 10 | progressCallbacksInterval: 1000, // 上传进度回调间隔,单位ms,默认为500ms 11 | testChunks: true, // 是否要测试分片是否完整,默认为true 12 | checkChunkUploadedByResponse: function (chunk, message) { 13 | try { 14 | const objMessage = JSON.parse(message) 15 | if (objMessage.code === 200 && objMessage.data?.uploadStatusId === 2) { 16 | return true 17 | } else { 18 | return false 19 | } 20 | } catch (error) { 21 | console.error(error) 22 | return false 23 | } 24 | }, 25 | query: { 26 | isChunk: true, 27 | ...userData 28 | } 29 | }) 30 | return simpleClient 31 | } 32 | 33 | export const uploadSimple = (simpleClient, startFile, callback) => { 34 | simpleClient.on('fileAdded', function (file) { 35 | startFile.status = 'uploading' 36 | callback('FileStarted', file) 37 | }) 38 | simpleClient.on('fileProgress', function (rootFile, file, chunk) { 39 | const uploadInfo = { 40 | progress: file.progress() 41 | } 42 | callback('FileProgress', uploadInfo) 43 | }) 44 | simpleClient.on('fileSuccess', function (rootFile, file, message, chunk) { 45 | const uploadInfo = { 46 | vid: JSON.parse(message).data.videoVid 47 | } 48 | callback('FileSucceed', uploadInfo) 49 | }) 50 | simpleClient.on('fileError', function (rootFile, file, message) { 51 | callback('FileFailed', message) 52 | }) 53 | 54 | simpleClient.addFile(startFile.file) 55 | simpleClient.upload() 56 | } 57 | -------------------------------------------------------------------------------- /src/views/course/record/Comment.vue: -------------------------------------------------------------------------------- 1 | 29 | 45 | -------------------------------------------------------------------------------- /src/utils/vod/polyv.js: -------------------------------------------------------------------------------- 1 | import PlvVideoUpload from '@polyv/vod-upload-js-sdk' 2 | 3 | export const getPolyvClient = (userData) => { 4 | //初始化上传实例 5 | const polyvClient = new PlvVideoUpload({ 6 | region: 'auto', 7 | events: { 8 | Error: (err) => { 9 | // 错误事件回调 10 | console.error(err) 11 | }, 12 | UploadComplete: () => { 13 | // 全部上传任务完成回调 14 | } 15 | } 16 | }) 17 | // 设置账号授权验证信息 18 | polyvClient.updateUserData({ 19 | userid: userData.userid, 20 | ptime: userData.ptime, 21 | hash: userData.hash, 22 | sign: userData.sign, 23 | cataid: userData.categoryId 24 | }) 25 | return polyvClient 26 | } 27 | 28 | export const uploadPolyv = (polyvClient, startFile, callback) => { 29 | // 添加文件到文件 30 | polyvClient.addFile( 31 | startFile.file, 32 | { 33 | FileStarted: function (uploadInfo) { 34 | // 文件开始上传回调 35 | startFile.status = 'uploading' 36 | callback('FileStarted', uploadInfo) 37 | }, 38 | FileProgress: function (uploadInfo) { 39 | // 文件上传过程返回上传进度信息回调 40 | callback('FileProgress', uploadInfo) 41 | }, 42 | FileStopped: function (uploadInfo) { 43 | // 文件暂停上传回调 44 | callback('FileStopped', uploadInfo) 45 | }, 46 | FileSucceed: function (uploadInfo) { 47 | // 文件上传成功回调 48 | callback('FileSucceed', uploadInfo) 49 | }, 50 | FileFailed: function (uploadInfo) { 51 | // 文件上传失败回调 52 | callback('FileFailed', uploadInfo) 53 | } 54 | }, 55 | { 56 | // 文件上传相关信息设置 57 | //title: file.name, // 标题 58 | //desc: , // 描述 59 | //cataid: startFile.categoryId, // 上传分类目录ID 60 | //tag: , // 标签 61 | luping: 1, // 是否开启视频课件优化处理,对于上传录屏类视频清晰度有所优化:0为不开启,1为开启 62 | keepsource: 0 // 是否源文件播放(不对视频进行编码):0为编码,1为不编码 63 | //state: //用户自定义信息,如果提交了该字段,会在服务端上传完成回调时透传返回。 64 | } 65 | ) 66 | polyvClient.startAll() 67 | } 68 | -------------------------------------------------------------------------------- /src/assets/svg/用户.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | -------------------------------------------------------------------------------- /src/store/modules/user.js: -------------------------------------------------------------------------------- 1 | import { cloneDeep, isEmpty } from 'lodash' 2 | import { defineStore } from 'pinia' 3 | import { removeToken } from '@/utils/cookie' 4 | 5 | export const useUserStore = defineStore({ 6 | id: 'user', 7 | state: () => ({ 8 | mobile: '', 9 | realName: '', 10 | routerList: [], 11 | menuList: [], 12 | permissionList: [], 13 | breadcrumbMap: new Map() 14 | }), 15 | getters: { 16 | getMobile(state) { 17 | return state.mobile 18 | }, 19 | getRealName(state) { 20 | return state.realName 21 | }, 22 | getRouterList(state) { 23 | return state.routerList 24 | }, 25 | getMenuList(state) { 26 | return state.menuList 27 | }, 28 | getPermissionList(state) { 29 | return state.permissionList 30 | }, 31 | getBreadcrumbMap(state) { 32 | return state.breadcrumbMap 33 | } 34 | }, 35 | actions: { 36 | // 登录操作 37 | login(data) { 38 | this.mobile = data.mobile 39 | this.realName = data.realName 40 | this.routerList = data.routerList 41 | this.menuList = data.menuList 42 | this.permissionList = data.permissionList 43 | this.breadcrumbMap = buildBreadcrumbMap(data.menuList) 44 | }, 45 | 46 | // logout 47 | logout() { 48 | this.routerList = [] 49 | this.menuList = [] 50 | this.breadcrumbMap = new Map() 51 | removeToken() 52 | } 53 | } 54 | }) 55 | 56 | /** 57 | * 构建菜单父级集合,面包屑导航 58 | */ 59 | function buildBreadcrumbMap(menuList) { 60 | let breadcrumbMaps = new Map() 61 | recursiveMap(menuList, [], breadcrumbMaps) 62 | return breadcrumbMaps 63 | } 64 | function recursiveMap(menuList, parentList, breadcrumbMaps) { 65 | for (const e of menuList) { 66 | if (e.parentId === 0) { 67 | parentList = [] 68 | } 69 | let menuIdStr = e.id.toString() 70 | let cloneParentList = cloneDeep(parentList) 71 | cloneParentList.push({ id: menuIdStr, title: e.menuName }) 72 | breadcrumbMaps.set(menuIdStr, cloneParentList) 73 | if (!isEmpty(e.children)) { 74 | // 递归 75 | recursiveMap(e.children, cloneParentList, breadcrumbMaps) 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/components/Preview/index.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 79 | 80 | -------------------------------------------------------------------------------- /src/layout/components/Navbar/User.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 53 | 80 | -------------------------------------------------------------------------------- /src/views/common/order/OrderForm.vue: -------------------------------------------------------------------------------- 1 | 16 | 73 | -------------------------------------------------------------------------------- /src/components/Selector/Lecturer/index.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /src/assets/svg/概况.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/views/dashboard/StatData.vue: -------------------------------------------------------------------------------- 1 | 51 | 52 | 63 | 70 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHashHistory } from 'vue-router' 2 | import Layout from '@/layout/index.vue' 3 | import { useUserStore } from '@/store/modules/user' 4 | import { getToken } from '@/utils/cookie' 5 | import { PATH } from '@/utils/constants/system' 6 | 7 | // 静态路由 8 | const constantRoutes = [ 9 | { 10 | path: '/', 11 | redirect: PATH.URL_DASHBOARD 12 | }, 13 | { 14 | path: PATH.URL_LOGIN, 15 | component: () => import('@/views/login/index.vue') 16 | }, 17 | { 18 | path: PATH.URL_404, 19 | component: () => import('@/views/404.vue'), 20 | meta: { title: '404' } 21 | }, 22 | { 23 | path: PATH.URL_403, 24 | component: () => import('@/views/403.vue'), 25 | meta: { title: '403' } 26 | } 27 | ] 28 | 29 | const createRouters = (routerList) => 30 | createRouter({ 31 | history: createWebHashHistory(), 32 | routes: routerList, 33 | strict: true 34 | }) 35 | 36 | const router = createRouters(constantRoutes) 37 | 38 | // 路由加载前 39 | router.beforeEach(async (to, from, next) => { 40 | const token = getToken() 41 | if (!token) { 42 | if (to.path === PATH.URL_LOGIN) { 43 | next() 44 | return 45 | } 46 | next({ path: PATH.URL_LOGIN }) 47 | return 48 | } 49 | if (to.matched.length === 0) { 50 | next({ path: PATH.URL_404 }) 51 | return 52 | } 53 | next() 54 | }) 55 | 56 | export default router 57 | 58 | // 创建路由 59 | export function createNewRouter(data) { 60 | data = data ? data : useUserStore().routerList || [] 61 | const menuList = data.filter((e) => e.path) 62 | const routerList = [] 63 | const modules = import.meta.glob('../views/**/**.vue') 64 | for (const e of menuList) { 65 | const route = { 66 | path: e.path, 67 | name: e.id.toString(), 68 | meta: { 69 | // id 70 | id: e.id.toString(), 71 | // 组件名称 72 | componentName: e.id.toString(), 73 | // 组件标题 74 | title: e.menuName, 75 | // 组件图标 76 | icon: e.menuIcon 77 | }, 78 | component: {} 79 | } 80 | route.component = modules[`../views${e.component}`] 81 | routerList.push(route) 82 | } 83 | 84 | router.addRoute({ 85 | path: '/', 86 | meta: {}, 87 | component: Layout, 88 | children: routerList 89 | }) 90 | } 91 | -------------------------------------------------------------------------------- /src/views/course/record/Course.vue: -------------------------------------------------------------------------------- 1 | 35 | 59 | -------------------------------------------------------------------------------- /src/components/Selector/Image/index.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 61 | 62 | 86 | -------------------------------------------------------------------------------- /src/views/system/log/index.vue: -------------------------------------------------------------------------------- 1 | 40 | 58 | -------------------------------------------------------------------------------- /src/views/course/chapter/ChapterForm.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 78 | -------------------------------------------------------------------------------- /src/views/users/record/Account.vue: -------------------------------------------------------------------------------- 1 | 38 | 60 | -------------------------------------------------------------------------------- /src/components/Editor/index.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 68 | 73 | -------------------------------------------------------------------------------- /src/views/users/record/Course.vue: -------------------------------------------------------------------------------- 1 | 39 | 60 | -------------------------------------------------------------------------------- /src/views/course/resource/ResourceForm.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 79 | -------------------------------------------------------------------------------- /src/api/users.js: -------------------------------------------------------------------------------- 1 | import { deleteRequest, getRequest, postRequest, putRequest } from '@/utils/request' 2 | 3 | export const usersApi = { 4 | // 用户账号金额消费 5 | usersAccountConsumeSave: (data) => { 6 | return postRequest('/user/admin/users/account/consume/save', data) 7 | }, 8 | // 用户账号金额消费记录 9 | usersAccountConsumePage: (data) => { 10 | return postRequest('/user/admin/users/account/consume/page', data) 11 | }, 12 | 13 | // 讲师分页 14 | lecturerPage: (params, pageCurrent = 1, pageSize = 20) => { 15 | return postRequest('/user/admin/lecturer/page', { pageCurrent, pageSize, ...params }) 16 | }, 17 | // 讲师查看 18 | lecturerView: (data) => { 19 | return getRequest('/user/admin/lecturer/view?id=' + data.id) 20 | }, 21 | // 讲师修改 22 | lecturerEdit: (data) => { 23 | return putRequest('/user/admin/lecturer/edit', data) 24 | }, 25 | // 讲师排序 26 | lecturerSort: (data) => { 27 | return putRequest('/user/admin/lecturer/sort', data) 28 | }, 29 | 30 | // 讲师保存 31 | lecturerSave: (data) => { 32 | return postRequest('/user/admin/lecturer/save', data) 33 | }, 34 | 35 | // 讲师删除 36 | lecturerDelete: (data) => { 37 | return deleteRequest('/user/admin/lecturer/delete?id=' + data.id) 38 | }, 39 | 40 | // 用户分页 41 | usersPage: (params, pageCurrent = 1, pageSize = 20) => { 42 | return postRequest('/user/admin/users/page', { pageCurrent, pageSize, ...params }) 43 | }, 44 | 45 | // 用户修改 46 | usersEdit: (data) => { 47 | return putRequest('/user/admin/users/edit', data) 48 | }, 49 | 50 | usersView(data) { 51 | return getRequest('/user/admin/users/view?id=' + data.id) 52 | }, 53 | // 用户删除 54 | usersDelete: (data) => { 55 | return deleteRequest('/user/admin/users/delete?id=' + data.id) 56 | }, 57 | 58 | // 登录日志 59 | logLoginPage: (params, pageCurrent = 1, pageSize = 20) => { 60 | return postRequest('/user/admin/users/log/page', { pageCurrent, pageSize, ...params }) 61 | }, 62 | 63 | // 用户课程分页 64 | userCoursePage: (params, pageCurrent = 1, pageSize = 20) => { 65 | return postRequest('/course/admin/user/course/page', { pageCurrent, pageSize, ...params }) 66 | }, 67 | 68 | // 订单 69 | orderInfoPage: (params, pageCurrent = 1, pageSize = 20) => { 70 | return postRequest('/user/admin/order/info/page', { pageCurrent, pageSize, ...params }) 71 | }, 72 | orderEdit: (data) => { 73 | return putRequest('/user/admin/order/info/edit', data) 74 | }, 75 | orderInfoStat: (data) => { 76 | return getRequest('/user/admin/order/info/stat?userId=' + data.userId) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/views/system/role/RoleForm.vue: -------------------------------------------------------------------------------- 1 | 19 | 79 | -------------------------------------------------------------------------------- /src/views/users/record/ConsumeForm.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 76 | -------------------------------------------------------------------------------- /src/components/Cascader/Category/CategoryForm.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 83 | -------------------------------------------------------------------------------- /src/views/course/category/CategoryForm.vue: -------------------------------------------------------------------------------- 1 | 19 | 82 | -------------------------------------------------------------------------------- /src/views/users/log/index.vue: -------------------------------------------------------------------------------- 1 | 47 | 59 | -------------------------------------------------------------------------------- /src/views/system/role/MenuSet.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 83 | -------------------------------------------------------------------------------- /src/views/course/record/index.vue: -------------------------------------------------------------------------------- 1 | 31 | 62 | 82 | -------------------------------------------------------------------------------- /src/utils/base.js: -------------------------------------------------------------------------------- 1 | import { systemApi } from '@/api/system.js' 2 | import { getSessionStorage, setSessionStorage } from '@/utils/storage.js' 3 | import _ from 'lodash' 4 | import { JSEncrypt } from 'jsencrypt' 5 | import { useWebsiteStore } from '@/store/modules/website.js' 6 | 7 | /** 8 | * 格式化时长 9 | * @param time 10 | */ 11 | export function formatTime(time) { 12 | let a = ~~(time / 3600) 13 | let b = ~~(time / 60) - a * 60 14 | let c = time % 60 15 | a = String(a).padStart(2, '0') 16 | b = String(b).padStart(2, '0') 17 | c = String(c).padStart(2, '0') 18 | if (a === '00') { 19 | return `${b}:${c}` 20 | } else { 21 | return `${a}:${b}:${c}` 22 | } 23 | } 24 | 25 | /** 26 | * 格式化时长 27 | * @param time 28 | */ 29 | export function formatTimeTotal(time) { 30 | let a = ~~(time / 3600) 31 | let b = ~~(time / 60) - a * 60 32 | let c = time % 60 33 | a = String(a).padStart(2, '0') 34 | b = String(b).padStart(2, '0') 35 | c = String(c).padStart(2, '0') 36 | if (a === '00') { 37 | return `${b}分${c}秒` 38 | } else { 39 | return `${a}时${b}分${c}秒` 40 | } 41 | } 42 | 43 | // 文件大小转换 44 | export function transformSize(bytes) { 45 | // 文件大小转换 46 | const bt = parseInt(bytes) 47 | let result 48 | if (bt === 0) { 49 | result = '0B' 50 | } else { 51 | const k = 1024 52 | const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] 53 | const i = Math.floor(Math.log(bt) / Math.log(k)) 54 | if (typeof i !== 'number') { 55 | result = '-' 56 | } else { 57 | result = (bt / Math.pow(k, i)).toFixed(2) + sizes[i] 58 | } 59 | } 60 | return result 61 | } 62 | 63 | export function cloneDeep(obj) { 64 | //return JSON.parse(JSON.stringify(obj)) 65 | return _.cloneDeep(obj) 66 | } 67 | 68 | /** 69 | * 获取枚举 70 | */ 71 | export async function getEnumList(enumName) { 72 | const enumAttr = getSessionStorage(enumName) 73 | if (enumAttr) { 74 | return enumAttr 75 | } 76 | const res = await systemApi.getEnum({ enumName }) 77 | setSessionStorage(enumName, res) 78 | return getSessionStorage(enumName) 79 | } 80 | 81 | export async function getEnumObj(enumName) { 82 | let res = await getEnumList(enumName) 83 | return toObj(res) 84 | } 85 | 86 | function toObj(attr) { 87 | const obj = {} 88 | if (attr) { 89 | for (let i = 0; i < attr.length; i++) { 90 | obj[attr[i].code] = attr[i].desc 91 | } 92 | } 93 | return obj 94 | } 95 | 96 | export function encrypt(password, publicKey) { 97 | if (!publicKey) { 98 | publicKey = useWebsiteStore().getInfo.rsaLoginPublicKey 99 | } 100 | const crypt = new JSEncrypt() 101 | crypt.setPublicKey(publicKey) 102 | return crypt.encrypt(password) 103 | } 104 | -------------------------------------------------------------------------------- /src/views/course/chapter/PeriodForm.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 85 | -------------------------------------------------------------------------------- /src/views/users/list/UsersForm.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 83 | -------------------------------------------------------------------------------- /src/views/course/resource/MoveModel.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 91 | 97 | -------------------------------------------------------------------------------- /src/views/common/navigation/NavigationForm.vue: -------------------------------------------------------------------------------- 1 | 22 | 85 | -------------------------------------------------------------------------------- /src/views/common/zone/ZoneForm.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 86 | -------------------------------------------------------------------------------- /src/views/common/link/LinkForm.vue: -------------------------------------------------------------------------------- 1 | 22 | 87 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 领课教育系统-开源版 3 |
4 | 5 | spring-boot 6 | 7 | 8 | spring-cloud-alibaba 9 | 10 | 11 | vue 12 | 13 | 14 | element-plus 15 | 16 |
17 |
18 | 19 | ### 使用须知 20 | 21 | 1. 允许用于个人学习、毕业设计、教学案例、公益事业等。 22 | 2. 限制商用,若需要商业使用请咨询作者:18302045627(微信可加)。 23 | 3. 禁止将本项目的相关代码和相关资料进行任何形式任何名义的出售。 24 | 25 | ### 项目介绍 26 | 27 | 领课教育系统(roncoo-education)是基于领课网络多年的在线教育平台开发和运营经验打造出来的产品,致力于打造一个各行业都适用的分布式在线教育系统。系统采用前后端分离模式,前台采用vue.js为核心框架,后台采用Spring 28 | Cloud为核心框架。系统目前主要功能有课程点播功能,支持多家视频云的接入,课程附件管理功能,支持多家存储云的接入,可以帮助个人或者企业快速搭建一个轻量级的在线教育平台。 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 |
48 | 49 | ### 演示地址 50 | 51 | * 门户系统:[https://eduos.roncoos.com/](https://eduos.roncoos.com/) 52 | * 管理系统:[https://eduos.roncoos.com/admin/](https://eduos.roncoos.com/admin/) 53 | * 前端技术体系:Vue3 + Nuxt3 + Vite6 + Vue-Router + Element-Plus + Pinia + Axios 54 | * 后端技术体系:Spring Cloud Alibaba2023 + MySQL8 + Nacos + Seata + Mybatis + Druid 55 | 56 | ### 源码地址 57 | 58 | * 后端系统:roncoo-education(核心框架:Spring Cloud Alibaba):[码云](https://gitee.com/roncoocom/roncoo-education) | [Github](https://github.com/roncoo/roncoo-education) | [Gitcode](https://gitcode.com/roncoocom/roncoo-education) 59 | * 门户系统:roncoo-education-web(核心框架:Nuxt3):[码云](https://gitee.com/roncoocom/roncoo-education-web) | [Github](https://github.com/roncoo/roncoo-education-web) | [Gitcode](https://gitcode.com/roncoocom/roncoo-education-web) 60 | * 管理系统:roncoo-education-admin(核心框架:Vue3):[码云](https://gitee.com/roncoocom/roncoo-education-admin) | [Github](https://github.com/roncoo/roncoo-education-admin) | [Gitcode](https://gitcode.com/roncoocom/roncoo-education-admin) 61 | 62 | --- 63 |
关注微信公众号可获取更多学习资料(SQL脚本、部署教程、常见问题等)
64 | 领课开源-微信公众号 65 | -------------------------------------------------------------------------------- /src/assets/svg/系统.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | -------------------------------------------------------------------------------- /src/components/Upload/File/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 92 | 93 | 98 | -------------------------------------------------------------------------------- /src/views/system/user/Password.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 94 | -------------------------------------------------------------------------------- /src/components/Selector/Course/index.vue: -------------------------------------------------------------------------------- 1 | 40 | 41 | 73 | 74 | 79 | -------------------------------------------------------------------------------- /src/views/common/carousel/CarouselForm.vue: -------------------------------------------------------------------------------- 1 | 22 | 88 | -------------------------------------------------------------------------------- /src/views/system/user/RoleSet.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /src/views/system/user/UserForm.vue: -------------------------------------------------------------------------------- 1 | 25 | 90 | -------------------------------------------------------------------------------- /src/views/course/category/index.vue: -------------------------------------------------------------------------------- 1 | 56 | 76 | -------------------------------------------------------------------------------- /src/utils/table.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 表单封装 3 | */ 4 | import { onMounted, reactive } from 'vue' 5 | import { ElMessage, ElMessageBox } from 'element-plus' 6 | import Sortable from 'sortablejs' 7 | 8 | export default function useTable(apis, paras = {}) { 9 | // 分页对象 10 | const page = reactive({ 11 | pageCurrent: 1, 12 | pageSize: 20, 13 | totalCount: 0, 14 | list: [], 15 | loading: true 16 | }) 17 | 18 | // 分页查询 19 | const handlePage = async () => { 20 | if (apis.page) { 21 | page.loading = true 22 | try { 23 | const res = await apis.page({ 24 | pageCurrent: page.pageCurrent, 25 | pageSize: page.pageSize, 26 | ...paras, 27 | ...query 28 | }) 29 | if (res) { 30 | page.list = res.list || res || [] 31 | page.totalCount = res.totalCount || 0 32 | 33 | if (apis.sort) { 34 | await handleSort() 35 | } 36 | } 37 | } finally { 38 | page.loading = false 39 | } 40 | } 41 | } 42 | 43 | // 查询对象 44 | const query = reactive({}) 45 | 46 | // 查询 47 | const handleQuery = () => { 48 | page.pageCurrent = 1 49 | // 分页查询 50 | handlePage() 51 | } 52 | 53 | // 重置 54 | const resetQuery = () => { 55 | for (let i in query) { 56 | query[i] = '' 57 | } 58 | handleQuery() 59 | } 60 | 61 | //删除功能 62 | const handleDelete = (data, tip = '') => { 63 | if (apis.delete) { 64 | ElMessageBox.confirm(tip || '确认删除当前数据?', '提示', { 65 | type: 'warning', 66 | cancelButtonText: '取消', 67 | confirmButtonText: '确认' 68 | }).then(async () => { 69 | page.loading = true 70 | try { 71 | const res = await apis.delete({ id: data.id }) 72 | ElMessage.success({ message: res.msg ? res.msg : '操作成功' }) 73 | await handlePage() 74 | } finally { 75 | page.loading = false 76 | } 77 | }) 78 | } 79 | } 80 | 81 | // 状态修改 82 | const handleStatus = async (row) => { 83 | page.loading = true 84 | try { 85 | if (apis.status) { 86 | row.statusId = row.statusId ? 0 : 1 87 | const res = await apis.status({ id: row.id, statusId: row.statusId }) 88 | ElMessage.success({ message: res.msg ? res.msg : '操作成功' }) 89 | } 90 | } finally { 91 | page.loading = false 92 | } 93 | } 94 | 95 | // 排序 96 | const handleSort = async () => { 97 | const tbody = document.querySelector('.drag-table .el-table__body-wrapper tbody') 98 | Sortable.create(tbody, { 99 | onEnd({ oldIndex, newIndex }) { 100 | const row = page.list.splice(oldIndex, 1)[0] 101 | page.list.splice(newIndex, 0, row) 102 | const newSorts = page.list.map((item, index) => { 103 | return { 104 | id: item.id, 105 | sort: page.pageSize * (page.pageCurrent - 1) + index + 1 106 | } 107 | }) 108 | apis.sort(newSorts).then(() => { 109 | ElMessage.success('排序完成') 110 | }) 111 | } 112 | }) 113 | } 114 | 115 | // 获取数据 116 | onMounted(handlePage) 117 | 118 | return { 119 | page, 120 | handlePage, 121 | query, 122 | handleQuery, 123 | resetQuery, 124 | handleDelete, 125 | handleStatus, 126 | handleSort 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/assets/svg/常用.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/views/course/record/Order.vue: -------------------------------------------------------------------------------- 1 | 60 | 84 | -------------------------------------------------------------------------------- /src/assets/images/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/views/users/record/index.vue: -------------------------------------------------------------------------------- 1 | 42 | 77 | -------------------------------------------------------------------------------- /src/views/system/app/index.vue: -------------------------------------------------------------------------------- 1 | 62 | 84 | -------------------------------------------------------------------------------- /src/views/users/lecturer/LecturerForm.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 104 | -------------------------------------------------------------------------------- /src/views/system/app/AppForm.vue: -------------------------------------------------------------------------------- 1 | 32 | 104 | -------------------------------------------------------------------------------- /src/views/users/lecturer/index.vue: -------------------------------------------------------------------------------- 1 | 62 | 85 | -------------------------------------------------------------------------------- /src/views/common/link/index.vue: -------------------------------------------------------------------------------- 1 | 66 | 88 | -------------------------------------------------------------------------------- /src/views/common/navigation/index.vue: -------------------------------------------------------------------------------- 1 | 66 | 88 | -------------------------------------------------------------------------------- /src/views/users/record/Order.vue: -------------------------------------------------------------------------------- 1 | 69 | 93 | -------------------------------------------------------------------------------- /src/views/system/role/index.vue: -------------------------------------------------------------------------------- 1 | 60 | 95 | --------------------------------------------------------------------------------