examCertification.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400
  1. <template>
  2. <div class="offline-training-container">
  3. <el-card class="box-card">
  4. <div slot="header" class="clearfix">
  5. <span>考试拿证流程介绍</span>
  6. </div>
  7. <viewer ref="imageWrapper" :images="previewImages" style="opacity: 1;position: absolute;top: -9999px;">
  8. <img v-for="(image, index) in previewImages" :src="image" :key="index" />
  9. </viewer>
  10. <el-form ref="form" :model="form" label-width="120px">
  11. <div class="form-section">
  12. <el-form-item label="分类名称" prop="typeName">
  13. <el-input v-model="form.typeName" placeholder="请输入分类名称" maxlength="100"></el-input>
  14. </el-form-item>
  15. </div>
  16. <div class="form-section">
  17. <el-form-item label="分类封面" prop="coverImage">
  18. <div class="upload-area">
  19. <el-upload
  20. class="cover-uploader"
  21. action="#"
  22. :show-file-list="false"
  23. :on-change="handleCoverChange"
  24. :auto-upload="false"
  25. :before-upload="beforeCoverUpload">
  26. <img v-if="form.coverImage" :src="form.coverImage" class="cover-preview">
  27. <i v-else class="el-icon-plus cover-uploader-icon"></i>
  28. <div v-if="form.coverImage" class="cover-mask">
  29. <el-link icon="el-icon-view" type="success" @click.stop="previewImage(form.coverImage)" :underline="false">查看</el-link>
  30. <el-link icon="el-icon-edit" type="success" :underline="false" style="margin-left: 10px;">修改</el-link>
  31. </div>
  32. </el-upload>
  33. <div class="upload-tip">建议尺寸:800x600px,大小不超过5MB</div>
  34. </div>
  35. </el-form-item>
  36. </div>
  37. <div class="form-section">
  38. <el-form-item label="内容上传" prop="type">
  39. <el-radio-group v-model="form.type" @change="handleContentTypeChange">
  40. <el-radio label="image">图片</el-radio>
  41. <el-radio label="video">视频</el-radio>
  42. </el-radio-group>
  43. <div class="upload-area" v-if="form.type === 'image'">
  44. <el-upload
  45. action="#"
  46. :show-file-list="false"
  47. :on-change="handleImageChange"
  48. :auto-upload="false"
  49. accept="image/*"
  50. :before-upload="beforeImageUpload">
  51. <el-button size="small" type="primary">点击上传图片</el-button>
  52. </el-upload>
  53. <div class="upload-preview" v-if="form.contentImage">
  54. <img :src="form.contentImage" class="content-preview" @click="previewImage(form.contentImage)">
  55. </div>
  56. </div>
  57. <div class="upload-area" v-else-if="form.type === 'video'">
  58. <el-upload
  59. action="#"
  60. :show-file-list="false"
  61. :on-change="handleVideoChange"
  62. :auto-upload="false"
  63. accept="video/*"
  64. :before-upload="beforeVideoUpload">
  65. <el-button size="small" type="primary">点击上传视频</el-button>
  66. </el-upload>
  67. <div class="upload-preview" v-if="form.contentVideo">
  68. <video controls class="video-preview">
  69. <source :src="form.contentVideo" type="video/mp4">
  70. </video>
  71. </div>
  72. </div>
  73. </el-form-item>
  74. </div>
  75. <div class="action-buttons">
  76. <el-button type="primary" @click="submitForm" :loading="submitLoading">提交</el-button>
  77. <el-button @click="resetForm" :loading="submitLoading">重置</el-button>
  78. </div>
  79. </el-form>
  80. </el-card>
  81. </div>
  82. </template>
  83. <script>
  84. import { post, checkAndAddUpload } from '../../api'
  85. export default {
  86. data() {
  87. return {
  88. form: {
  89. typeName: '',
  90. coverImage: '',
  91. type: 'image',
  92. contentImage: '',
  93. contentVideo: ''
  94. },
  95. previewImages: [],
  96. submitLoading: false
  97. }
  98. },
  99. methods: {
  100. previewImage(url) {
  101. this.previewImages = [url];
  102. this.$nextTick(() => {
  103. if (this.$refs.imageWrapper && this.$refs.imageWrapper.$viewer) {
  104. this.$refs.imageWrapper.$viewer.show();
  105. }
  106. });
  107. },
  108. checkAndAddUpload(str) {
  109. if(!str) return '';
  110. return str.includes('/upload/') ? str : '/upload/' + str;
  111. },
  112. handleCoverChange(file) {
  113. const isImage = file.raw.type.includes('image');
  114. const isLt5M = file.raw.size / 1024 / 1024 < 5;
  115. if (!isImage) {
  116. this.$message.error('只能上传图片文件!');
  117. return;
  118. }
  119. if (!isLt5M) {
  120. this.$message.error('图片大小不能超过5MB!');
  121. return;
  122. }
  123. const formData = new FormData();
  124. formData.append('multipartFile', file.raw);
  125. this.http.uploadFile('/common/uploadFile', formData, res => {
  126. if (res.code === "ok") {
  127. this.form.coverImage = this.checkAndAddUpload(res.data);
  128. this.$message.success('封面图片上传成功');
  129. } else {
  130. this.$message.error(res.msg || '上传失败');
  131. }
  132. });
  133. },
  134. beforeCoverUpload(file) {
  135. const isImage = file.type.includes('image');
  136. const isLt5M = file.size / 1024 / 1024 < 5;
  137. if (!isImage) {
  138. this.$message.error('只能上传图片文件!');
  139. return false;
  140. }
  141. if (!isLt5M) {
  142. this.$message.error('图片大小不能超过5MB!');
  143. return false;
  144. }
  145. return true;
  146. },
  147. handleImageChange(file) {
  148. const isImage = file.raw.type.includes('image');
  149. const isLt5M = file.raw.size / 1024 / 1024 < 5;
  150. if (!isImage) {
  151. this.$message.error('只能上传图片文件!');
  152. return;
  153. }
  154. if (!isLt5M) {
  155. this.$message.error('图片大小不能超过5MB!');
  156. return;
  157. }
  158. const formData = new FormData();
  159. formData.append('multipartFile', file.raw);
  160. this.http.uploadFile('/common/uploadFile', formData, res => {
  161. if (res.code === "ok") {
  162. this.form.contentImage = this.checkAndAddUpload(res.data);
  163. this.$message.success('内容图片上传成功');
  164. } else {
  165. this.$message.error(res.msg || '上传失败');
  166. }
  167. });
  168. },
  169. beforeImageUpload(file) {
  170. return this.beforeCoverUpload(file);
  171. },
  172. handleVideoChange(file) {
  173. const isVideo = file.raw.type.includes('video');
  174. const isLt50M = file.raw.size / 1024 / 1024 < 50;
  175. if (!isVideo) {
  176. this.$message.error('只能上传视频文件!');
  177. return;
  178. }
  179. if (!isLt50M) {
  180. this.$message.error('视频大小不能超过50MB!');
  181. return;
  182. }
  183. const formData = new FormData();
  184. formData.append('multipartFile', file.raw);
  185. this.http.uploadFile('/common/uploadFile', formData, res => {
  186. if (res.code === "ok") {
  187. this.form.contentVideo = this.checkAndAddUpload(res.data);
  188. this.$message.success('视频上传成功');
  189. } else {
  190. this.$message.error(res.msg || '上传失败');
  191. }
  192. });
  193. },
  194. beforeVideoUpload(file) {
  195. const isVideo = file.type.includes('video');
  196. const isLt50M = file.size / 1024 / 1024 < 50;
  197. if (!isVideo) {
  198. this.$message.error('只能上传视频文件!');
  199. return false;
  200. }
  201. if (!isLt50M) {
  202. this.$message.error('视频大小不能超过50MB!');
  203. return false;
  204. }
  205. return true;
  206. },
  207. handleContentTypeChange(val) {
  208. this.form.contentImage = '';
  209. this.form.contentVideo = '';
  210. },
  211. submitForm() {
  212. this.$refs.form.validate(valid => {
  213. if (valid) {
  214. const { id, type, typeName, coverImage, contentImage, contentVideo } = this.form
  215. const obj = {
  216. typeName,
  217. coverImage,
  218. type: type == 'video' ? 1 : 0,
  219. content: type == 'video' ? contentVideo : contentImage,
  220. }
  221. if(id) {
  222. obj.id = id
  223. }
  224. this.submitLoading = true
  225. post(`/examination-process/saveOrUpdate`, { ...obj }).then(res => {
  226. this.$message({
  227. type: 'success',
  228. message: res.msg || '保存成功',
  229. });
  230. this.obtainOfflineTrainingCourses()
  231. }).finally(() => {
  232. this.submitLoading = false
  233. })
  234. }
  235. });
  236. },
  237. resetForm() {
  238. this.$refs.form.resetFields();
  239. this.form.contentImage = '';
  240. this.form.contentVideo = '';
  241. },
  242. obtainOfflineTrainingCourses() {
  243. post(`/examination-process/getDetail`, {}).then((res) => {
  244. this.form = {
  245. ...res.data,
  246. content: checkAndAddUpload(res.data.content),
  247. coverImage: checkAndAddUpload(res.data.coverImage),
  248. contentImage: res.data.type != 1 ? checkAndAddUpload(res.data.content) : '',
  249. contentVideo: res.data.type == 1 ? checkAndAddUpload(res.data.content) : '',
  250. type: res.data.type == 1 ? 'video' : 'image'
  251. }
  252. })
  253. },
  254. },
  255. mounted() {
  256. this.obtainOfflineTrainingCourses()
  257. }
  258. }
  259. </script>
  260. <style scoped>
  261. .offline-training-container {
  262. padding: 20px;
  263. max-width: 1000px;
  264. margin: 0 auto;
  265. }
  266. .box-card {
  267. border-radius: 8px;
  268. box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
  269. }
  270. .form-section {
  271. margin-bottom: 24px;
  272. }
  273. .upload-area {
  274. border: 1px dashed #dcdfe6;
  275. border-radius: 6px;
  276. padding: 20px;
  277. margin-bottom: 20px;
  278. background-color: #f5f7fa;
  279. }
  280. .upload-preview {
  281. margin-top: 15px;
  282. text-align: center;
  283. }
  284. .cover-uploader {
  285. border: 1px dashed #d9d9d9;
  286. border-radius: 6px;
  287. cursor: pointer;
  288. position: relative;
  289. overflow: hidden;
  290. width: 200px;
  291. height: 150px;
  292. margin: 0 auto;
  293. }
  294. .cover-uploader:hover {
  295. border-color: #409EFF;
  296. }
  297. .cover-uploader:hover .cover-mask {
  298. opacity: 1;
  299. }
  300. .cover-mask {
  301. width: 100%;
  302. height: 100%;
  303. background: rgba(0, 0, 0, 0.5);
  304. display: flex;
  305. align-items: center;
  306. justify-content: center;
  307. position: absolute;
  308. top: 0;
  309. left: 0;
  310. opacity: 0;
  311. }
  312. .cover-uploader-icon {
  313. font-size: 28px;
  314. color: #8c939d;
  315. width: 200px;
  316. height: 150px;
  317. line-height: 150px;
  318. text-align: center;
  319. }
  320. .cover-preview {
  321. width: 200px;
  322. height: 150px;
  323. object-fit: cover;
  324. border-radius: 4px;
  325. cursor: pointer;
  326. }
  327. .content-preview {
  328. max-width: 100%;
  329. max-height: 300px;
  330. border-radius: 4px;
  331. cursor: pointer;
  332. box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  333. }
  334. .video-preview {
  335. max-width: 100%;
  336. max-height: 300px;
  337. border-radius: 4px;
  338. background-color: #000;
  339. }
  340. .upload-tip {
  341. color: #909399;
  342. font-size: 12px;
  343. margin-top: 8px;
  344. text-align: center;
  345. }
  346. .action-buttons {
  347. margin-top: 24px;
  348. text-align: center;
  349. }
  350. </style>