From 00c6c38846f94687cfc4ce6d3c0506f8349a26e5 Mon Sep 17 00:00:00 2001
From: zhouwx <1175765986@qq.com>
Date: 星期四, 04 七月 2024 16:49:18 +0800
Subject: [PATCH] 提交

---
 src/layout/components/Sidebar/menu.js                                                        |   41 
 src/layout/components/Navbar.vue                                                             |   32 
 src/views/onlineEducation/courseManage/components/courseManageDialog.vue                     |  320 ++++++
 src/views/onlineEducation/courseManage/courseResource/index.vue                              |  115 ++
 src/views/onlineEducation/systemManage/banner/components/bannerDialog.vue                    |    2 
 src/api/onlineEducation/upload.js                                                            |   21 
 src/views/onlineEducation/courseManage/courseResource/componets/resourceDialog.vue           |  345 ++++++
 src/views/onlineEducation/people/index.vue                                                   |  140 ++
 src/views/onlineEducation/systemManage/user/components/userDialog.vue                        |    5 
 public/spark-md5.min.js                                                                      |    1 
 src/api/onlineEducation/courseManage.js                                                      |   65 +
 src/views/onlineEducation/systemManage/courseClassification/index.vue                        |    2 
 public/hash.js                                                                               |   31 
 src/api/onlineEducation/chapters.js                                                          |   70 +
 src/api/onlineEducation/student.js                                                           |   67 +
 src/views/onlineEducation/people/components/stuDialog.vue                                    |  311 ++++++
 src/views/onlineEducation/courseManage/index.vue                                             |  265 +++++
 src/router/index.js                                                                          |   29 
 src/views/onlineEducation/courseManage/courseChapters/components/chapterDialog.vue           |  249 ++++
 src/views/onlineEducation/courseManage/courseChapters/index.vue                              |  124 ++
 src/main.js                                                                                  |    1 
 package.json                                                                                 |    3 
 src/views/onlineEducation/courseManage/courseChapters/components/chooseResource.vue          |  123 ++
 src/api/onlineEducation/courseResource.js                                                    |   49 
 src/views/components/upload.vue                                                              |  530 ++++++++++
 src/views/onlineEducation/systemManage/courseClassification/components/courseClassDialog.vue |   13 
 26 files changed, 2,896 insertions(+), 58 deletions(-)

diff --git a/package.json b/package.json
index 41c27a2..ad42e19 100644
--- a/package.json
+++ b/package.json
@@ -31,9 +31,12 @@
     "js-cookie": "3.0.1",
     "jsencrypt": "3.3.1",
     "nprogress": "0.2.0",
+    "p-limit": "^5.0.0",
     "pinia": "2.0.22",
     "quill": "^2.0.0-dev.3",
+    "spark-md5": "^3.0.2",
     "tinymce": "^5.10.2",
+    "video.js": "^8.12.0",
     "vue": "3.2.45",
     "vue-baidu-map-3x": "^1.0.35",
     "vue-cropper": "1.0.3",
diff --git a/public/hash.js b/public/hash.js
new file mode 100644
index 0000000..a5245bd
--- /dev/null
+++ b/public/hash.js
@@ -0,0 +1,31 @@
+self.importScripts("/spark-md5.min.js"); // 导入脚本
+
+// 生成文件 hash
+self.onmessage = e => {
+  const { fileChunkList } = e.data;
+  const spark = new self.SparkMD5.ArrayBuffer();
+  let percentage = 0;
+  let count = 0;
+  const loadNext = index => {
+    const reader = new FileReader();
+    reader.readAsArrayBuffer(fileChunkList[index].file);
+    reader.onload = e => {
+      count++;
+      spark.append(e.target.result);
+      if (count === fileChunkList.length) {
+        self.postMessage({
+          percentage: 100,
+          hash: spark.end()
+        });
+        self.close();
+      } else {
+        percentage += 100 / fileChunkList.length;
+        self.postMessage({
+          percentage
+        });
+        loadNext(count);
+      }
+    };
+  };
+  loadNext(0);
+};
\ No newline at end of file
diff --git a/public/spark-md5.min.js b/public/spark-md5.min.js
new file mode 100644
index 0000000..5a22f70
--- /dev/null
+++ b/public/spark-md5.min.js
@@ -0,0 +1 @@
+(function(factory){if(typeof exports==="object"){module.exports=factory()}else if(typeof define==="function"&&define.amd){define(factory)}else{var glob;try{glob=window}catch(e){glob=self}glob.SparkMD5=factory()}})(function(undefined){"use strict";var add32=function(a,b){return a+b&4294967295},hex_chr=["0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f"];function cmn(q,a,b,x,s,t){a=add32(add32(a,q),add32(x,t));return add32(a<<s|a>>>32-s,b)}function md5cycle(x,k){var a=x[0],b=x[1],c=x[2],d=x[3];a+=(b&c|~b&d)+k[0]-680876936|0;a=(a<<7|a>>>25)+b|0;d+=(a&b|~a&c)+k[1]-389564586|0;d=(d<<12|d>>>20)+a|0;c+=(d&a|~d&b)+k[2]+606105819|0;c=(c<<17|c>>>15)+d|0;b+=(c&d|~c&a)+k[3]-1044525330|0;b=(b<<22|b>>>10)+c|0;a+=(b&c|~b&d)+k[4]-176418897|0;a=(a<<7|a>>>25)+b|0;d+=(a&b|~a&c)+k[5]+1200080426|0;d=(d<<12|d>>>20)+a|0;c+=(d&a|~d&b)+k[6]-1473231341|0;c=(c<<17|c>>>15)+d|0;b+=(c&d|~c&a)+k[7]-45705983|0;b=(b<<22|b>>>10)+c|0;a+=(b&c|~b&d)+k[8]+1770035416|0;a=(a<<7|a>>>25)+b|0;d+=(a&b|~a&c)+k[9]-1958414417|0;d=(d<<12|d>>>20)+a|0;c+=(d&a|~d&b)+k[10]-42063|0;c=(c<<17|c>>>15)+d|0;b+=(c&d|~c&a)+k[11]-1990404162|0;b=(b<<22|b>>>10)+c|0;a+=(b&c|~b&d)+k[12]+1804603682|0;a=(a<<7|a>>>25)+b|0;d+=(a&b|~a&c)+k[13]-40341101|0;d=(d<<12|d>>>20)+a|0;c+=(d&a|~d&b)+k[14]-1502002290|0;c=(c<<17|c>>>15)+d|0;b+=(c&d|~c&a)+k[15]+1236535329|0;b=(b<<22|b>>>10)+c|0;a+=(b&d|c&~d)+k[1]-165796510|0;a=(a<<5|a>>>27)+b|0;d+=(a&c|b&~c)+k[6]-1069501632|0;d=(d<<9|d>>>23)+a|0;c+=(d&b|a&~b)+k[11]+643717713|0;c=(c<<14|c>>>18)+d|0;b+=(c&a|d&~a)+k[0]-373897302|0;b=(b<<20|b>>>12)+c|0;a+=(b&d|c&~d)+k[5]-701558691|0;a=(a<<5|a>>>27)+b|0;d+=(a&c|b&~c)+k[10]+38016083|0;d=(d<<9|d>>>23)+a|0;c+=(d&b|a&~b)+k[15]-660478335|0;c=(c<<14|c>>>18)+d|0;b+=(c&a|d&~a)+k[4]-405537848|0;b=(b<<20|b>>>12)+c|0;a+=(b&d|c&~d)+k[9]+568446438|0;a=(a<<5|a>>>27)+b|0;d+=(a&c|b&~c)+k[14]-1019803690|0;d=(d<<9|d>>>23)+a|0;c+=(d&b|a&~b)+k[3]-187363961|0;c=(c<<14|c>>>18)+d|0;b+=(c&a|d&~a)+k[8]+1163531501|0;b=(b<<20|b>>>12)+c|0;a+=(b&d|c&~d)+k[13]-1444681467|0;a=(a<<5|a>>>27)+b|0;d+=(a&c|b&~c)+k[2]-51403784|0;d=(d<<9|d>>>23)+a|0;c+=(d&b|a&~b)+k[7]+1735328473|0;c=(c<<14|c>>>18)+d|0;b+=(c&a|d&~a)+k[12]-1926607734|0;b=(b<<20|b>>>12)+c|0;a+=(b^c^d)+k[5]-378558|0;a=(a<<4|a>>>28)+b|0;d+=(a^b^c)+k[8]-2022574463|0;d=(d<<11|d>>>21)+a|0;c+=(d^a^b)+k[11]+1839030562|0;c=(c<<16|c>>>16)+d|0;b+=(c^d^a)+k[14]-35309556|0;b=(b<<23|b>>>9)+c|0;a+=(b^c^d)+k[1]-1530992060|0;a=(a<<4|a>>>28)+b|0;d+=(a^b^c)+k[4]+1272893353|0;d=(d<<11|d>>>21)+a|0;c+=(d^a^b)+k[7]-155497632|0;c=(c<<16|c>>>16)+d|0;b+=(c^d^a)+k[10]-1094730640|0;b=(b<<23|b>>>9)+c|0;a+=(b^c^d)+k[13]+681279174|0;a=(a<<4|a>>>28)+b|0;d+=(a^b^c)+k[0]-358537222|0;d=(d<<11|d>>>21)+a|0;c+=(d^a^b)+k[3]-722521979|0;c=(c<<16|c>>>16)+d|0;b+=(c^d^a)+k[6]+76029189|0;b=(b<<23|b>>>9)+c|0;a+=(b^c^d)+k[9]-640364487|0;a=(a<<4|a>>>28)+b|0;d+=(a^b^c)+k[12]-421815835|0;d=(d<<11|d>>>21)+a|0;c+=(d^a^b)+k[15]+530742520|0;c=(c<<16|c>>>16)+d|0;b+=(c^d^a)+k[2]-995338651|0;b=(b<<23|b>>>9)+c|0;a+=(c^(b|~d))+k[0]-198630844|0;a=(a<<6|a>>>26)+b|0;d+=(b^(a|~c))+k[7]+1126891415|0;d=(d<<10|d>>>22)+a|0;c+=(a^(d|~b))+k[14]-1416354905|0;c=(c<<15|c>>>17)+d|0;b+=(d^(c|~a))+k[5]-57434055|0;b=(b<<21|b>>>11)+c|0;a+=(c^(b|~d))+k[12]+1700485571|0;a=(a<<6|a>>>26)+b|0;d+=(b^(a|~c))+k[3]-1894986606|0;d=(d<<10|d>>>22)+a|0;c+=(a^(d|~b))+k[10]-1051523|0;c=(c<<15|c>>>17)+d|0;b+=(d^(c|~a))+k[1]-2054922799|0;b=(b<<21|b>>>11)+c|0;a+=(c^(b|~d))+k[8]+1873313359|0;a=(a<<6|a>>>26)+b|0;d+=(b^(a|~c))+k[15]-30611744|0;d=(d<<10|d>>>22)+a|0;c+=(a^(d|~b))+k[6]-1560198380|0;c=(c<<15|c>>>17)+d|0;b+=(d^(c|~a))+k[13]+1309151649|0;b=(b<<21|b>>>11)+c|0;a+=(c^(b|~d))+k[4]-145523070|0;a=(a<<6|a>>>26)+b|0;d+=(b^(a|~c))+k[11]-1120210379|0;d=(d<<10|d>>>22)+a|0;c+=(a^(d|~b))+k[2]+718787259|0;c=(c<<15|c>>>17)+d|0;b+=(d^(c|~a))+k[9]-343485551|0;b=(b<<21|b>>>11)+c|0;x[0]=a+x[0]|0;x[1]=b+x[1]|0;x[2]=c+x[2]|0;x[3]=d+x[3]|0}function md5blk(s){var md5blks=[],i;for(i=0;i<64;i+=4){md5blks[i>>2]=s.charCodeAt(i)+(s.charCodeAt(i+1)<<8)+(s.charCodeAt(i+2)<<16)+(s.charCodeAt(i+3)<<24)}return md5blks}function md5blk_array(a){var md5blks=[],i;for(i=0;i<64;i+=4){md5blks[i>>2]=a[i]+(a[i+1]<<8)+(a[i+2]<<16)+(a[i+3]<<24)}return md5blks}function md51(s){var n=s.length,state=[1732584193,-271733879,-1732584194,271733878],i,length,tail,tmp,lo,hi;for(i=64;i<=n;i+=64){md5cycle(state,md5blk(s.substring(i-64,i)))}s=s.substring(i-64);length=s.length;tail=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];for(i=0;i<length;i+=1){tail[i>>2]|=s.charCodeAt(i)<<(i%4<<3)}tail[i>>2]|=128<<(i%4<<3);if(i>55){md5cycle(state,tail);for(i=0;i<16;i+=1){tail[i]=0}}tmp=n*8;tmp=tmp.toString(16).match(/(.*?)(.{0,8})$/);lo=parseInt(tmp[2],16);hi=parseInt(tmp[1],16)||0;tail[14]=lo;tail[15]=hi;md5cycle(state,tail);return state}function md51_array(a){var n=a.length,state=[1732584193,-271733879,-1732584194,271733878],i,length,tail,tmp,lo,hi;for(i=64;i<=n;i+=64){md5cycle(state,md5blk_array(a.subarray(i-64,i)))}a=i-64<n?a.subarray(i-64):new Uint8Array(0);length=a.length;tail=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];for(i=0;i<length;i+=1){tail[i>>2]|=a[i]<<(i%4<<3)}tail[i>>2]|=128<<(i%4<<3);if(i>55){md5cycle(state,tail);for(i=0;i<16;i+=1){tail[i]=0}}tmp=n*8;tmp=tmp.toString(16).match(/(.*?)(.{0,8})$/);lo=parseInt(tmp[2],16);hi=parseInt(tmp[1],16)||0;tail[14]=lo;tail[15]=hi;md5cycle(state,tail);return state}function rhex(n){var s="",j;for(j=0;j<4;j+=1){s+=hex_chr[n>>j*8+4&15]+hex_chr[n>>j*8&15]}return s}function hex(x){var i;for(i=0;i<x.length;i+=1){x[i]=rhex(x[i])}return x.join("")}if(hex(md51("hello"))!=="5d41402abc4b2a76b9719d911017c592"){add32=function(x,y){var lsw=(x&65535)+(y&65535),msw=(x>>16)+(y>>16)+(lsw>>16);return msw<<16|lsw&65535}}if(typeof ArrayBuffer!=="undefined"&&!ArrayBuffer.prototype.slice){(function(){function clamp(val,length){val=val|0||0;if(val<0){return Math.max(val+length,0)}return Math.min(val,length)}ArrayBuffer.prototype.slice=function(from,to){var length=this.byteLength,begin=clamp(from,length),end=length,num,target,targetArray,sourceArray;if(to!==undefined){end=clamp(to,length)}if(begin>end){return new ArrayBuffer(0)}num=end-begin;target=new ArrayBuffer(num);targetArray=new Uint8Array(target);sourceArray=new Uint8Array(this,begin,num);targetArray.set(sourceArray);return target}})()}function toUtf8(str){if(/[\u0080-\uFFFF]/.test(str)){str=unescape(encodeURIComponent(str))}return str}function utf8Str2ArrayBuffer(str,returnUInt8Array){var length=str.length,buff=new ArrayBuffer(length),arr=new Uint8Array(buff),i;for(i=0;i<length;i+=1){arr[i]=str.charCodeAt(i)}return returnUInt8Array?arr:buff}function arrayBuffer2Utf8Str(buff){return String.fromCharCode.apply(null,new Uint8Array(buff))}function concatenateArrayBuffers(first,second,returnUInt8Array){var result=new Uint8Array(first.byteLength+second.byteLength);result.set(new Uint8Array(first));result.set(new Uint8Array(second),first.byteLength);return returnUInt8Array?result:result.buffer}function hexToBinaryString(hex){var bytes=[],length=hex.length,x;for(x=0;x<length-1;x+=2){bytes.push(parseInt(hex.substr(x,2),16))}return String.fromCharCode.apply(String,bytes)}function SparkMD5(){this.reset()}SparkMD5.prototype.append=function(str){this.appendBinary(toUtf8(str));return this};SparkMD5.prototype.appendBinary=function(contents){this._buff+=contents;this._length+=contents.length;var length=this._buff.length,i;for(i=64;i<=length;i+=64){md5cycle(this._hash,md5blk(this._buff.substring(i-64,i)))}this._buff=this._buff.substring(i-64);return this};SparkMD5.prototype.end=function(raw){var buff=this._buff,length=buff.length,i,tail=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],ret;for(i=0;i<length;i+=1){tail[i>>2]|=buff.charCodeAt(i)<<(i%4<<3)}this._finish(tail,length);ret=hex(this._hash);if(raw){ret=hexToBinaryString(ret)}this.reset();return ret};SparkMD5.prototype.reset=function(){this._buff="";this._length=0;this._hash=[1732584193,-271733879,-1732584194,271733878];return this};SparkMD5.prototype.getState=function(){return{buff:this._buff,length:this._length,hash:this._hash}};SparkMD5.prototype.setState=function(state){this._buff=state.buff;this._length=state.length;this._hash=state.hash;return this};SparkMD5.prototype.destroy=function(){delete this._hash;delete this._buff;delete this._length};SparkMD5.prototype._finish=function(tail,length){var i=length,tmp,lo,hi;tail[i>>2]|=128<<(i%4<<3);if(i>55){md5cycle(this._hash,tail);for(i=0;i<16;i+=1){tail[i]=0}}tmp=this._length*8;tmp=tmp.toString(16).match(/(.*?)(.{0,8})$/);lo=parseInt(tmp[2],16);hi=parseInt(tmp[1],16)||0;tail[14]=lo;tail[15]=hi;md5cycle(this._hash,tail)};SparkMD5.hash=function(str,raw){return SparkMD5.hashBinary(toUtf8(str),raw)};SparkMD5.hashBinary=function(content,raw){var hash=md51(content),ret=hex(hash);return raw?hexToBinaryString(ret):ret};SparkMD5.ArrayBuffer=function(){this.reset()};SparkMD5.ArrayBuffer.prototype.append=function(arr){var buff=concatenateArrayBuffers(this._buff.buffer,arr,true),length=buff.length,i;this._length+=arr.byteLength;for(i=64;i<=length;i+=64){md5cycle(this._hash,md5blk_array(buff.subarray(i-64,i)))}this._buff=i-64<length?new Uint8Array(buff.buffer.slice(i-64)):new Uint8Array(0);return this};SparkMD5.ArrayBuffer.prototype.end=function(raw){var buff=this._buff,length=buff.length,tail=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],i,ret;for(i=0;i<length;i+=1){tail[i>>2]|=buff[i]<<(i%4<<3)}this._finish(tail,length);ret=hex(this._hash);if(raw){ret=hexToBinaryString(ret)}this.reset();return ret};SparkMD5.ArrayBuffer.prototype.reset=function(){this._buff=new Uint8Array(0);this._length=0;this._hash=[1732584193,-271733879,-1732584194,271733878];return this};SparkMD5.ArrayBuffer.prototype.getState=function(){var state=SparkMD5.prototype.getState.call(this);state.buff=arrayBuffer2Utf8Str(state.buff);return state};SparkMD5.ArrayBuffer.prototype.setState=function(state){state.buff=utf8Str2ArrayBuffer(state.buff,true);return SparkMD5.prototype.setState.call(this,state)};SparkMD5.ArrayBuffer.prototype.destroy=SparkMD5.prototype.destroy;SparkMD5.ArrayBuffer.prototype._finish=SparkMD5.prototype._finish;SparkMD5.ArrayBuffer.hash=function(arr,raw){var hash=md51_array(new Uint8Array(arr)),ret=hex(hash);return raw?hexToBinaryString(ret):ret};return SparkMD5});
diff --git a/src/api/onlineEducation/chapters.js b/src/api/onlineEducation/chapters.js
new file mode 100644
index 0000000..26898bf
--- /dev/null
+++ b/src/api/onlineEducation/chapters.js
@@ -0,0 +1,70 @@
+import request from "@/utils/request";
+
+
+export function checkChapterName(data) {
+    return request({
+        url: '/course-chapter/checkNameUnique',
+        method: 'post',
+        data: data
+    })
+}
+export function getChapters(params) {
+    return request({
+        url: '/course-chapter/getCourseChapter',
+        method: 'get',
+        params: params
+    })
+}
+
+export function addChapter(data) {
+    return request({
+        url: '/course-chapter',
+        method: 'post',
+        data: data
+    })
+}
+
+export function addChapterPeriod(data) {
+    return request({
+        url: '/course-chapter-period',
+        method: 'post',
+        data: data
+    })
+}
+
+export function editChapter(params) {
+    return request({
+        url: `/course-chapter`,
+        method: 'put',
+        data: params
+    })
+}
+export function editChapterPeriod(params) {
+    return request({
+        url: `/course-chapter-period`,
+        method: 'put',
+        data: params
+    })
+}
+
+export function getChapterById(params) {
+    return request({
+        url: '/course-chapter/' +params ,
+        method: 'get',
+        params: params
+    })
+}
+
+
+export function delChapter(data) {
+    return request({
+        url: `/course-chapter/` + data,
+        method: 'delete'
+    })
+}
+export function delPeriod(data) {
+    return request({
+        url: `/course-chapter-period/` + data,
+        method: 'delete'
+    })
+}
diff --git a/src/api/onlineEducation/courseManage.js b/src/api/onlineEducation/courseManage.js
new file mode 100644
index 0000000..0024c74
--- /dev/null
+++ b/src/api/onlineEducation/courseManage.js
@@ -0,0 +1,65 @@
+import request from "@/utils/request";
+
+export function checkCourseName(data) {
+    return request({
+        url: '/course/checkNameUnique',
+        method: 'post',
+        data: data
+    })
+}
+
+export function getCourse(params) {
+    return request({
+        url: '/course/list',
+        method: 'get',
+        params: params
+    })
+}
+
+export function addCourse(data) {
+    return request({
+        url: '/course',
+        method: 'post',
+        data: data
+    })
+}
+
+export function editCourse(params) {
+    return request({
+        url: `/course`,
+        method: 'put',
+        data: params
+    })
+}
+
+export function getCourseById(params) {
+    return request({
+        url: '/course/' +params ,
+        method: 'get',
+        params: params
+    })
+}
+
+
+export function delCourse(data) {
+    return request({
+        url: `/course/` + data,
+        method: 'delete'
+    })
+}
+
+export function doCourse(data) {
+    return request({
+        url: '/course/doApprove',
+        method: 'post',
+        data: data
+    })
+}
+
+export function changeCourseStatus(params) {
+    return request({
+        url: '/course/changeStatus',
+        method: 'put',
+        data: params
+    })
+}
diff --git a/src/api/onlineEducation/courseResource.js b/src/api/onlineEducation/courseResource.js
new file mode 100644
index 0000000..46b7bc6
--- /dev/null
+++ b/src/api/onlineEducation/courseResource.js
@@ -0,0 +1,49 @@
+import request from "@/utils/request";
+
+export function checkResourceName(data) {
+    return request({
+        url: '/resource/checkNameUnique',
+        method: 'post',
+        data: data
+    })
+}
+
+export function getResource(params) {
+    return request({
+        url: '/resource/list',
+        method: 'get',
+        params: params
+    })
+}
+
+export function addResource(data) {
+    return request({
+        url: '/resource',
+        method: 'post',
+        data: data
+    })
+}
+
+export function editResource(params) {
+    return request({
+        url: `/resource`,
+        method: 'put',
+        data: params
+    })
+}
+
+export function getResourceById(params) {
+    return request({
+        url: '/resource/' +params ,
+        method: 'get',
+        params: params
+    })
+}
+
+
+export function delResource(data) {
+    return request({
+        url: `/resource/` + data,
+        method: 'delete'
+    })
+}
diff --git a/src/api/onlineEducation/student.js b/src/api/onlineEducation/student.js
new file mode 100644
index 0000000..46a6d13
--- /dev/null
+++ b/src/api/onlineEducation/student.js
@@ -0,0 +1,67 @@
+import request from '@/utils/request'
+
+export function getStudent(param) {
+    return request({
+        url: '/student/list',
+        method: 'get',
+        params: param
+    })
+}
+
+export function getStudentById(params) {
+    return request({
+        url: '/system/user/' +params ,
+        method: 'get',
+        params: params
+    })
+}
+
+export function addStudent(data) {
+    return request({
+        url: '/student',
+        method: 'post',
+        data: data
+    })
+}
+
+export function checkStuIdNo(data) {
+    return request({
+        url: '/student/checkIdNoUnique',
+        method: 'post',
+        data: data
+    })
+}
+
+export function checkStuPhone(data) {
+    return request({
+        url: '/student/checkPhoneUnique',
+        method: 'post',
+        data: data
+    })
+}
+
+
+
+export function editStudent(params) {
+    return request({
+        url: `/student`,
+        method: 'put',
+        data: params
+    })
+}
+
+export function resetPwd(params) {
+    return request({
+        url: `/student/resetPwd`,
+        method: 'put',
+        data: params
+    })
+}
+
+export function delStudent(userId) {
+    return request({
+        url: '/student/' + userId,
+        method: 'delete'
+    })
+}
+
diff --git a/src/api/onlineEducation/upload.js b/src/api/onlineEducation/upload.js
new file mode 100644
index 0000000..a42f314
--- /dev/null
+++ b/src/api/onlineEducation/upload.js
@@ -0,0 +1,21 @@
+import request from '@/utils/request'
+
+
+export function uploadFileRequest(data,uploadProgressHandle) {
+    return request({
+        url: '/system/common/uploadSlice',
+        method: 'post',
+        headers:{'Content-Type':'multipart/form-data'},
+        data: data,
+        onUploadProgress: uploadProgressHandle
+    })
+}
+
+export function mergeFileRequest(data) {
+    return request({
+        url: '/system/common/uploadMerge',
+        headers:{'Content-Type':'multipart/form-data'},
+        method: 'post',
+        data: data
+    })
+}
diff --git a/src/layout/components/Navbar.vue b/src/layout/components/Navbar.vue
index 68b1fd7..f75f237 100644
--- a/src/layout/components/Navbar.vue
+++ b/src/layout/components/Navbar.vue
@@ -48,7 +48,8 @@
         </el-dropdown>
       </div>
     </div>
-<!--    <review-dialog ref="reviewRef" ></review-dialog>-->
+
+    <user-dialog ref="reviewRef" ></user-dialog>
 <!--    <register ref="regRef" @getList="getList" />-->
 <!--    <supervise-dialog ref="superRef"></supervise-dialog>-->
   </div>
@@ -72,6 +73,7 @@
 import Cookies from "js-cookie";
 import {getUserById} from "@/api/sysUsers";
 import menu from "@/layout/components/Sidebar/menu";
+import userDialog from '@/views/onlineEducation/systemManage/user/components/userDialog.vue'
 
 const appStore = useAppStore()
 const userStore = useUserStore()
@@ -135,34 +137,10 @@
 
 function getInfo() {
   console.log("getInfo",userInfo.value)
-  //机构用户
-  if(userInfo.value.identity === 1){
-    //审核驳回(可修改)
-    if(userInfo.value.state === 3){
-      const obj = {
-        id: userInfo.value.id,
-        username: userInfo.value.username,
-        agencyId: userInfo.value.agentId
-      }
-      regRef.value.openDialog('reject', obj);
-    }else{
-      //审核通过、未审核状态(不可修改)
-      const obj = {
-        agencyId: userInfo.value.agentId
-      }
-      reviewRef.value.openDialog(obj,'view')
-    }
-  }
-  //监管用户
-  else if (userInfo.value.identity === 0) {
-    const obj = {
-      id: userInfo.value.id
-    }
-    superRef.value.openDialog('view', obj);
-  }
+  reviewRef.value.openDialog('view',userInfo.value)
 }
 function editPsd() {
-  superRef.value.openDialog('pwd', userInfo.value);
+  reviewRef.value.openDialog('pwd',userInfo.value)
 }
 const sidebarRouters = ref([])
 
diff --git a/src/layout/components/Sidebar/menu.js b/src/layout/components/Sidebar/menu.js
index c319557..0e23c86 100644
--- a/src/layout/components/Sidebar/menu.js
+++ b/src/layout/components/Sidebar/menu.js
@@ -1,10 +1,27 @@
 import Layout from '@/layout'
 const menu = {
     adminMenu: [
+        // {
+        //     path: '/course',
+        //     name: 'Course',
+        //     meta: { title: '课程管理',icon: 'documentation',affix: true }
+        // },
         {
-            path: '/course',
-            name: 'Course',
-            meta: { title: '课程管理',icon: 'documentation',affix: true }
+            path: '/onlineEducation',
+            redirect: '/onlineEducation/courseManage',
+            meta: { title: '课程管理',icon: 'form'},
+            children: [
+                {
+                    path: 'course',
+                    name: 'course',
+                    meta: { title: '课程列表',icon: 'list'}
+                } ,
+                {
+                    path: 'courseResource',
+                    name: 'courseResource',
+                    meta: { title: '课程资源',icon: 'education'}
+                } ,
+            ]
         },
         {
             path: '/question',
@@ -66,9 +83,21 @@
     ],
     companyMenu: [
         {
-            path: '/course',
-            name: 'Course',
-            meta: { title: '课程管理',icon: 'documentation',affix: true }
+            path: '/onlineEducation',
+            redirect: '/onlineEducation/courseManage',
+            meta: { title: '课程管理',icon: 'form'},
+            children: [
+                {
+                    path: 'course',
+                    name: 'course',
+                    meta: { title: '课程列表',icon: 'list'}
+                } ,
+                {
+                    path: 'courseResource',
+                    name: 'courseResource',
+                    meta: { title: '课程资源',icon: 'education'}
+                } ,
+            ]
         },
         {
             path: '/question',
diff --git a/src/main.js b/src/main.js
index d1504ce..ad736f7 100644
--- a/src/main.js
+++ b/src/main.js
@@ -51,6 +51,7 @@
 import { Boot } from '@wangeditor/editor'
 import attachmentModule from '@wangeditor/plugin-upload-attachment'
 import loadMore from '@/utils/selectLoadMoreDirective'
+import "video.js/dist/video-js.css"
 Boot.registerModule(attachmentModule)
 const app = createApp(App)
 
diff --git a/src/router/index.js b/src/router/index.js
index d650ebd..20f9d8f 100644
--- a/src/router/index.js
+++ b/src/router/index.js
@@ -53,6 +53,18 @@
     hidden: true
   },
 
+  {
+    path: '/chapters',
+    component: Layout,
+    redirect: '/chapters',
+    children: [
+      {
+        path: '/chapters',
+        component: () => import('@/views/onlineEducation/courseManage/courseChapters/index.vue'),
+        name: 'Chapters',
+      }
+    ]
+  },
   // {
   //   path: '',
   //   component: Layout,
@@ -79,16 +91,23 @@
     ]
   },
   {
-    path: '/course',
+    path: '/onlineEducation',
     component: Layout,
-    redirect: '/course',
+    redirect: '/onlineEducation/courseManage',
+    meta: { title: '课程管理'},
     children: [
       {
-        path: '/course',
+        path: 'course',
         component: () => import('@/views/onlineEducation/courseManage/index.vue'),
         name: 'course',
-        meta: { title: '课程管理',icon: 'form',  affix: true }
-      }
+        meta: { title: '课程列表',icon: 'form',  affix: true }
+      },
+      {
+        path: 'courseResource',
+        component: () => import('@/views/onlineEducation/courseManage/courseResource/index.vue'),
+        name: 'courseResource',
+        meta: { title: '课程资源',icon: 'form',  affix: true }
+      },
     ]
   },
   {
diff --git a/src/views/components/upload.vue b/src/views/components/upload.vue
new file mode 100644
index 0000000..efb2c60
--- /dev/null
+++ b/src/views/components/upload.vue
@@ -0,0 +1,530 @@
+
+<template>
+  <div class="greetings">
+    <el-upload accept=".mp4, .mp3, .xls, .xlsx, .doc, .docx, .ppt, .pptx, .pdf"   :on-change="handleFileChange" :on-preview="view"  :auto-upload="false" ref="uploadfileComponent" :limit="1" :on-exceed="handleExceed"  v-model:file-list="fileList">
+      <template #trigger>
+        <el-button type="primary">选择文件</el-button>
+      </template>
+      <el-button :disabled="uploadDisabled" style="margin-left: 10px" type="success" @click="handlerUpload">上传</el-button>
+      <el-button class="ml-3" type="success" @click="resetData" >重置</el-button>
+      <template #tip>
+        <br /><br />
+        <span>上传进度:{{ fakeUploadPercentage }}%</span>
+        <el-progress :text-inside="true" :stroke-width="26" :percentage="fakeUploadPercentage" />
+        <div class="el-upload__tip text-red">限制一个文件, 新文件将会覆盖原文件</div>
+      </template>
+    </el-upload>
+    <br>
+    <div v-if="container.showVideo" style="width: 300px;height: 200px">
+      <video   ref="videoPlayer" class="video-js" style="margin: auto auto"></video>
+    </div>
+
+  </div>
+</template>
+
+<script setup>
+import { ElMessage } from "element-plus";
+import videojs from "video.js"
+import { computed, nextTick, onMounted, onUnmounted,ref,reactive,watch  } from "vue";
+import SparkMD5 from "spark-md5";
+import {uploadFileRequest,mergeFileRequest} from "@/api/onlineEducation/upload"
+import pLimit from 'p-limit'
+
+const videoPlayer = ref(null)
+const myPlayer = ref(null)
+const uploadDisabled=ref(false)
+const chunkSize = ref(10 * 1024 * 1024) // 切片大小
+const uploadedCount=ref(0) //已上传的分配个数
+const fileChunkList=ref([])
+const fileList=ref([])
+const uploadfileComponent=ref(null)
+const emit = defineEmits(["getFile"]);
+const props = defineProps({
+  responseType: {
+    type: Number,
+    default: 0
+  }
+})
+const container=reactive({
+  file:{
+    name:'',
+    percentage:0,
+    status:1,
+    size:0,
+    url:'',
+    raw:null,
+    uid:0
+  },
+  fileMd5:'',
+  worker:null,
+  showVideo:false
+})
+// 生成文件hash的进度
+const hashPercentage = ref(0)
+// 显示在页面上的文件上传进度
+const fakeUploadPercentage = ref(0)
+const type = ref();
+onMounted(() => {
+  type.value = props.responseType
+  // getVideo(props.responseType)
+
+})
+const resourcePath = ref();
+const getVideo = (value) => {
+  type.value = value;
+  if(value == 1){
+    // container.showVideo = true
+    nextTick(() => {
+      console.log("111111",videoPlayer.value)
+      myPlayer.value = videojs(videoPlayer.value, {
+        poster: "",//视频封面
+        controls: true,//视频控件
+        autoplay:true,//自动播放
+        sources: [
+          {
+            src: resourcePath.value ? "http://192.168.2.16:9000/trainexam/" + resourcePath.value : '',
+            // src:'',
+            type: 'application/x-mpegURL',
+          }
+        ],
+        controlBar: {
+          remainingTimeDisplay: {
+            displayNegative: false
+          }
+        },
+        playbackRates: [0.5, 1, 1.5, 2]//设置播放速度
+      }, onPlayerReady)
+    });
+  }
+}
+// watch(() => props.responseType, value => getVideo(value))
+onUnmounted(() => {
+  if (myPlayer.value) {
+    myPlayer.value.dispose()
+  }
+})
+
+const dispose = () => {
+  // if (myPlayer.value) {
+  //   myPlayer.value.dispose()
+  //   resourcePath.value = ''
+  // }
+  container.showVideo = false;
+  resourcePath.value = ''
+  hashPercentage.value=0
+  uploadPercentage.value=0
+  fakeUploadPercentage.value=0
+  uploadedCount.value=0
+  fileChunkList.value=[]
+  fileList.value=[]
+}
+
+const changeType = (val) => {
+  type.value = val
+  dispose()
+  if(val == 1){
+    container.showVideo = true
+    nextTick(() => {
+      getVideo(val)
+    })
+  }
+}
+const openValue = ref();
+const open = (val) => {
+  console.log("val",val)
+  openValue.value = val
+  fakeUploadPercentage.value = 100
+  if(val.resourceType == 1){
+    container.showVideo = true
+    resourcePath.value = val.resourcePath;
+    getVideo(val.resourceType)
+  }else {
+    container.showVideo = false
+    // if (myPlayer.value) {
+    //   myPlayer.value.dispose()
+    // }
+    fileList.value.push({
+      path: val.resourcePath,
+      name: val.originName
+    })
+  }
+}
+const view = (file) => {
+  console.log('vlco',file)
+  // console.log("点击文件=>", file);
+  const url = 'http://192.168.2.16:9000/trainexam/' + file.path;
+  const link = document.createElement("a");
+  link.href = url;
+  link.download = file.name;
+  // link.target = "_blank";
+  document.body.appendChild(link);
+  link.click();
+  document.body.removeChild(link);
+}
+// video初始化完成的回调函数
+const onPlayerReady = () => {
+  myPlayer.value.log("play.....")
+  bindVideoEvents()
+}
+// 绑定事件
+const bindVideoEvents = () => {
+  if (!myPlayer.value) return
+  myPlayer.value.on('play', onPlay)
+  myPlayer.value.on('pause', onPause)
+  myPlayer.value.on('ended', onEnded)
+  myPlayer.value.on('timeupdate', onTimeupdate)
+  myPlayer.value.on('loadedmetadata', onLoadedmetadata)
+  myPlayer.value.on('fullscreenchange', onFullscreenchange)
+  myPlayer.value.on('error', err => {
+    console.log('视频加载发生错误', err)
+  })
+}
+
+const onPlay = () => {
+  console.log('播放视频')
+}
+const onPause = () => {
+  console.log('暂停播放')
+}
+const onEnded = () => {}
+const onTimeupdate = () => {
+  console.log('播放位置已更改时,播放时间更新')
+}
+// 全屏切换
+const onFullscreenchange = () => {
+  console.log('全屏状态改变')
+}
+// 元数据加载完成
+const onLoadedmetadata = () => {
+  console.log('元数据加载完成')
+
+}
+
+
+//计算文件上传的进度
+const uploadPercentage= computed({
+  get(){
+    if(!container.file||!fileChunkList.value.length){
+      return 0
+    }
+    const loaded=fileChunkList.value.map(item => item.size * item.percentage).reduce((acc,cur) => {
+      return acc+cur
+    })
+    console.log('loaded',uploadedCount.value,loaded)
+    return parseInt((loaded/container.file.size).toFixed(2))
+  },
+  set(value){
+    return value
+  }
+})
+
+// watch uploadPercentage,得到fakeUploadPercentage
+watch(uploadPercentage, (newValue) => {
+  if (newValue >= fakeUploadPercentage.value) {
+    fakeUploadPercentage.value = newValue
+  }
+})
+
+const resetData = () => {
+  container.showVideo = false
+  resourcePath.value = ''
+  hashPercentage.value=0
+  uploadPercentage.value=0
+  fakeUploadPercentage.value=0
+  uploadedCount.value=0
+  fileChunkList.value=[]
+  fileList.value=[]
+  if(container.worker){
+    container.worker.onmessage=null
+  }
+  // if (myPlayer.value) {
+  //   myPlayer.value.dispose()
+  // }
+  const file = {
+    resourceSize: null,
+    md5: '',
+    resourcePath: '',
+    mediaType: '',
+    docPage: 0,
+    resourceLength: '',
+    originName:''
+  }
+  emit("getFile",file)
+}
+
+//选择了文件
+const handleFileChange=(uploadFile,uploadFiles) =>{
+  // resetData()
+  if(!uploadFile){
+    return
+  }
+  container.file=uploadFile
+  fileList.value=uploadFiles
+}
+
+const handleExceed= (files) => {
+  uploadfileComponent.value.clearFiles()
+  nextTick(() => {
+    uploadfileComponent.value.handleStart(files[0])
+  })
+}
+
+//上传
+const handlerUpload= async() => {
+
+  if(type.value == null){
+    ElMessage({
+      type: 'warning',
+      message: '请先选择资源类型'
+    });
+    return false
+  }
+
+  if(!container.file.raw){
+    return
+  }
+  if(container.file.raw.size > 1024 * 1024 * 1000){
+      ElMessage({
+        type: 'warning',
+        message: '文件大小不能超过1G'
+      });
+      return false
+  }
+  const filetype = container.file.raw.name.split(".").pop();
+  const extension = (filetype === "mp4" || filetype ==="mp3" || filetype ==="xls" || filetype === "xlsx" || filetype ==="doc" || filetype ==="docx" || filetype === "ppt" || filetype ==="pptx" || filetype ==="pdf");
+  if (!extension ) {
+    ElMessage({
+      type: 'warning',
+      message: '暂不支持该格式上传'
+    });
+    return false;
+  }
+  if((type.value == 1 && filetype != 'mp4') || (type.value == 2 && filetype != 'mp3')){
+    ElMessage({
+      type: 'warning',
+      message: '请上传所选资源类型的文件'
+    });
+    return false;
+  }
+  if(type.value == 3){
+    if( filetype == 'xls' || filetype == 'xlsx' || filetype == 'doc'|| filetype == 'docx'|| filetype == 'ppt'|| filetype == 'pptx'|| filetype == 'pdf' ){
+
+    }else {
+      ElMessage({
+        type: 'warning',
+        message: '请上传所选资源类型的文件'
+      });
+      return false;
+    }
+  }
+
+  //文件分片
+  const chunkList=createFileChunk(container.file.raw)
+  console.log('文件分了多少片:',chunkList.length)
+  //通过webworker计算出文件hash
+  container.fileMd5=await calculateMd5(chunkList)
+  console.log('文件hash1:',container.fileMd5)
+  // container.fileMd5=await getFileMD5(container.file.raw)
+  // console.log('文件hash2:',container.hash)
+
+  fileChunkList.value=[]
+  fileChunkList.value=chunkList.map(({file},index) => ({
+    fileMd5:container.fileMd5,
+    index,
+    chunkName: `${container.fileMd5}-${index}`,
+    chunk:file,
+    size:file.size,
+    // 如果已上传切片数组uploadedList中包含这个切片,则证明这个切片之前已经上传成功了,进度设为100。
+    percentage:0
+  }))
+
+  uploadChunks(fileChunkList)
+}
+
+//文件分片
+const createFileChunk = (file,size=chunkSize.value) => {
+  const chunkList=[]
+  let cur=0
+  while(cur<file.size){
+    chunkList.push({
+      file:file.slice(cur,cur+size),
+    })
+    cur+=size
+  }
+  return chunkList
+}
+
+//计算文件md5 方法1
+const calculateMd5 = (chunkList) => {
+  return new Promise((resolve) => {
+    container.worker=new Worker('/hash.js')
+    container.worker.postMessage({fileChunkList:chunkList})
+    container.worker.onmessage= (e) => {
+      const {percentage,hash} = e.data
+      hashPercentage.value=percentage.toFixed(2)
+      if(hash){
+        resolve(hash)
+      }
+    }
+  })
+}
+
+//计算文件md5 方法2
+const  getFileMD5 = (file) => {
+  return new Promise((resolve, reject) => {
+    const spark = new SparkMD5.ArrayBuffer()
+    const fileReader = new FileReader()
+    fileReader.onload = (e) => {
+      spark.append(e.target?.result)
+      resolve(spark.end())
+    }
+    fileReader.onerror = () => {
+      reject('')
+    }
+    fileReader.readAsArrayBuffer(file)
+  })
+}
+
+//计算上传进度
+const createProgressHandler = (item) => {
+  console.log('createProgresshandler -> item', item);
+  return (p) => {
+    if(item.percentage>=100){
+      item.percentage = 100
+    }else{
+      item.percentage=parseInt(String((p.loaded/p.total)*100))
+    }
+    // 确保进度百分比不会超过100%
+    if (item.percentage > 100) item.percentage = 100
+  }
+}
+
+
+
+//上传切片
+const uploadChunks= async(uploadedList) => {
+  const limit = pLimit(10); // 控制并发数为10
+  const requestList=uploadedList.value.map(({chunk,chunkName,index,fileMd5}) => {
+    const formdata=new FormData()
+    formdata.append('file',chunk)
+    formdata.append('chunkName',chunkName)
+    formdata.append('fileName',container.file.name)
+    formdata.append('fileMd5',fileMd5)
+    formdata.append('index',index)
+    return {formdata,index}
+  }).map(async ({formdata,index}) => {
+    return limit(() => doUploadChunk({data:formdata,onUploadProgress:createProgressHandler(fileChunkList.value[index])}))
+  })
+  await Promise.all(requestList)
+  console.log("数组:",fileChunkList)
+  if(uploadedCount.value>=fileChunkList.value.length){
+    mergeRequest()
+  }
+
+}
+
+
+const doUploadChunk = ({data,onUploadProgress}) => {
+  return new Promise((resolve) => {
+    uploadFileRequest(data,onUploadProgress).then((result) => {
+      let resData=result.data
+      if(result&&result.code==200){
+        uploadedCount.value=uploadedCount.value+1
+        fileChunkList.value[data.get('index')].percentage=100 //手动更新进度
+        console.log(uploadedCount.value,'result--------------')
+      }
+      resolve('done')
+    })
+  })
+}
+
+const mergeRequest = async() => {
+  if(container.file.name.lastIndexOf(".") === -1){
+    ElMessage.warning("请输入文件后缀名")
+    return
+  }
+  let data=await mergeFileRequest({fileMd5:container.fileMd5,fileName:container.file.name})
+  console.log(data,"mege------------222")
+  if(data && data.code==200){
+    const filetype = data.data.originName.split(".").pop();
+    if(filetype == 'mp4' || filetype == 'MP4'){
+      container.showVideo = true
+      await nextTick(() => {
+
+        console.log("myPlayer.value",myPlayer.value)
+        myPlayer.value.src(
+            {
+              src:"http://192.168.2.16:9000/trainexam/"+data.data.path,
+              type: 'application/x-mpegURL',
+            })
+        //   myPlayer.value.load()
+        myPlayer.value.play().catch((error) => {
+          console.error('Error playing video:', error);
+        });
+      })
+        //  myPlayer.value.pause()
+        //myPlayer.value.reset()
+    }
+    const file = {
+      resourceSize: data.data.size,
+      md5: data.data.md5,
+      resourcePath: data.data.path,
+      mediaType: filetype,
+      docPage: data.data.docPage,
+      resourceLength: data.data.resourceLength,
+      originName: data.data.originName
+    }
+    emit("getFile",file)
+
+
+
+    ElMessage.success("上传成功")
+  }else{
+    ElMessage.success("合并数据失败")
+  }
+}
+
+defineExpose({
+  dispose,
+  changeType,
+  open
+});
+</script>
+
+
+<style scoped>
+.greetings{
+  :deep(.video-js) {
+    width: 300px;
+    height: 200px;
+  }
+  :deep(.el-icon--close) {
+    display: none;
+  }
+}
+
+
+
+h1 {
+  font-weight: 500;
+  font-size: 2.6rem;
+  position: relative;
+  top: -10px;
+}
+
+h3 {
+  font-size: 1.2rem;
+}
+
+.greetings h1,
+.greetings h3 {
+  text-align: center;
+}
+
+@media (min-width: 1024px) {
+  .greetings h1,
+  .greetings h3 {
+    text-align: left;
+  }
+}
+</style>
diff --git a/src/views/onlineEducation/courseManage/components/courseManageDialog.vue b/src/views/onlineEducation/courseManage/components/courseManageDialog.vue
new file mode 100644
index 0000000..9d397ac
--- /dev/null
+++ b/src/views/onlineEducation/courseManage/components/courseManageDialog.vue
@@ -0,0 +1,320 @@
+<template>
+  <div class="notice">
+    <el-dialog
+        v-model="dialogVisible"
+        :title="title"
+        width="500px"
+        :before-close="handleClose"
+    >
+      <el-form :model="state.form" size="default" ref="busRef" :rules="state.formRules" label-width="150px" >
+        <el-form-item label="课程名称:" prop="name">
+          <el-input v-model.trim="state.form.name" placeholder="请输入课程名称"></el-input>
+        </el-form-item>
+        <el-form-item label="课程分类:" prop="categoryId" >
+<!--          <el-select clearable v-model="state.form.categoryId" style="width: 100%" placeholder="请选择课程分类" @change="changeType">-->
+<!--            <el-option v-for="item in state.classifyList" :key="item" :label="item.name" :value="item.id" />-->
+<!--          </el-select>-->
+          <el-cascader
+              ref="classifyRef"
+              v-model="state.form.categoryId"
+              :options="state.classifyList"
+              :props="state.props"
+              clearable
+              :show-all-levels="false"
+              @change="handleChange"
+          />
+        </el-form-item>
+        <el-form-item label="要求课时:" prop="period">
+          <el-input v-model="state.form.period" placeholder="请输入要求课时">
+            <template #append>分钟</template>
+          </el-input>
+        </el-form-item>
+        <el-form-item label="提交单位:" prop="companyName" >
+          <el-input v-model="state.form.companyName" disabled/>
+        </el-form-item>
+        <el-form-item label="封面:">
+          <el-upload accept="image/*" :action="state.uploadUrl" :headers="state.header" method="post" :on-success="(res, uploadFile)=>handleAvatarSuccess(res, uploadFile)" :on-exceed="showTip" :limit='state.imgLimit' v-model:file-list="state.imgList" list-type="picture-card" :before-upload="picSize" :on-remove="(file, uploadFiles)=>handleRemove(file, uploadFiles)" >
+            <el-icon><Plus /></el-icon>
+            <template #tip>
+              <div class="el-upload__tip">上传jpg/png图片尺寸小于5M,最多可上传1张</div>
+            </template>
+          </el-upload>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <span class="dialog-footer">
+            <el-button @click="handleClose" size="default">取 消</el-button>
+            <el-button type="primary"  @click="onSubmit" size="default" v-preReClick>确认</el-button>
+        </span>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+<script setup>
+import {reactive, ref, toRefs} from 'vue'
+import Editor from "@/components/Editor/index.vue";
+import {ElMessage} from "element-plus";
+import {addNotice} from "@/api/backManage/notice";
+import {addDict, editDict, getDictDetail} from "@/api/backManage/evaluate";
+import {addCompany, checkName, distributeCompany, editCompany} from "@/api/onlineEducation/company";
+import {verifyPhone} from "@/utils/validate";
+import {
+  addClassification,
+  checkClassName,
+  editClassification,
+  getClassification
+} from "@/api/onlineEducation/courseClass";
+import {addCourse, checkCourseName, editCourse, getCourseById} from "@/api/onlineEducation/courseManage";
+import {getToken} from "@/utils/auth";
+import {delPic, getBannerById} from "@/api/onlineEducation/banner";
+import Cookies from "js-cookie";
+
+const dialogVisible = ref(false);
+const title = ref("");
+const busRef = ref();
+const length = ref()
+const emit = defineEmits(["getList"]);
+const startUsername = ref('');
+const classifyRef = ref(null)
+
+const validateName = (rule, value, callback)=>{
+  if(value === ''){
+    callback(new Error('请输入课程名称'))
+  }else if(title.value === '编辑' && value === startUsername.value){
+    callback()
+  }else{
+    let param = {}
+    if(title.value === '新增') {
+      param = {
+        name:value
+      }
+    }else if(title.value === '编辑'){
+      param = {
+        name:value,
+        id: state.form.id
+      }
+    }
+    checkCourseName(param).then((res)=>{
+      if(res.data == false){
+        callback(new Error('课程名称已被占用,请更换其他名称'))
+      }else{
+        callback()
+      }
+    })
+  }
+}
+const state = reactive({
+  form: {
+    id: '',
+    name: '',
+    categoryId: null,
+    period: null,
+    logo: '',
+    companyId: null
+  },
+  formRules: {
+    name: [{required: true, trigger: "blur", validator: validateName}],
+    categoryId: [{required: true, message: '请选择课程分类', trigger: 'blur'}],
+    period: [{required: true, message: '请输入要求课时', trigger: 'blur'}],
+  },
+  classifyList: [],
+  uploadUrl: import.meta.env.VITE_APP_BASE_API + '/system/common/uploadFile',
+  header: {
+    Authorization: getToken()
+  },
+  imgLimit: 1,
+  imgList: [],
+  isAdmin: false,
+  props: {
+    checkStrictly: true,
+  }
+})
+
+const openDialog = async (type, value) => {
+  await getClassifyList();
+  const userInfo = JSON.parse(Cookies.get('userInfo'))
+  console.log("userInfo",userInfo)
+  if(userInfo.userType === 0){
+    state.isAdmin = true;
+    state.form.companyName = '公开课'
+    state.form.companyId = null
+  }else {
+    state.isAdmin = false;
+    state.form.companyName = userInfo.companyName
+    state.form.companyId = userInfo.companyId
+  }
+  title.value = type === 'addFirst' || type === 'add' ? '新增' : type ==='edit' ? '编辑' : '' ;
+  if(type === 'edit') {
+    const res = await getCourseById(value.id);
+    if(res.code === 200){
+      state.form = res.data
+      console.log("11",res.data)
+      if(res.data.logo) {
+        const obj = {
+          url: import.meta.env.VITE_APP_BASE_API + "/" +  res.data.logo,
+          name: ''
+        }
+        state.imgList = [obj]
+      }
+    }else{
+      ElMessage.warning(res.message)
+    }
+    startUsername.value = value.username;
+  }else if(type === 'add' && value ){
+    state.form.parentId = value.id
+  }
+  dialogVisible.value = true;
+}
+const  getClassifyList = async () => {
+  const res = await getClassification();
+  if(res.code === 200){
+    state.classifyList = recursion(res.data)
+  }else{
+    ElMessage.warning(res.message)
+  }
+}
+const recursion = (data) => {
+  let tmp = []
+  for (let i = 0; i < data.length; i++) {
+    let item = data[i]
+    // children为空
+    if (item.children&& item.children.length==0) {
+      tmp.push({
+        value: item.id,
+        label: item.name
+      })
+      // 有children
+    } else {
+      tmp.push({
+        value: item.id,
+        label: item.name,
+        children:recursion(item.children)
+      })
+    }
+  }
+  return tmp;
+}
+const handleAvatarSuccess = (res, uploadFile) => {
+  if(res.code == 200){
+    state.form.logo = res.data.path
+  }else{
+    state.imgList = []
+    ElMessage({
+      type: 'warning',
+      message: '文件上传失败'
+    })
+  }
+}
+const handleChange = ()=> {
+  console.log("label====",classifyRef.value.getCheckedNodes()[0].value)
+  state.form.categoryId = classifyRef.value.getCheckedNodes()[0].value
+  // 我这里只是打印了一下label的值哦,需要赋值的话自己去赋值哦
+}
+
+
+const showTip =()=>{
+  ElMessage({
+    type: 'warning',
+    message: '超出文件上传数量'
+  });
+}
+const picSize = async (rawFile) => {
+  if(rawFile.size / 1024 / 1024 > 5){
+    ElMessage({
+      type: 'warning',
+      message: '文件大小不能超过5M'
+    });
+    return false
+  }
+};
+const handleRemove = async (file, uploadFiles) => {
+  let path = state.form.logo;
+  await delPic({path: path}).then(res => {
+    if(res.code == 200){
+      // ElMessage({
+      //   type: 'success',
+      //   message: '文件已删除'
+      // })
+      state.form.logo = ''
+    }else{
+      ElMessage({
+        type: 'warning',
+        message: res.message
+      })
+    }
+  }).catch(() => {
+    state.form.imgUrl = ''
+  });
+}
+const onSubmit = async () => {
+  const valid = await busRef.value.validate();
+  if(valid){
+    if(title.value === '新增'){
+      const {id, ...data} = JSON.parse(JSON.stringify(state.form))
+      const res = await addCourse(data)
+      if(res.code === 200){
+        ElMessage({
+          type: 'success',
+          message: '新增成功'
+        });
+      }else{
+        ElMessage.warning(res.message)
+      }
+      emit("getList")
+      busRef.value.clearValidate();
+      reset();
+      dialogVisible.value = false;
+    }else if(title.value === '编辑'){
+      const {...data} = JSON.parse(JSON.stringify(state.form))
+      const res = await editCourse(data)
+      if(res.code === 200){
+        ElMessage({
+          type: 'success',
+          message: '编辑成功'
+        });
+      }else{
+        ElMessage.warning(res.message)
+      }
+      emit("getList")
+      busRef.value.clearValidate();
+      reset();
+      dialogVisible.value = false;
+    }
+  }
+}
+
+const handleClose = () => {
+  busRef.value.clearValidate();
+  reset();
+  dialogVisible.value = false;
+  emit("getList")
+
+}
+const reset = () => {
+  state.form = {
+    id: '',
+    name: '',
+    categoryId: null,
+    period: null,
+    logo: '',
+    companyId: null
+  }
+}
+defineExpose({
+  openDialog
+});
+
+</script>
+
+<style scoped lang="scss">
+.notice{
+  :deep(.el-form .el-form-item__label) {
+    font-size: 15px;
+  }
+  .file {
+    display: flex;
+    flex-direction: column;
+    align-items: flex-start;
+  }
+}
+</style>
diff --git a/src/views/onlineEducation/courseManage/courseChapters/components/chapterDialog.vue b/src/views/onlineEducation/courseManage/courseChapters/components/chapterDialog.vue
new file mode 100644
index 0000000..3bc2552
--- /dev/null
+++ b/src/views/onlineEducation/courseManage/courseChapters/components/chapterDialog.vue
@@ -0,0 +1,249 @@
+<template>
+  <div class="notice">
+    <el-dialog
+        v-model="dialogVisible"
+        :title="title"
+        width="550px"
+        :before-close="handleClose"
+    >
+      <el-form :model="state.form" size="default" ref="busRef" :rules="state.formRules" label-width="100px" >
+        <el-form-item label="章名称:" prop="name">
+          <el-input v-model.trim="state.form.name" maxlength="100" show-word-limit :disabled="!state.isFirst"></el-input>
+        </el-form-item>
+        <el-form-item label="节名称:" prop="chapter.name" v-if="!state.isFirst">
+          <el-input v-model.trim="state.chapter.name"></el-input>
+        </el-form-item>
+        <el-form-item label="资源:" v-if="!state.isFirst">
+          <div style="display: flex;align-items: center; width: 100%;justify-content: space-between;">
+            <el-input v-model.trim="state.chapter.resourceName" disabled style="width: 80%"></el-input>
+            <el-button type="primary" style="margin-left: 20px" plain size="default" @click="openResource">选择资源</el-button>
+          </div>
+
+        </el-form-item>
+
+        <el-form-item label="排序:" prop="sort" >
+          <el-input-number v-model="state.form.sort" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <span class="dialog-footer">
+            <el-button @click="handleClose" size="default">取 消</el-button>
+            <el-button type="primary"  @click="onSubmit" size="default" v-preReClick>确认</el-button>
+        </span>
+      </template>
+    </el-dialog>
+    <courseDialog ref="courseRef" @choose-res="choosedResource" ></courseDialog>
+  </div>
+</template>
+<script setup>
+import {reactive, ref, toRefs} from 'vue'
+import Editor from "@/components/Editor/index.vue";
+import {ElMessage} from "element-plus";
+import {addNotice} from "@/api/backManage/notice";
+import {addDict, editDict, getDictDetail} from "@/api/backManage/evaluate";
+import {addCompany, checkName, distributeCompany, editCompany} from "@/api/onlineEducation/company";
+import {verifyPhone} from "@/utils/validate";
+import {addClassification, checkClassName, editClassification} from "@/api/onlineEducation/courseClass";
+import courseDialog from './chooseResource.vue'
+import {
+  addChapter,
+  addChapterPeriod,
+  checkChapterName,
+  editChapter,
+  editChapterPeriod
+} from "@/api/onlineEducation/chapters";
+
+const dialogVisible = ref(false);
+const title = ref("");
+const busRef = ref();
+const courseRef = ref()
+const length = ref()
+const emit = defineEmits(["getList"]);
+const startUsername = ref('');
+const startUsernamePeriod = ref('');
+
+
+const validateName = (rule, value, callback)=>{
+  if(value === ''){
+    callback(new Error('请输入章名称'))
+  }else if(title.value === '编辑' && value === startUsername.value || title.value === '新增' && value === startUsername.value){
+    callback()
+  }else{
+    let param = {}
+    if(title.value === '新增') {
+      param = {
+        name:value,
+        courseId: state.form.courseId
+      }
+    }else if(title.value === '编辑'){
+      param = {
+        name:value,
+        id: state.form.id,
+        courseId: state.form.courseId
+      }
+    }
+    checkChapterName(param).then((res)=>{
+      if(res.data == false){
+        callback(new Error('名称已被占用,请更换其他名称'))
+      }else{
+        callback()
+      }
+    })
+  }
+}
+const state = reactive({
+  form: {
+    id: '',
+    name: '',
+    sort: 0,
+    courseId: null,
+    chapterPeriods: []
+  },
+  formRules:{
+    name: [{ required: true, trigger: "blur", validator: validateName }],
+  },
+  isFirst: true,
+  chapter: {}
+})
+
+const openDialog = async (type, value) => {
+  length.value = value.listLength
+  state.form.courseId = value.courseId
+  title.value = type === 'addFirst' || type === 'add' ? '新增' : type ==='edit' ? '编辑' : '' ;
+  if(type === 'edit') {
+    if(!value.chapterId){
+      state.isFirst = true;
+      startUsername.value = value.name;
+      state.form = value;
+      state.form.sort = value.sort;
+    }else {
+      state.isFirst = false;
+      startUsernamePeriod.value = value.name;
+      state.chapter = value;
+      state.form.name = value.capterName;
+      startUsername.value = value.capterName;
+      state.chapter.resourceName = value.resource.name
+    }
+
+  }else if(type === 'add' && value ){
+    startUsername.value = value.name;
+    state.chapter.chapterId = value.id;
+    state.chapter.courseId = value.courseId;
+    state.form.name = value.name
+    state.isFirst = false;
+  }else {
+    state.isFirst = true;
+  }
+  dialogVisible.value = true;
+}
+const choosedResource = (val) => {
+  state.chapter.resourceName = val.name
+  state.chapter.resourceId = val.id
+}
+
+const onSubmit = async () => {
+  const valid = await busRef.value.validate();
+  if(valid){
+    if(title.value === '新增'){
+      if(state.chapter.chapterId){
+        const {id, ...data} = JSON.parse(JSON.stringify(state.chapter))
+        const res = await addChapterPeriod(data)
+        if(res.code === 200){
+          ElMessage({
+            type: 'success',
+            message: '新增成功'
+          });
+        }else{
+          ElMessage.warning(res.message)
+        }
+      }else{
+        const {id, ...data} = JSON.parse(JSON.stringify(state.form))
+        const res = await addChapter(data)
+        if(res.code === 200){
+          ElMessage({
+            type: 'success',
+            message: '新增成功'
+          });
+        }else{
+          ElMessage.warning(res.message)
+        }
+      }
+
+      emit("getList")
+      busRef.value.clearValidate();
+      reset();
+      dialogVisible.value = false;
+    }else if(title.value === '编辑'){
+      if(state.chapter.chapterId){
+        const {...data} = JSON.parse(JSON.stringify(state.chapter))
+        const res = await editChapterPeriod(data)
+        if(res.code === 200){
+          ElMessage({
+            type: 'success',
+            message: '编辑成功'
+          });
+        }else{
+          ElMessage.warning(res.message)
+        }
+        emit("getList")
+        busRef.value.clearValidate();
+        reset();
+        dialogVisible.value = false;
+      }else {
+        const {...data} = JSON.parse(JSON.stringify(state.form))
+        const res = await editChapter(data)
+        if(res.code === 200){
+          ElMessage({
+            type: 'success',
+            message: '编辑成功'
+          });
+        }else{
+          ElMessage.warning(res.message)
+        }
+        emit("getList")
+        busRef.value.clearValidate();
+        reset();
+        dialogVisible.value = false;
+      }
+
+    }
+  }
+}
+
+const handleClose = () => {
+  busRef.value.clearValidate();
+  reset();
+  dialogVisible.value = false;
+  emit("getList")
+}
+const openResource = () => {
+  courseRef.value.openDialog()
+}
+const reset = () => {
+  state.form = {
+    id: '',
+    name: '',
+    sort: 0,
+    courseId: null,
+    chapterPeriods: []
+  }
+  state.chapter={}
+}
+defineExpose({
+  openDialog
+});
+
+</script>
+
+<style scoped lang="scss">
+.notice{
+  :deep(.el-form .el-form-item__label) {
+    font-size: 15px;
+  }
+  .file {
+    display: flex;
+    flex-direction: column;
+    align-items: flex-start;
+  }
+}
+</style>
diff --git a/src/views/onlineEducation/courseManage/courseChapters/components/chooseResource.vue b/src/views/onlineEducation/courseManage/courseChapters/components/chooseResource.vue
new file mode 100644
index 0000000..b67ed2c
--- /dev/null
+++ b/src/views/onlineEducation/courseManage/courseChapters/components/chooseResource.vue
@@ -0,0 +1,123 @@
+<template>
+  <div class="app-container">
+    <el-dialog
+        v-model="dialogVisible"
+        title="选择资源"
+        width="600px"
+        :before-close="handleClose"
+    >
+    <div style="margin-bottom: 10px">
+      <el-form>
+        <el-form-item label="资源名称">
+          <el-input style="width: 20%" v-model="data.queryParams.name "></el-input>
+          <el-button type="primary" style="margin-left: 30px" @click="getList">查询</el-button>
+          <el-button plain @click="reset">重置</el-button>
+        </el-form-item>
+
+      </el-form>
+    </div>
+    <!-- 表格数据 -->
+    <el-table v-loading="loading" :data="dataList" :border="true">
+      <el-table-column label="序号" type="index" align="center" width="80" />
+      <el-table-column label="资源名称" prop="name" align="center"  />
+      <el-table-column label="资源类型" prop="resourceType" align="center"  >
+        <template #default="scope">
+          <span>{{scope.row.resourceType == 1 ? '视频':scope.row.resourceType == 2 ? '音频':'文档'}}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width" >
+        <template #default="scope">
+          <el-button link type="primary" @click="choose(scope.row)">选择</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <pagination
+        v-show="total > 0"
+        :total="total"
+        v-model:page="queryParams.pageNum"
+        v-model:limit="queryParams.pageSize"
+        @pagination="getList"
+    />
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import {getCurrentInstance, onMounted, onUnmounted, reactive, ref, toRefs} from "vue";
+import {ElMessage, ElMessageBox} from "element-plus";
+import {checkResourceName, delResource, getResource} from "@/api/onlineEducation/courseResource";
+import {checkName} from "@/api/onlineEducation/company";
+const { proxy } = getCurrentInstance();
+const loading = ref(false);
+const emit = defineEmits(["chooseRes"]);
+const dialogRef = ref();
+const data = reactive({
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+    name: ''
+  },
+  total: 0,
+  dataList: []
+});
+
+const dialogVisible = ref(false);
+const { queryParams, total, dataList } = toRefs(data);
+
+onMounted(()=>{
+
+})
+
+onUnmounted(()=>{
+
+})
+
+const openDialog = async () => {
+  await getList()
+  dialogVisible.value = true;
+}
+
+const handleClose = () => {
+  dialogVisible.value = false;
+}
+const getList = async () => {
+  loading.value = true
+  const res = await getResource(data.queryParams)
+  if(res.code == 200){
+    data.dataList = res.data.list.map(item => {
+      return{
+        ...item,
+        sizeMB: Number((item.resourceSize /1024 /1024).toFixed(2))+'MB'
+      }
+    })
+    console.log("ddd",data.dataList)
+    data.total = res.data.total
+  }else{
+    ElMessage.warning(res.message)
+  }
+  loading.value = false
+}
+
+const choose = (value) => {
+  console.log("co",value)
+  emit('chooseRes',value)
+  dialogVisible.value = false
+  // dialogRef.value.openDialog(type, value);
+}
+defineExpose({
+  openDialog
+});
+
+
+/** 重置新增的表单以及其他数据  */
+function reset() {
+  data.queryParams = {
+    pageNum: 1,
+        pageSize: 10,
+        name: ''
+  }
+  getList()
+}
+
+</script>
diff --git a/src/views/onlineEducation/courseManage/courseChapters/index.vue b/src/views/onlineEducation/courseManage/courseChapters/index.vue
new file mode 100644
index 0000000..1ce31f6
--- /dev/null
+++ b/src/views/onlineEducation/courseManage/courseChapters/index.vue
@@ -0,0 +1,124 @@
+<template>
+  <div class="app-container">
+    <div style="margin-bottom: 10px">
+      <el-button type="success" plain @click="openDialog('addFirst',{courseId: data.courseId})">章添加</el-button>
+
+    </div>
+    <!-- 表格数据 -->
+    <el-table v-loading="loading" :data="dataList" :border="true" row-key="id" :tree-props="{ children: 'chapterPeriods' }">
+      <el-table-column label="序号" type="index" align="center" width="80" />
+      <el-table-column label="章节名称" >
+        <template #default="scope">
+          <span>{{scope.row.name}}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="排序" prop="sort" align="center" width="80" />
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="250" >
+        <template #default="scope">
+          <el-button type="success" plain @click="openDialog('add',scope.row)" v-if="!scope.row.chapterId">节添加</el-button>
+          <el-button type="primary" plain @click="openDialog('edit',scope.row)">编辑</el-button>
+          <el-button type="danger" plain @click="handleDelete(scope.row)">删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <chapters-dialog ref="areaRef" @getList="getList"></chapters-dialog>
+  </div>
+</template>
+
+<script setup>
+import {getCurrentInstance, onMounted, reactive, ref, toRefs} from "vue";
+import {ElMessage, ElMessageBox} from "element-plus";
+import chaptersDialog from "./components/chapterDialog.vue"
+import {delArea, getArea} from "@/api/backManage/area";
+import {getDictList} from "@/api/backManage/evaluate";
+import {delMonitor} from "@/api/sysUsers";
+import {useRoute} from 'vue-router'
+import {delClassification, getClassification} from "@/api/onlineEducation/courseClass";
+import {delChapter, delPeriod, getChapters} from "@/api/onlineEducation/chapters";
+const { proxy } = getCurrentInstance();
+const route = useRoute()
+const loading = ref(false);
+const areaRef = ref();
+const cityList = ref([])
+const data = reactive({
+  queryParams: {
+    name: '',
+  },
+  total: 0,
+  dataList: [
+  ],
+  courseId: ''
+});
+
+const { queryParams, total, dataList } = toRefs(data);
+
+//页面加载
+onMounted(() => {
+
+  data.courseId = route.query.courseId
+  console.log("rou",data.courseId)
+  getList();
+});
+const getList = async () => {
+  loading.value = true;
+  const param = {
+    courseId: data.courseId
+  }
+  const res = await getChapters(param);
+  if(res.code === 200){
+    dataList.value = res.data
+  }else{
+    ElMessage.warning(res.message)
+  }
+  loading.value = false;
+  console.log('dataList.value',dataList.value)
+}
+
+const openDialog = (type, value) => {
+  dataList.value.forEach(item => {
+    if(item.id == value.chapterId){
+      value.capterName = item.name
+    }
+  })
+  areaRef.value.openDialog(type, value);
+}
+
+/** 重置新增的表单以及其他数据  */
+function reset() {
+  data.queryParams.name = '';
+  data.queryParams.pageNum = 1;
+  getList();
+}
+const handleDelete = (val) => {
+  ElMessageBox.confirm(
+      '确定删除此条数据?',
+      '提示',
+      {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning',
+      })
+      .then( async() => {
+        if(!val.chapterId){
+          const res = await delChapter(val.id)
+          if(res.code == 200){
+            ElMessage.success('数据删除成功')
+            await getList()
+          }else{
+            ElMessage.warning(res.message)
+          }
+        }else {
+          const res = await delPeriod(val.id)
+          if(res.code == 200){
+            ElMessage.success('数据删除成功')
+            await getList()
+          }else{
+            ElMessage.warning(res.message)
+          }
+        }
+
+      })
+}
+
+
+</script>
diff --git a/src/views/onlineEducation/courseManage/courseResource/componets/resourceDialog.vue b/src/views/onlineEducation/courseManage/courseResource/componets/resourceDialog.vue
new file mode 100644
index 0000000..d1cbdd4
--- /dev/null
+++ b/src/views/onlineEducation/courseManage/courseResource/componets/resourceDialog.vue
@@ -0,0 +1,345 @@
+<template>
+  <div class="notice">
+    <el-dialog
+        v-model="dialogVisible"
+        :title="title"
+        width="550px"
+        :before-close="handleClose"
+    >
+      <el-form :model="state.form" size="default" ref="busRef" :rules="state.formRules" label-width="130px" >
+        <el-form-item label="资源名称:" prop="name" >
+          <el-input v-model.trim="state.form.name" placeholder="请输入资源名称"></el-input>
+        </el-form-item>
+        <el-form-item label="资源类型:" prop="resourceType" >
+          <el-select
+              @change="changeType"
+              v-model="state.form.resourceType"
+              placeholder="请选择资源类型"
+              size="default"
+              style="width: 100%"
+
+          >
+            <el-option
+                v-for="item in state.typeList"
+                :key="item.id"
+                :label="item.name"
+                :value="item.id"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item  label="资源时长(秒):" prop="resourceLength"  v-if="(state.form.resourceType == 1 || state.form.resourceType == 2)&& state.form.resourceLength">
+          <el-input
+              :disabled="state.form.resourceLength!=0"
+              v-model="state.form.resourceLength"
+              style="width: 100%"
+              placeholder="请输入资源时长(秒)"
+          >
+            <template #append>秒</template>
+          </el-input>
+        </el-form-item>
+        <el-form-item  label="文档页数:" prop="docPage"  v-if="state.form.resourceType == 3 && state.form.docPage ">
+          <el-input
+              :disabled="state.form.docPage!=0"
+              v-model="state.form.docPage"
+              style="width: 100%"
+              placeholder="请输入文档页数:"
+          >
+            <template #append>页</template>
+          </el-input>
+        </el-form-item>
+
+        <el-form-item  label="资源上传:">
+          <file-upload ref="fileRef" :responseType="state.form.resourceType" @getFile="getFile"></file-upload>
+        </el-form-item>
+
+      </el-form>
+      <template #footer>
+        <span class="dialog-footer">
+            <el-button @click="handleClose" size="default">取 消</el-button>
+            <el-button type="primary"  @click="onSubmit" size="default" v-preReClick>确认</el-button>
+        </span>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+<script setup>
+import {nextTick, reactive, ref, toRefs} from 'vue'
+import Editor from "@/components/Editor/index.vue";
+import {ElMessage} from "element-plus";
+import {addNotice} from "@/api/backManage/notice";
+import {addDict, editDict, getDictDetail} from "@/api/backManage/evaluate";
+import {addCompany, checkName, distributeCompany, editCompany} from "@/api/onlineEducation/company";
+import {verifyPhone} from "@/utils/validate";
+import {addBanner, delPic, editBanner, getBannerById} from "@/api/onlineEducation/banner";
+import {getToken} from "@/utils/auth";
+import {getUserById} from "@/api/onlineEducation/user";
+import {addResource, checkResourceName, editResource, getResourceById} from "@/api/onlineEducation/courseResource";
+import fileUpload from '@/views/components/upload.vue'
+import Cookies from "js-cookie";
+
+const dialogVisible = ref(false);
+const title = ref("");
+const fileRef = ref();
+const busRef = ref();
+const length = ref()
+const emit = defineEmits(["getList"]);
+const startUsername = ref('');
+const startPhone = ref('');
+
+const validateName = (rule, value, callback)=>{
+  if(value === ''){
+    callback(new Error('请输入资源名称'))
+  }else if(title.value === '编辑' && value === startUsername.value){
+    callback()
+  }else{
+    let param = {}
+    if(title.value === '新增') {
+      param = {
+        name:value,
+      }
+    }else if(title.value === '编辑'){
+      param = {
+        name:value,
+        id: state.form.id
+      }
+    }
+    checkResourceName(param).then((res)=>{
+      if(res.data == false){
+        callback(new Error('资源名称已被占用,请更换其他名称'))
+      }else{
+        callback()
+      }
+    })
+  }
+}
+
+const state = reactive({
+  form: {
+    id: '',
+    name: '',
+    mediaType: '',
+    resourceType: null,
+    companyId: null,
+    resourcePath:'',
+    md5: '',
+    resourceSize: null,
+    resourceLength: '',
+    docPage: null,
+    originName: ''
+  },
+  formRules:{
+    name: [{ required: true, trigger: "blur", validator: validateName }],
+    resourceType:[{ required: true, message: '请选择资源类型', trigger: 'blur' }],
+    resourceLength: [{ required: true, trigger: "blur", message: '请输入资源时长(秒)' }],
+  },
+  uploadUrl: import.meta.env.VITE_APP_BASE_API + '/system/common/uploadFile',
+  header: {
+    Authorization:getToken()
+  },
+  imgLimit: 1,
+  imgList: [],
+  typeList: [
+    {
+      id: 1,
+      name: '视频'
+    },
+    {
+      id: 2,
+      name: '音频'
+    },
+    {
+      id: 3,
+      name: '文档'
+    },
+  ]
+})
+const  handleChange = (file, fileLists) => {
+  state.form.file = file.raw;
+  console.log(file);
+  // 这里把数组置空是为了每次只能上传一个文件,以防报错
+  state.imgList = [];
+  state.form.mediaType = file.raw.type
+}
+const handleAvatarSuccess = (response, file, fileList) => {
+  if(response.code == 200){
+    if(file && file.raw){
+      state.form.mediaType = file.raw.type
+    }
+    state.form.file = file
+  }else{
+    state.imgList = []
+    ElMessage({
+      type: 'warning',
+      message: '文件上传失败'
+    })
+  }
+}
+const showTip =()=>{
+  ElMessage({
+    type: 'warning',
+    message: '超出文件上传数量'
+  });
+}
+const picSize = async (rawFile) => {
+  if(rawFile.size / 1024 / 1024 > 5){
+    ElMessage({
+      type: 'warning',
+      message: '文件大小不能超过5M'
+    });
+    return false
+  }
+};
+const handleRemove = async (file, uploadFiles) => {
+  let path = state.form.imgUrl;
+  await delPic({path: path}).then(res => {
+    if(res.code == 200){
+      // ElMessage({
+      //   type: 'success',
+      //   message: '文件已删除'
+      // })
+      state.form.imgUrl = ''
+    }else{
+      ElMessage({
+        type: 'warning',
+        message: res.message
+      })
+    }
+  }).catch(() => {
+    state.form.imgUrl = ''
+  });
+}
+
+const openDialog = async (type, value) => {
+  length.value = value.listLength
+  title.value = type === 'add' ? '新增' : type ==='edit' ? '编辑' : '' ;
+  if(type === 'edit') {
+    const res = await getResourceById(value.id);
+    if(res.code === 200){
+      state.form = res.data
+    }else{
+      ElMessage.warning(res.message)
+    }
+  }
+  dialogVisible.value = true;
+  if(type === 'edit') {
+    await nextTick(() => {
+      fileRef.value.open(state.form);
+    })
+  }
+}
+const getFile = (val) => {
+
+  if(val.md5 != ''){
+    state.form.md5 = val.md5
+    state.form.resourcePath = val.resourcePath
+    state.form.mediaType = val.mediaType
+    state.form.resourceSize = val.resourceSize
+    state.form.docPage = val.docPage
+    state.form.resourceLength = val.resourceLength
+    state.form.originName = val.originName
+  } else if( val.md5 == ''){
+    state.form.resourceType = ''
+
+  } else {
+      ElMessage({
+        type: 'warning',
+        message: '请上传题库资源'
+      });
+  }
+}
+
+const changeType = (val) => {
+  state.form.md5 = ''
+  state.form.resourcePath = ''
+  state.form.mediaType =''
+  state.form.resourceSize = null
+  state.form.docPage = null
+  state.form.resourceLength = null
+  state.form.originName = ''
+  fileRef.value.changeType(val);
+}
+const onSubmit = async () => {
+  const valid = await busRef.value.validate();
+  if(valid){
+    if(title.value === '新增'){
+      const {id,...data} = JSON.parse(JSON.stringify(state.form))
+      const res = await addResource(data)
+      if(res.code === 200){
+        ElMessage({
+          type: 'success',
+          message: '新增成功'
+        });
+      }else{
+        ElMessage.warning(res.message)
+      }
+      dialogVisible.value = false;
+      emit("getList")
+      busRef.value.clearValidate();
+      fileRef.value.dispose();
+      reset();
+
+
+    }else if(title.value === '编辑'){
+      const {...data} = JSON.parse(JSON.stringify(state.form))
+      const res = await editResource(data)
+      if(res.code === 200){
+        ElMessage({
+          type: 'success',
+          message: '编辑成功'
+        });
+      }else{
+        ElMessage.warning(res.message)
+      }
+      dialogVisible.value = false;
+      emit("getList")
+      busRef.value.clearValidate();
+      fileRef.value.dispose();
+      reset();
+
+
+    }
+  }
+}
+
+const handleClose = () => {
+  dialogVisible.value = false;
+  fileRef.value.dispose();
+  busRef.value.clearValidate();
+  reset();
+
+  emit("getList")
+
+}
+const reset = () => {
+  state.form = {
+    id: '',
+    name: '',
+    mediaType: '',
+    resourceType: null,
+    companyId: null,
+    resourcePath:'',
+    md5: '',
+    resourceSize: null,
+    resourceLength: '',
+    docPage: null,
+    originName: ''
+  }
+}
+defineExpose({
+  openDialog
+});
+
+</script>
+
+<style scoped lang="scss">
+.notice{
+  :deep(.el-form .el-form-item__label) {
+    font-size: 15px;
+  }
+  .file {
+    display: flex;
+    flex-direction: column;
+    align-items: flex-start;
+  }
+}
+</style>
diff --git a/src/views/onlineEducation/courseManage/courseResource/index.vue b/src/views/onlineEducation/courseManage/courseResource/index.vue
new file mode 100644
index 0000000..e0b3274
--- /dev/null
+++ b/src/views/onlineEducation/courseManage/courseResource/index.vue
@@ -0,0 +1,115 @@
+<template>
+  <div class="app-container">
+    <div style="margin-bottom: 10px">
+      <el-button
+          type="primary"
+          plain
+          icon="Plus"
+          @click="openDialog('add',{})"
+      >新增</el-button>
+    </div>
+    <!-- 表格数据 -->
+    <el-table v-loading="loading" :data="dataList" :border="true">
+      <el-table-column label="序号" type="index" align="center" width="80" />
+      <el-table-column label="资源名称" prop="name" align="center"  />
+      <el-table-column label="资源大小" prop="sizeMB" align="center"  >
+      </el-table-column>
+      <el-table-column label="资源类型" prop="resourceType" align="center"  >
+        <template #default="scope">
+          <span>{{scope.row.resourceType == 1 ? '视频':scope.row.resourceType == 2 ? '音频':'文档'}}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width" >
+        <template #default="scope">
+          <el-button link type="primary" @click="openDialog('edit',scope.row)">编辑</el-button>
+          <el-button link type="danger" @click="handleDelete(scope.row)">删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <pagination
+        v-show="total > 0"
+        :total="total"
+        v-model:page="queryParams.pageNum"
+        v-model:limit="queryParams.pageSize"
+        @pagination="getList"
+    />
+<resource-dialog ref="dialogRef" @getList="getList"></resource-dialog>
+  </div>
+</template>
+
+<script setup>
+import {getCurrentInstance, onMounted, onUnmounted, reactive, ref, toRefs} from "vue";
+import {ElMessage, ElMessageBox} from "element-plus";
+import resourceDialog from './componets/resourceDialog.vue'
+import {checkResourceName, delResource, getResource} from "@/api/onlineEducation/courseResource";
+import {checkName} from "@/api/onlineEducation/company";
+const { proxy } = getCurrentInstance();
+const loading = ref(false);
+const dialogRef = ref();
+const data = reactive({
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+  },
+  total: 0,
+  dataList: []
+});
+
+const { queryParams, total, dataList } = toRefs(data);
+
+onMounted(()=>{
+  getList()
+})
+
+onUnmounted(()=>{
+
+})
+
+const getList = async () => {
+  loading.value = true
+  const res = await getResource(data.queryParams)
+  if(res.code == 200){
+    data.dataList = res.data.list.map(item => {
+      return{
+        ...item,
+        sizeMB: Number((item.resourceSize /1024 /1024).toFixed(2))+'MB'
+      }
+    })
+    console.log("ddd",data.dataList)
+    data.total = res.data.total
+  }else{
+    ElMessage.warning(res.message)
+  }
+  loading.value = false
+}
+
+const openDialog = (type, value) => {
+  dialogRef.value.openDialog(type, value);
+}
+
+/** 重置新增的表单以及其他数据  */
+function reset() {
+  proxy.resetForm("roleRef");
+}
+const handleDelete = (val) => {
+  ElMessageBox.confirm(
+      '确定删除此条数据?',
+      '提示',
+      {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning',
+      })
+      .then( async() => {
+        const res = await delResource(val.id)
+        if(res.code == 200){
+          ElMessage.success('数据删除成功')
+          await getList()
+        }else{
+          ElMessage.warning(res.message)
+        }
+      })
+}
+
+</script>
diff --git a/src/views/onlineEducation/courseManage/index.vue b/src/views/onlineEducation/courseManage/index.vue
index 14b32de..08dc044 100644
--- a/src/views/onlineEducation/courseManage/index.vue
+++ b/src/views/onlineEducation/courseManage/index.vue
@@ -1,12 +1,263 @@
 <template>
-<div>课程管理</div>
+  <div class="app-container">
+    <div style="margin-bottom: 10px">
+      <el-button
+          type="primary"
+          plain
+          icon="Plus"
+          @click="openDialog('add',{})"
+      >新增</el-button>
+    </div>
+    <!-- 表格数据 -->
+    <el-table v-loading="loading" :data="dataList" :border="true">
+      <el-table-column label="序号" type="index" align="center" width="80" />
+      <el-table-column label="封面" prop="logo" align="center"  >
+        <template #default="scope">
+          <div class="demo-image__preview" v-if="scope.row.logo && scope.row.logo.length>0">
+            <el-image
+                style="width: 100px; height: 100px"
+                :src= "scope.row.logo[0]"
+                :zoom-rate="1.2"
+                :max-scale="7"
+                :min-scale="0.2"
+                :preview-src-list="scope.row.logo"
+                :initial-index="0"
+                fit="cover"
+                :preview-teleported=true
+            />
+          </div>
+        </template>
+      </el-table-column>
+      <el-table-column label="课程名称" prop="name" align="center"  />
+      <el-table-column label="课程分类" prop="categoryName" align="center" />
+      <el-table-column label="要求课时" prop="period" align="center" />
+      <el-table-column label="提交单位" prop="companyName" align="center" />
+      <el-table-column label="审核状态" prop="state" align="center" >
+        <template #default="scope">
+          <span>{{scope.row.state == 1?'待审核':scope.row.state == 2?'审批通过':'审批不通过'}}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="创建时间" prop="createTime" align="center" width="180" />
+      <el-table-column label="状态" prop="status" align="center" >
+
+        <template #default="scope" v-if="data.isAdmin">
+          <el-switch
+              v-if="scope.row.state == 2"
+              v-model="scope.row.status"
+              :active-value="0"
+              :inactive-value="1"
+              inline-prompt
+              active-text="正常"
+              inactive-text="停用"
+              @change="switchStatus($event,scope.row)"
+          />
+          <span v-else>--</span>
+        </template>
+        <template #default="scope" v-else>
+          <span v-if="scope.row.state == 2">{{scope.row.status == 1? '停用' : '正常'}}</span>
+          <span v-else>--</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="180">
+        <template #default="scope">
+          <div v-if="scope.row.state == 2">
+            <div v-if="data.isAdmin">
+              <el-button link type="primary" @click="openDialog('edit',scope.row)">编辑</el-button>
+              <el-button link type="primary" @click="toChapters(scope.row)">章节</el-button>
+              <el-button link type="danger" @click="handleDelete(scope.row)">删除</el-button>
+            </div>
+            <div v-else>--</div>
+          </div>
+          <div v-else-if="scope.row.state == 1" >
+              <el-button link type="primary" @click="openDialog('edit',scope.row)">编辑</el-button>
+              <el-button link type="primary" v-if="data.isAdmin" @click="openApprove(scope.row)">审核</el-button>
+            <el-button link type="primary" @click="toChapters(scope.row)">章节</el-button>
+              <el-button link type="danger" @click="handleDelete(scope.row)">删除</el-button>
+          </div>
+          <div v-else-if="scope.row.state == 3" >
+            <div v-if="data.isAdmin">--</div>
+            <div v-else>
+              <el-button link type="primary" @click="openDialog('edit',scope.row)">编辑</el-button>
+              <el-button link type="primary" @click="submitApprove(scope.row)">提交审核</el-button>
+              <el-button link type="primary" @click="toChapters(scope.row)">章节</el-button>
+              <el-button link type="danger" @click="handleDelete(scope.row)">删除</el-button>
+            </div>
+          </div>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <pagination
+        v-show="total > 0"
+        :total="total"
+        v-model:page="queryParams.pageNum"
+        v-model:limit="queryParams.pageSize"
+        @pagination="getList"
+    />
+    <course-manage-dialog ref="dialogRef" @getList=getList></course-manage-dialog>
+    <el-dialog v-model="data.appDialog" title="审批课程" width="30%" center align-center>
+      <el-radio-group v-model="data.appoveForm.state" style="width: 100%">
+        <el-radio :label="2" size="large" border>通过</el-radio>
+        <el-radio :label="3" size="large" border>驳回</el-radio>
+      </el-radio-group>
+      <template #footer>
+            <span class="dialog-footer">
+              <el-button @click="data.appDialog = false">取消</el-button>
+              <el-button type="primary" @click="confirmApproval">确认</el-button>
+            </span>
+      </template>
+    </el-dialog>
+  </div>
 </template>
+
 <script setup>
+import {getCurrentInstance, onMounted, onUnmounted, reactive, ref, toRefs} from "vue";
+import {ElMessage, ElMessageBox} from "element-plus";
+import {delCompany, getCompany} from "@/api/onlineEducation/company";
+import courseManageDialog from  './components/courseManageDialog.vue'
+import {delBanner, getBanner} from "@/api/onlineEducation/banner";
+import {useRouter} from 'vue-router'
+import Cookies from "js-cookie";
+import {changeCourseStatus, delCourse, doCourse, getCourse} from "@/api/onlineEducation/courseManage";
+const { proxy } = getCurrentInstance();
+const router = useRouter()
+const loading = ref(false);
+const dialogRef = ref();
+const data = reactive({
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+  },
+  total: 0,
+  dataList: [],
+  isAdmin: false,
+  appDialog: false,
+  appoveForm: {
+    id: null,
+    state: null
+  },
+});
+
+const { queryParams, total, dataList } = toRefs(data);
+
+onMounted(async ()=>{
+  const userInfo = JSON.parse(Cookies.get('userInfo'))
+  console.log("userInfo",userInfo)
+  if(userInfo.userType === 0){
+    data.isAdmin = true;
+  }else {
+    data.isAdmin = false;
+  }
+  await getList()
+})
+
+onUnmounted(()=>{
+
+})
+
+const getList = async () => {
+  loading.value = true
+  const res = await getCourse(data.queryParams)
+  if(res.code == 200){
+    data.dataList = res.data.list.map(item => {
+      return {
+        ...item,
+        logo: item.logo ?[import.meta.env.VITE_APP_BASE_API + "/" +  item.logo] : [],
+      }
+    })
+    console.log("ddd",data.dataList)
+    data.total = res.data.total
+  }else{
+    ElMessage.warning(res.message)
+  }
+  loading.value = false
+}
+
+const openDialog = (type, value) => {
+  dialogRef.value.openDialog(type, value);
+}
+
+/** 重置新增的表单以及其他数据  */
+function reset() {
+  proxy.resetForm("roleRef");
+}
+const handleDelete = (val) => {
+  ElMessageBox.confirm(
+      '确定删除此条数据?',
+      '提示',
+      {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning',
+      })
+      .then( async() => {
+        const res = await delCourse(val.id)
+        if(res.code == 200){
+          ElMessage.success('数据删除成功')
+          await getList()
+        }else{
+          ElMessage.warning(res.message)
+        }
+      })
+}
+const switchStatus = (e,val) => {
+  ElMessageBox.confirm(
+      '确定修改该课程当前状态?',
+      '提示',
+      {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning',
+      })
+      .then( async() => {
+        const res = await changeCourseStatus({id: val.id,status: e})
+        if(res.code == 200){
+          ElMessage.success('状态修改成功')
+          await getList()
+        }else{
+          ElMessage.warning(res.message)
+        }
+      })
+      .catch(() => {
+        getList()
+      })
+
+}
+const openApprove  = (val) => {
+  data.appoveForm = {
+    id: null,
+    state: null
+  }
+  data.appoveForm.id = val.id
+  data.appDialog = true
+}
+const confirmApproval = async () =>{
+  if(data.appoveForm.state !== null){
+    const res = await doCourse(data.appoveForm)
+    if(res.code == 200){
+      ElMessage.success('审批成功')
+      await getList()
+      data.appDialog = false
+    }else{
+      ElMessage.warning(res.message)
+    }
+  }
+}
+const submitApprove = async (val) => {
+  const param = {
+    id: val.id,
+    state: 1
+  }
+  const res = await doCourse(param)
+  if(res.code == 200){
+    ElMessage.success('提交成功')
+    await getList()
+  }else{
+    ElMessage.warning(res.message)
+  }
+}
+const toChapters = (val) => {
+  router.push({ path: "/chapters", query: { courseId: val.id } });
+}
 
 </script>
-
-
-
-<style scoped lang="scss">
-
-</style>
diff --git a/src/views/onlineEducation/people/components/stuDialog.vue b/src/views/onlineEducation/people/components/stuDialog.vue
new file mode 100644
index 0000000..3b92dd1
--- /dev/null
+++ b/src/views/onlineEducation/people/components/stuDialog.vue
@@ -0,0 +1,311 @@
+<template>
+  <div class="notice">
+    <el-dialog
+        v-model="dialogVisible"
+        :title="state.title"
+        width="550px"
+        :before-close="handleClose"
+    >
+      <el-form :model="state.form" size="default" ref="superRef" :rules="state.formRules" label-width="180px" >
+        <el-form-item label="企业:"  prop="companyName" v-if="state.title !== '修改密码'">
+          <el-input v-model.trim="state.form.companyName" disabled></el-input>
+        </el-form-item>
+        <el-form-item label="姓名:"  prop="name" v-if="state.title !== '修改密码'">
+          <el-input v-model.trim="state.form.name" :disabled="disabled" placeholder="请输入姓名" ></el-input>
+        </el-form-item>
+        <el-form-item label="性别:"  prop="sex" v-if="state.title !== '修改密码'">
+          <el-radio-group v-model="state.form.sex"  :disabled="disabled">
+            <el-radio :label="0">男</el-radio>
+            <el-radio :label="1">女</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="身份证号:"  prop="idNo" v-if="state.title !== '修改密码'">
+          <el-input v-model.trim="state.form.idNo" :disabled="disabled" placeholder="请输入身份证号" ></el-input>
+        </el-form-item>
+        <el-form-item label="手机号(登录用户名):" prop="phone" v-if="state.title !== '修改密码'" >
+          <el-input v-model.trim="state.form.phone" :maxlength="11" :disabled="disabled" placeholder="请输入手机号"></el-input>
+        </el-form-item>
+        <el-form-item label="密码:" prop="password" v-if="state.title == '新增' || state.title == '修改密码'">
+          <el-input v-model.trim="state.form.password" type="password" show-password placeholder="请输入密码"></el-input>
+        </el-form-item>
+        <el-form-item label="重复密码:" prop="confirmPassword" v-if="state.title == '新增' || state.title == '修改密码'">
+          <el-input v-model.trim="state.form.confirmPassword" type="password" show-password placeholder="请输入确认密码"></el-input>
+        </el-form-item>
+        <el-form-item label="工号:"  prop="empno" v-if="state.title !== '修改密码'">
+          <el-input v-model.trim="state.form.empno" :disabled="disabled" placeholder="请输入工号" ></el-input>
+        </el-form-item>
+        <el-form-item label="岗位:"  prop="post" v-if="state.title !== '修改密码'">
+          <el-input v-model.trim="state.form.post" :disabled="disabled" placeholder="请输入岗位" ></el-input>
+        </el-form-item>
+        <el-form-item label="职务:"  prop="duty" v-if="state.title !== '修改密码'">
+          <el-input v-model.trim="state.form.duty" :disabled="disabled" placeholder="请输入职务" ></el-input>
+        </el-form-item>
+      </el-form>
+      <template #footer v-if="state.title !='查看'">
+        <span class="dialog-footer">
+            <el-button @click="handleClose" size="default">取 消</el-button>
+            <el-button type="primary"  @click="onSubmit" size="default" v-preReClick>确认</el-button>
+        </span>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+<script setup>
+import {reactive, ref, toRefs, defineEmits, nextTick, onMounted} from 'vue'
+import { View } from "@element-plus/icons-vue";
+import scorllSelect from '@/components/scrollSelect/index.vue'
+import {ElMessage, ElMessageBox} from "element-plus";
+import {verifyPhone, verifyPwd, verifyUsername} from "@/utils/validate";
+import { checkUserName, checkPhone } from "@/api/login"
+import {resetPwd} from "@/api/onlineEducation/student"
+import {Base64} from "js-base64"
+import Cookies from "js-cookie";
+import {addStudent, checkStuIdNo, checkStuPhone, editStudent} from "@/api/onlineEducation/student";
+
+const emit = defineEmits(["getList"]);
+const dialogVisible = ref(false)
+const superRef = ref(null)
+const scrollRef = ref(null)
+
+
+const equalToPassword = (rule, value, callback) => {
+  if (state.form.password !== value) {
+    callback(new Error("两次输入的密码不一致"));
+  } else {
+    callback();
+  }
+};
+
+const validateUserPhone = (rule, value, callback)=>{
+  if(value === ''){
+    callback(new Error('请输入手机号'))
+  }else if(state.title === '编辑' && value === startPhone.value){
+    callback()
+  } else if(!verifyPhone(value)){
+    callback(new Error('手机号格式有误'))
+  }else{
+    let param = {}
+    if(state.title === '新增') {
+      param = {
+        phone:value
+      }
+    }else if(state.title === '编辑'){
+      param = {
+        phone:value,
+        id: state.form.id
+      }
+    }
+    checkStuPhone(param).then((res)=>{
+      if(res.data == false){
+        callback(new Error('手机号已被占用,请更换其他手机号'))
+      }else{
+        callback()
+      }
+    })
+  }
+}
+
+let validatePwd = (rule, value, callback)=>{
+  if(value === ''){
+    callback(new Error('请输入密码'))
+  }else{
+    if(!verifyPwd(value)){
+      callback(new Error('密码须包含字母、数字、特殊字符,长度在6-16之间'))
+    }else{
+      callback()
+    }
+  }
+}
+let validateIdNo = (rule, value, callback)=>{
+  if(value === ''){
+    callback(new Error('请输入身份证号'))
+  }else if(state.title === '编辑' && value === startIdNo.value){
+    callback()
+  }else{
+    let param = {}
+    if(state.title === '新增') {
+      param = {
+        idNo:value
+      }
+    }else if(state.title === '编辑'){
+      param = {
+        idNo:value,
+        id: state.form.id
+      }
+    }
+    checkStuIdNo(param).then((res)=>{
+      if(res.data == false){
+        callback(new Error('身份证号已被占用,请更换其他身份证号'))
+        // ElMessageBox.confirm(
+        //     `该人员${state.form.name}(身份证号:${state.form.idNo})与${state.form.idNo}已经绑定,确定将该人员的责任归属变更到贵公企业?`,
+        //     '提示',
+        //     {
+        //       confirmButtonText: '确认',
+        //       cancelButtonText: '取消',
+        //       type: 'warning',
+        //       icon: ''
+        //     }
+        // )
+        //     .then(() => {
+        //       ElMessage({
+        //         type: 'success',
+        //         message: 'Delete completed',
+        //       })
+        //     })
+        //     .catch(() => {
+        //       callback(new Error('身份证号已被占用,请更换其他身份证号'))
+        //     })
+      }else{
+        callback()
+      }
+    })
+  }
+}
+
+
+const state = reactive({
+  title: '',
+  form: {
+    id: null,
+    name: '',
+    phone: '',
+    password: '',
+    confirmPassword: '',
+    sex: 0,
+    companyId: null,
+    empno: '',
+    post: '',
+    duty: '',
+    idNo: ''
+
+  },
+  formRules:{
+    name: [{ required: true, message: '请输入公司、部门或者车间岗位名称', trigger: 'blur' }],
+    password: [{ required: true, validator: validatePwd, trigger: 'blur' }],
+    confirmPassword: [{ required: true, validator: equalToPassword, trigger: 'blur' }],
+    phone: [{ required: true, validator: validateUserPhone, trigger: 'blur' }],
+    idNo: [{ required: true, validator: validateIdNo, trigger: 'blur' }],
+  },
+  isAdmin: false
+
+})
+const startPhone = ref('');
+const startIdNo = ref('');
+const UisMounted = ref(false);
+onMounted(() => {
+  UisMounted.value = true;
+
+});
+
+const disabled = ref(false);
+const openDialog = async (type, value) => {
+  const userInfo = JSON.parse(Cookies.get('userInfo'))
+  console.log("userInfo",userInfo)
+  if(userInfo.userType === 0){
+    state.isAdmin = true;
+    state.form.userType = 0;
+  }else {
+    state.isAdmin = false;
+    state.form.companyId = userInfo.companyId;
+    state.form.companyName = userInfo.companyName;
+    state.form.userType = 1;
+  }
+  state.title = type === 'add' ? '新增' : type ==='edit' ? '编辑' : type ==='pwd' ? '修改密码' : '查看' ;
+  if(type === 'edit' || type === 'view') {
+    if( type === 'view'){
+      disabled.value = true;
+    }
+    startPhone.value = value.phone
+    startIdNo.value = value.idNo
+    state.form = value
+    state.form.companyName = value.company.name;
+    console.log("ba",state.form)
+  }
+  if(type == 'pwd'){
+    state.form.id = value.id
+  }
+  dialogVisible.value = true
+}
+const onSubmit = async () => {
+  const valid = await superRef.value.validate();
+  if(valid){
+    if(state.title == '新增'){
+      const {confirmPassword,id,...data} = state.form
+      data.password = Base64.encode(data.password)
+      const res = await addStudent(data)
+      if(res.code == 200){
+        ElMessage.success(res.message)
+        emit('getList')
+        handleClose()
+        dialogVisible.value = false;
+      }else{
+        ElMessage.warning(res.message)
+      }
+    }else if(state.title == '编辑'){
+      const {id, name, phone, sex, companyId, empno, post, duty, idNo} = state.form
+      const data = {id, name, phone, sex, companyId, empno, post, duty, idNo}
+      const res = await editStudent(data)
+      if(res.code == 200){
+        ElMessage.success(res.message)
+        emit('getList')
+        handleClose()
+      }else{
+        ElMessage.warning(res.message)
+      }
+    }else{
+      const {id,password} = state.form
+      const data = {id,password}
+      data.password = Base64.encode(data.password)
+      const res = await resetPwd(data)
+      if(res.code == 200){
+        ElMessage.success(res.message)
+        emit('getList')
+        handleClose()
+      }else{
+        ElMessage.warning(res.message)
+      }
+    }
+  }
+}
+const handleClose = () => {
+  state.form = {
+    id: null,
+    name: '',
+    phone: '',
+    password: '',
+    confirmPassword: '',
+    sex: 0,
+    companyId: null,
+    empno: '',
+    post: '',
+    duty: '',
+    idNo: ''
+  }
+  superRef.value.clearValidate();
+  superRef.value.resetFields()
+  dialogVisible.value = false;
+}
+
+
+
+
+
+
+defineExpose({
+  openDialog
+});
+
+</script>
+
+<style scoped lang="scss">
+.notice{
+  :deep(.el-form .el-form-item__label) {
+    font-size: 15px;
+  }
+  .file {
+    display: flex;
+    flex-direction: column;
+    align-items: flex-start;
+  }
+}
+</style>
diff --git a/src/views/onlineEducation/people/index.vue b/src/views/onlineEducation/people/index.vue
index 4e19fe7..2141fa2 100644
--- a/src/views/onlineEducation/people/index.vue
+++ b/src/views/onlineEducation/people/index.vue
@@ -1,12 +1,138 @@
 <template>
-<div>人员管理</div>
+  <div class="app-container">
+    <div style="margin-bottom: 10px">
+      <el-button
+          type="primary"
+          plain
+          icon="Plus"
+          @click="openDialog('add',{})"
+      >新增</el-button>
+    </div>
+    <!-- 表格数据 -->
+    <el-table v-loading="loading" :data="dataList" :border="true">
+      <el-table-column label="序号" type="index" align="center" width="80" />
+      <el-table-column label="工号" prop="empno" align="center"  />
+      <el-table-column label="姓名" prop="name" align="center"  />
+      <el-table-column label="性别" prop="sex" align="center" >
+        <template #default="scope">
+          <span>{{scope.row.sex == 0 ? '男':'女'}}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="手机号" prop="phone" align="center" width="130"/>
+      <el-table-column label="身份证" prop="idNo" align="center" width="200" :show-overflow-tooltip="true"/>
+      <el-table-column label="创建人" prop="createBy" align="center"/>
+      <el-table-column label="工作岗位" prop="post" align="center"/>
+      <el-table-column label="职务" prop="duty" align="center"/>
+      <el-table-column label="一人一档" prop="duty" align="center" width="120">
+        <template #default="scope">
+          <el-button link type="primary">培训考试记录</el-button>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width"  width="180">
+        <template #default="scope">
+          <el-button link type="primary" @click="openDialog('edit',scope.row)">编辑</el-button>
+          <el-button link type="danger" @click="handleDelete(scope.row)">删除</el-button>
+          <el-button link type="primary" @click="openDialog('pwd',scope.row)">修改密码</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <pagination
+        v-show="total > 0"
+        :total="total"
+        v-model:page="queryParams.pageNum"
+        v-model:limit="queryParams.pageSize"
+        @pagination="getList"
+    />
+
+    <stu-dialog ref="dialogRef" @getList=getList></stu-dialog>
+  </div>
 </template>
+
 <script setup>
+import {getCurrentInstance, onMounted, onUnmounted, reactive, ref, toRefs} from "vue";
+import {ElMessage, ElMessageBox} from "element-plus";
+import {delCompany, getCompany} from "@/api/onlineEducation/company";
+import stuDialog from "./components/stuDialog.vue"
+import {delUser, getUser} from "@/api/onlineEducation/user";
+import Cookies from "js-cookie";
+import {delStudent, getStudent} from "@/api/onlineEducation/student";
+
+
+const { proxy } = getCurrentInstance();
+const loading = ref(false);
+const dialogRef = ref();
+const data = reactive({
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+  },
+  total: 0,
+  dataList: [],
+  isAdmin: false
+
+});
+
+const { queryParams, total, dataList } = toRefs(data);
+
+onMounted(async ()=>{
+  const userInfo = JSON.parse(Cookies.get('userInfo'))
+  console.log("userInfo",userInfo)
+  if(userInfo.userType === 0){
+    data.isAdmin = true;
+  }else {
+    data.isAdmin = false;
+  }
+  await getList()
+})
+onUnmounted(()=>{
+
+})
+
+const getList = async () => {
+  loading.value = true
+  const res = await getStudent(data.queryParams)
+  if(res.code == 200){
+    data.dataList = res.data.list
+    data.total = res.data.total
+  }else{
+    ElMessage.warning(res.message)
+  }
+  loading.value = false
+}
+
+const openDialog = (type, value) => {
+  if(type == 'add' && data.isAdmin){
+    ElMessage.warning('监管部门请联系企业创建企业学员')
+  }else{
+    dialogRef.value.openDialog(type, value);
+  }
+
+
+}
+
+/** 重置新增的表单以及其他数据  */
+function reset() {
+  proxy.resetForm("roleRef");
+}
+const handleDelete = (val) => {
+  ElMessageBox.confirm(
+      '确定删除此条数据?',
+      '提示',
+      {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning',
+      })
+      .then( async() => {
+        const res = await delStudent(val.id)
+        if(res.code == 200){
+          ElMessage.success('数据删除成功')
+          await getList()
+        }else{
+          ElMessage.warning(res.message)
+        }
+      })
+}
 
 </script>
-
-
-
-<style scoped lang="scss">
-
-</style>
diff --git a/src/views/onlineEducation/systemManage/banner/components/bannerDialog.vue b/src/views/onlineEducation/systemManage/banner/components/bannerDialog.vue
index b95c4cf..bd7f546 100644
--- a/src/views/onlineEducation/systemManage/banner/components/bannerDialog.vue
+++ b/src/views/onlineEducation/systemManage/banner/components/bannerDialog.vue
@@ -11,7 +11,7 @@
           <el-input v-model.trim="state.form.title"></el-input>
         </el-form-item>
         <el-form-item prop="imgUrl" label="图片:">
-          <el-upload accept="image/*" :action="state.uploadUrl" :headers="state.header" method="post" :on-success="(res, uploadFile)=>handleAvatarSuccess(res, uploadFile)" :on-exceed="showTip" :limit='state.imgLimit' v-model:file-list="state.imgList" list-type="picture-card" :before-upload="picSize" :on-remove="(file, uploadFiles)=>handleRemove(file, uploadFiles,'证书')" >
+          <el-upload accept="image/*" :action="state.uploadUrl" :headers="state.header" method="post" :on-success="(res, uploadFile)=>handleAvatarSuccess(res, uploadFile)" :on-exceed="showTip" :limit='state.imgLimit' v-model:file-list="state.imgList" list-type="picture-card" :before-upload="picSize" :on-remove="(file, uploadFiles)=>handleRemove(file, uploadFiles)" >
             <el-icon><Plus /></el-icon>
             <template #tip>
               <div class="el-upload__tip">上传jpg/png图片尺寸小于5M,最多可上传1张</div>
diff --git a/src/views/onlineEducation/systemManage/courseClassification/components/courseClassDialog.vue b/src/views/onlineEducation/systemManage/courseClassification/components/courseClassDialog.vue
index 51901ac..7b29ada 100644
--- a/src/views/onlineEducation/systemManage/courseClassification/components/courseClassDialog.vue
+++ b/src/views/onlineEducation/systemManage/courseClassification/components/courseClassDialog.vue
@@ -7,6 +7,9 @@
         :before-close="handleClose"
     >
       <el-form :model="state.form" size="default" ref="busRef" :rules="state.formRules" label-width="150px" >
+        <el-form-item label="上级分类:" prop="name" v-if="!state.isFirst">
+          <el-input v-model.trim="state.form.parentName" disabled></el-input>
+        </el-form-item>
         <el-form-item label="名称:" prop="name">
           <el-input v-model.trim="state.form.name"></el-input>
         </el-form-item>
@@ -76,6 +79,7 @@
   form: {
     id: '',
     name: '',
+    parentName: '',
     sort: 0,
     parentId: null,
     status: true
@@ -83,8 +87,8 @@
   formRules:{
     name: [{ required: true, trigger: "blur", validator: validateName }],
   },
+  isFirst: true
 })
-
 
 const openDialog = async (type, value) => {
   length.value = value.listLength
@@ -95,7 +99,11 @@
     state.form.sort = value.sort;
     startUsername.value = value.username;
   }else if(type === 'add' && value ){
-    state.form.parentId = value.id
+    state.isFirst = false;
+    state.form.parentId = value.id;
+    state.form.parentName = value.name;
+  }else {
+    state.isFirst = true;
   }
   dialogVisible.value = true;
 }
@@ -150,6 +158,7 @@
   state.form = {
     id: '',
     name: '',
+    parentName: '',
     sort: 0,
     parentId: null,
     status: true
diff --git a/src/views/onlineEducation/systemManage/courseClassification/index.vue b/src/views/onlineEducation/systemManage/courseClassification/index.vue
index 3f76fc7..49436e4 100644
--- a/src/views/onlineEducation/systemManage/courseClassification/index.vue
+++ b/src/views/onlineEducation/systemManage/courseClassification/index.vue
@@ -52,8 +52,6 @@
 const data = reactive({
   queryParams: {
     name: '',
-    pageNum: 1,
-    pageSize: 10,
   },
   total: 0,
   dataList: [
diff --git a/src/views/onlineEducation/systemManage/user/components/userDialog.vue b/src/views/onlineEducation/systemManage/user/components/userDialog.vue
index 71dd1e2..7ef7401 100644
--- a/src/views/onlineEducation/systemManage/user/components/userDialog.vue
+++ b/src/views/onlineEducation/systemManage/user/components/userDialog.vue
@@ -57,6 +57,7 @@
             </el-select>
           <el-input v-else disabled style="width: 45%" v-model="state.form.companyName"></el-input>
             <scorllSelect
+                :disabled="disabled"
                 ref="scrollRef"
                 v-if="UisMounted && (state.form.userType === 2 || state.form.userType === 3)"
                 v-model="state.form.parentName"
@@ -190,7 +191,9 @@
     state.form.userType = 1;
   }
 
-  await getCompanyList('open')
+  if(type !== 'view' && type !== 'pwd'){
+    await getCompanyList('open')
+  }
   state.title = type === 'add' ? '新增' : type ==='edit' ? '编辑' : type ==='pwd' ? '修改密码' : '查看' ;
   if(type === 'edit' || type === 'view') {
     if( type === 'view'){

--
Gitblit v1.9.2