create.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533
  1. <!-- 课程创建/修改 -->
  2. <template>
  3. <div class="pjc">
  4. <el-page-header @back="$router.go(-1)" :content="title" style="margin-bottom: 30px;"></el-page-header>
  5. <!-- <h3>{{ title }}</h3> -->
  6. <el-form :model="courseDeail" label-width="110px" :rules="rules">
  7. <el-form-item label="课程名称:" prop="name">
  8. <el-input :maxlength="30" show-word-limit v-model.trim="courseDeail.name" style="width: 40%" placeholder="输入课程名称,最多30字符"></el-input>
  9. </el-form-item>
  10. <el-form-item label="预览图:" prop="thumb">
  11. <uploadOss
  12. :headers="$xtoken"
  13. :action="$action"
  14. :limit="1"
  15. list_type="picture-card"
  16. :accept="$acceptImg"
  17. :multiple="true"
  18. ref="clearPicture"
  19. coursePath="course"
  20. :show-file-list="true"
  21. :file-list="courseDeail.thumb"
  22. :on-success="handleFilesSuccess2"
  23. :on-preview="onFilePreView"
  24. :before-upload="beforeUpload"
  25. :on-remove="onFileRemove2"
  26. >
  27. <i class="el-icon-plus"></i>
  28. </uploadOss>
  29. <!-- <div style="display: flex;">
  30. <div style="cursor: pointer;margin-right: 10px;" @click="showApm(courseDeail.thumb)">
  31. <userImage
  32. v-if="courseDeail.thumb"
  33. width="100px"
  34. height="100px"
  35. radius="5px"
  36. :img_url="courseDeail.thumb"
  37. :user_name="courseDeail.name"
  38. ></userImage>
  39. </div>
  40. <div class="cursor" @click="showSelectImg(1)">
  41. <div><i class="el-icon-plus"></i></div>
  42. </div>
  43. </div> -->
  44. </el-form-item>
  45. <el-form-item label="价格:" prop="price">
  46. <!-- <el-input-number
  47. v-model="courseDeail.price"
  48. :min="0"
  49. :max="10000000"
  50. label="课程价格"
  51. ></el-input-number> -->
  52. <el-input v-model="courseDeail.price" maxlength="6" style="width: 150px;" placeholder="请输入价格" size="normal" clearable></el-input>
  53. </el-form-item>
  54. <el-form-item label="浏览量:" prop="price">
  55. <el-input-number
  56. v-model="courseDeail.baseClick"
  57. :min="0"
  58. :max="10000000"
  59. label="浏览次数"
  60. ></el-input-number>
  61. </el-form-item>
  62. <el-form-item label="是否上架:">
  63. <el-switch
  64. v-model="courseDeail.enable"
  65. active-color="#13ce66"
  66. inactive-color="#999"
  67. >
  68. </el-switch>
  69. </el-form-item>
  70. <el-form-item label="课程介绍:" prop="images">
  71. <uploadOss
  72. :headers="$xtoken"
  73. :action="$action"
  74. :limit="23"
  75. list_type="picture-card"
  76. :accept="$acceptImg"
  77. :multiple="true"
  78. ref="clearPicture"
  79. coursePath="course"
  80. :show-file-list="true"
  81. :file-list="courseDeail.images"
  82. :on-success="handleFilesSuccess"
  83. :on-preview="onFilePreView"
  84. :before-upload="beforeUpload"
  85. :on-remove="onFileRemove"
  86. >
  87. <i class="el-icon-plus"></i>
  88. </uploadOss>
  89. </el-form-item>
  90. <el-form-item label="课程章节" size="normal">
  91. <div class="sectionout">
  92. <div
  93. class="sectionInner"
  94. v-for="(item, index) in courseDeail.sections"
  95. :key="index"
  96. >
  97. <div class="sectionName">
  98. <el-input v-model.trim="item.name" placeholder="请输入章节名..."></el-input>
  99. </div>
  100. <div class="sectionUrl">
  101. <el-input v-model.trim="item.link" placeholder="请输入章节链接..."></el-input>
  102. </div>
  103. <div class="del">
  104. <el-button
  105. type="text"
  106. size="mini"
  107. plain
  108. @click="sectionDel(index, item)"
  109. style="color: #f89881"
  110. >删除</el-button
  111. >
  112. </div>
  113. </div>
  114. <div class="addSection">
  115. <el-button plain @click="addSection">+</el-button>
  116. </div>
  117. </div>
  118. </el-form-item>
  119. <el-form-item style="display: flex; flex-direction: row-reverse">
  120. <el-button @click="$router.go(-1)">取消</el-button>
  121. <el-button @click="iffirm">{{ alterCreate }}</el-button>
  122. </el-form-item>
  123. </el-form>
  124. <el-dialog :visible.sync="dialogVisible">
  125. <img width="100%" :src="dialogImageUrl" alt="" />
  126. </el-dialog>
  127. <image-cropper
  128. v-loading="company_img_loading"
  129. field="file"
  130. :width="imgWidth"
  131. :height="imgHeight"
  132. :url="'https://integralsys.oss-cn-shenzhen.aliyuncs.com'"
  133. @close="company_img_show = false"
  134. @crop-upload-success="company_img_success"
  135. langType="zh"
  136. v-if="company_img_show"
  137. ></image-cropper>
  138. </div>
  139. </template>
  140. <script>
  141. import ImageCropper from "@/components/ImageCropper";
  142. import uploadOss from '@/components/upload';
  143. import { validateURL } from "@/utils/validate";
  144. import {createCourse,updataCourse,getCourseDetail} from '../api'
  145. export default {
  146. name: "courseEdit",
  147. components: { ImageCropper,uploadOss },
  148. data() {
  149. return {
  150. curId: 0,
  151. title: "",
  152. company_img_show: false,
  153. company_img_loading: false,
  154. alterCreate: "保存",
  155. imgIndex: 1,
  156. imgWidth: 300,
  157. imgHeight: 300,
  158. // 个人信息
  159. courseDeail: {
  160. name: "",
  161. price: 0,
  162. baseClick:100,
  163. thumb: [],
  164. enable: true,
  165. images: [],
  166. sections: [],
  167. },
  168. dialogVisible: false,
  169. dialogImageUrl: "",
  170. rules: {
  171. name: [
  172. { required: true, message: '请输入课程名称', trigger: 'blur' },
  173. { min: 1, max: 30, message: '长度在 1 到 30 个字符', trigger: 'blur' }
  174. ],
  175. price: [
  176. { required: true, message: '请输入课程价格', trigger: 'blur' },
  177. ],
  178. baseClick: [
  179. { required: true, message: '请输入浏览量', trigger: 'blur' },
  180. ],
  181. thumb: [
  182. { required: true, message: '请选择预览图', trigger: 'change' },
  183. {type:'array',trigger:'change'}
  184. ],
  185. images: [
  186. { required: true, message: '请选择介绍图', trigger: 'change' },
  187. {type:'array',trigger:'change'}
  188. ],
  189. }
  190. };
  191. },
  192. computed: {
  193. courseImages() {
  194. if (this.courseDeail.images) {
  195. return this.courseDeail.images.split(",");
  196. } else {
  197. return [];
  198. }
  199. },
  200. },
  201. methods: {
  202. // 介绍图上传格式
  203. beforeUpload(file) {
  204. const isJPG = /^image\/(jpeg|png|jpg)$/.test(file.type);
  205. const isLt2M = file.size / 1024 / 1024 < 5;
  206. if (!isJPG) {
  207. this.$message.error('上传图片只能是 JPG,PNG,JPEG 格式!');
  208. }
  209. if (!isLt2M) {
  210. this.$message.error('上传图片大小不能超过 5MB!');
  211. }
  212. return isJPG && isLt2M;
  213. },
  214. //介绍图预览
  215. onFilePreView(file) {
  216. this.showApm(file.url)
  217. },
  218. // 介绍图删除
  219. onFileRemove(file, fileList) {
  220. this.courseDeail.images = fileList
  221. },
  222. // 介绍图上传
  223. handleFilesSuccess(response, file, fileList) {
  224. let fileListData = fileList.filter(e => {
  225. return e.url;
  226. });
  227. this.courseDeail.images = fileListData
  228. },
  229. // 预览图删除
  230. onFileRemove2(file, fileList) {
  231. this.courseDeail.thumb = fileList
  232. },
  233. // 预览图上传
  234. handleFilesSuccess2(response, file, fileList) {
  235. let fileListData = fileList.filter(e => {
  236. return e.url;
  237. });
  238. this.courseDeail.thumb = fileListData
  239. },
  240. //章节信息校验
  241. validateURL() {
  242. let result = false;
  243. result = this.courseDeail.sections.every((item) => {
  244. let reg = /^[^\u4e00-\u9fff]*$/
  245. return item.link !== "" && reg.test(item.link) && item.name !== "";
  246. // return validateURL(item.link) && item.name !== "";
  247. });
  248. return result;
  249. },
  250. // 获取课程详情
  251. getCourseDetail() {
  252. getCourseDetail(this.curId).then((res) => {
  253. this.courseDeail = this.uploadToData(res);
  254. }).catch(err=>{
  255. console.error(err)
  256. });
  257. },
  258. //课程详情数据转换展示
  259. uploadToData(data) {
  260. data.enable = data.enable == 1 ? true : false;
  261. // data.price = Number(data.price);
  262. let arr = []
  263. data.images.forEach(item=>{
  264. arr.push({name:item,url:item})
  265. })
  266. data.images = arr
  267. let arr2 = []
  268. data.thumb.split(',').forEach(item=>{
  269. arr2.push({name:item,url:item})
  270. })
  271. data.thumb = arr2
  272. return data;
  273. },
  274. //预览图上传成功回调
  275. company_img_success(resData) {
  276. if (this.imgIndex == 1) {
  277. this.courseDeail.thumb = resData.url;
  278. } else {
  279. if (
  280. this.courseDeail.images.split(",").length == 1 &&
  281. this.courseDeail.images.split(",")[0] == ""
  282. ) {
  283. this.courseDeail.images = resData.url;
  284. } else {
  285. this.courseDeail.images = `${this.courseDeail.images},${resData.url}`;
  286. }
  287. }
  288. this.company_img_show = false;
  289. },
  290. // 查看预览图
  291. showApm(url){
  292. this.dialogVisible = true;
  293. this.dialogImageUrl = url;
  294. },
  295. //打开上传预览图片组件
  296. showSelectImg(type) {
  297. this.imgIndex = type;
  298. this.company_img_show = true;
  299. },
  300. //展示数据转换成上传数据
  301. toUpdata() {
  302. let data = JSON.parse(JSON.stringify(this.courseDeail));
  303. data.enable = data.enable ? 1 : 0;
  304. data.sections = JSON.stringify(data.sections);
  305. let arr = data.images.map(item=>{
  306. return item.url
  307. })
  308. data.images = arr.join(',')
  309. let arr2 = data.thumb.map(item=>{
  310. return item.url
  311. })
  312. data.thumb = arr2.join(',')
  313. return data;
  314. },
  315. // 添加课程
  316. addSection() {
  317. let section = {
  318. name: "",
  319. link: "",
  320. };
  321. this.courseDeail.sections.push(section);
  322. },
  323. // 删除章节
  324. sectionDel(index, item) {
  325. this.$confirm("确定删除当前章节?", "提示", {
  326. confirmButtonText: "确定",
  327. cancelButtonText: "取消",
  328. type: "warning",
  329. })
  330. .then(() => {
  331. this.courseDeail.sections.splice(index, 1);
  332. })
  333. .catch((err) => {
  334. this.$message({
  335. type: "info",
  336. message: "已取消",
  337. });
  338. });
  339. },
  340. //保存
  341. iffirm() {
  342. if (
  343. !this.courseDeail.name == "" &&
  344. this.courseDeail.images.length &&
  345. this.courseDeail.thumb.length &&
  346. this.courseDeail.price &&
  347. this.courseDeail.baseClick != undefined &&
  348. this.validateURL()
  349. ) {
  350. this.$confirm("确定保存当前课程信息?", "提示", {
  351. confirmButtonText: "确定",
  352. cancelButtonText: "取消",
  353. type: "info",
  354. })
  355. .then(() => {
  356. if (this.$route.params.id) {
  357. this.saveAlter();
  358. } else {
  359. this.saveCreate();
  360. }
  361. })
  362. .catch(() => {
  363. this.$message({
  364. type: "info",
  365. message: "已取消",
  366. });
  367. });
  368. } else if (!this.validateURL()) {
  369. this.$message({
  370. type: "warning",
  371. message: "章节列表未填写正确",
  372. });
  373. } else {
  374. this.$message({
  375. type: "warning",
  376. message: "课程信息未填写完整",
  377. });
  378. }
  379. },
  380. //修改保存
  381. saveAlter() {
  382. let data = this.toUpdata();
  383. updataCourse(this.curId,data).then(res=>{
  384. this.$message({
  385. message: "修改成功!",
  386. type: "success",
  387. showClose: true,
  388. duration: 2000,
  389. });
  390. this.$router.push("/course/courseManage");
  391. }).catch(err=>{
  392. console.error(err)
  393. })
  394. },
  395. //保存新建课程
  396. saveCreate() {
  397. let data = this.toUpdata();
  398. createCourse(data).then(res=>{
  399. this.$message({
  400. message: "新建成功!",
  401. type: "success",
  402. showClose: true,
  403. duration: 2000,
  404. });
  405. this.$router.push("/course/courseManage");
  406. }).catch(err=>{
  407. console.error(err)
  408. })
  409. },
  410. //判断新建/修改
  411. init() {
  412. if (this.$route.params.id == undefined) {
  413. this.title = "添加课程";
  414. this.courseDeail = {
  415. name: "",
  416. price: 0,
  417. thumb: [],
  418. enable: true,
  419. baseClick:100,
  420. images: [],
  421. sections: [],
  422. };
  423. } else {
  424. this.curId = this.$route.params.id;
  425. this.title = "编辑课程";
  426. this.getCourseDetail();
  427. }
  428. },
  429. },
  430. created() {
  431. this.init();
  432. },
  433. };
  434. </script>
  435. <style scoped lang="scss">
  436. .pjc {
  437. width: 900px;
  438. height: auto;
  439. margin: 0 auto;
  440. margin-top: 10px;
  441. background-color: #fff;
  442. border-radius: 5px;
  443. padding: 30px 60px;
  444. h3 {
  445. color: #000;
  446. text-align: center;
  447. margin: 30px 0;
  448. }
  449. form {
  450. legend {
  451. font-size: 20px;
  452. color: #000;
  453. line-height: 30px;
  454. font-weight: bold;
  455. margin-bottom: 22px;
  456. }
  457. .el-form-item {
  458. .el-select {
  459. width: 380px;
  460. }
  461. }
  462. }
  463. }
  464. .sectionout {
  465. .sectionInner {
  466. display: flex;
  467. // justify-content: space-between;
  468. align-items: center;
  469. margin-bottom: 10px;
  470. .sectionName {
  471. width: 150px;
  472. margin-right: 20px;
  473. }
  474. .sectionUrl {
  475. width: 250px;
  476. margin-right: 20px;
  477. }
  478. .sectionDel {
  479. }
  480. }
  481. }
  482. .img_all {
  483. display: flex;
  484. flex-wrap: wrap;
  485. & > div {
  486. margin-right: 10px;
  487. margin-bottom: 10px;
  488. }
  489. .imgout{
  490. position: relative;
  491. .imgDelete{
  492. width: 100%;
  493. height: 100%;
  494. display: flex;
  495. align-items: center;
  496. justify-content: center;
  497. cursor: pointer;
  498. position: absolute;
  499. left:0;
  500. top:0;
  501. background-color: rgba(0,0,0,.3);
  502. opacity: 0;
  503. transition: opacity 0.3s ease;
  504. }
  505. &:hover{
  506. .imgDelete{
  507. opacity:1;
  508. }
  509. }
  510. }
  511. }
  512. .cursor {
  513. & > div {
  514. height: 100px;
  515. width: 100px;
  516. background-color: #fbfdff;
  517. border: 1px dashed #c0ccda;
  518. border-radius: 5px;
  519. font-size: 30px;
  520. display: flex;
  521. justify-content: center;
  522. align-items: center;
  523. i{
  524. font-size: 28px;
  525. color: #8c939d;
  526. }
  527. }
  528. }
  529. </style>