addClassHours.vue 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. <template>
  2. <a-modal
  3. v-model:visible="modalVisible"
  4. title="添加课时"
  5. :footer="null"
  6. width="700px"
  7. @cancel="handleCancel"
  8. class="add-class-hours-modal"
  9. >
  10. <a-form :model="form" :rules="rules" ref="formRef" layout="vertical">
  11. <a-form-item label="课时名称:" name="title" required>
  12. <a-input v-model:value="form.title" placeholder="输入内容" />
  13. </a-form-item>
  14. <a-form-item label="选择视频:" required>
  15. <div class="video-select-row">
  16. <a-select v-model:value="form.video" style="width: 220px; margin-right: 12px" placeholder="选择已有资源">
  17. <a-select-option v-for="item in videoList" :key="item.id" :value="item.id">{{ item.name }}</a-select-option>
  18. </a-select>
  19. <a-upload :show-upload-list="false" :before-upload="beforeUploadVideo" :custom-request="dummyRequest">
  20. <a-button type="primary">上传新资源</a-button>
  21. </a-upload>
  22. </div>
  23. </a-form-item>
  24. <a-form-item label="上传封面:" required>
  25. <div class="cover-upload-row">
  26. <a-upload
  27. :show-upload-list="false"
  28. :before-upload="beforeUploadImg"
  29. :custom-request="dummyRequest"
  30. accept=".jpg,.png"
  31. >
  32. <div class="cover-upload-box">
  33. <img v-if="form.coverUrl" :src="form.coverUrl" class="cover-img" />
  34. <div v-else class="cover-placeholder">
  35. <PictureOutlined style="font-size: 32px; color: #bbb" />
  36. </div>
  37. </div>
  38. </a-upload>
  39. <div class="cover-tip">支持jpg、png等格式文件上传,文件大小不超过10MB</div>
  40. </div>
  41. </a-form-item>
  42. <a-form-item label="上传讲义:">
  43. <a-upload
  44. :show-upload-list="false"
  45. :before-upload="beforeUploadDoc"
  46. :custom-request="dummyRequest"
  47. accept=".ppt,.pptx,.doc,.docx,.pdf"
  48. >
  49. <a-button><CloudUploadOutlined /> 上传文件</a-button>
  50. </a-upload>
  51. <span class="upload-tip">支持ppt、word等格式文件上传,文件大小不超过10MB</span>
  52. </a-form-item>
  53. <a-form-item label="上传字幕:">
  54. <a-upload
  55. :show-upload-list="false"
  56. :before-upload="beforeUploadSrt"
  57. :custom-request="dummyRequest"
  58. accept=".srt"
  59. >
  60. <a-button><CloudUploadOutlined /> 上传文件</a-button>
  61. </a-upload>
  62. <span class="upload-tip">仅支持srt格式文件上传,文件大小不超过1MB</span>
  63. </a-form-item>
  64. <div class="footer-btns">
  65. <a-button @click="handleCancel">取消</a-button>
  66. <a-button type="primary" @click="handleOk">确定</a-button>
  67. </div>
  68. </a-form>
  69. </a-modal>
  70. </template>
  71. <script setup>
  72. import { ref, reactive, watch, defineProps, defineEmits } from 'vue'
  73. import { message } from 'ant-design-vue'
  74. import { PictureOutlined, CloudUploadOutlined } from '@ant-design/icons-vue'
  75. const props = defineProps({
  76. visible: Boolean
  77. })
  78. const emit = defineEmits(['update:visible', 'ok'])
  79. const modalVisible = ref(props.visible)
  80. watch(
  81. () => props.visible,
  82. (v) => {
  83. modalVisible.value = v
  84. }
  85. )
  86. watch(modalVisible, (v) => {
  87. emit('update:visible', v)
  88. })
  89. const formRef = ref()
  90. const form = reactive({
  91. title: '',
  92. video: '',
  93. coverUrl: '',
  94. docUrl: '',
  95. srtUrl: ''
  96. })
  97. const rules = {
  98. title: [{ required: true, message: '请输入课时名称' }],
  99. video: [{ required: true, message: '请选择或上传视频' }],
  100. coverUrl: [{ required: true, message: '请上传封面' }]
  101. }
  102. // mock视频资源
  103. const videoList = ref([
  104. { id: 'v1', name: '示例视频1.mp4' },
  105. { id: 'v2', name: '示例视频2.mp4' }
  106. ])
  107. function beforeUploadImg(file) {
  108. const isImg = file.type === 'image/jpeg' || file.type === 'image/png'
  109. const isLt10M = file.size / 1024 / 1024 < 10
  110. if (!isImg) {
  111. message.error('只能上传jpg/png图片')
  112. return false
  113. }
  114. if (!isLt10M) {
  115. message.error('图片不能超过10MB')
  116. return false
  117. }
  118. // mock上传
  119. const reader = new FileReader()
  120. reader.onload = (e) => {
  121. form.coverUrl = e.target.result
  122. }
  123. reader.readAsDataURL(file)
  124. return false
  125. }
  126. function beforeUploadVideo(file) {
  127. // 这里只做类型和大小校验,mock上传
  128. const isVideo = file.type.startsWith('video/')
  129. const isLt500M = file.size / 1024 / 1024 < 500
  130. if (!isVideo) {
  131. message.error('只能上传视频文件')
  132. return false
  133. }
  134. if (!isLt500M) {
  135. message.error('视频不能超过500MB')
  136. return false
  137. }
  138. // mock添加到下拉
  139. videoList.value.push({ id: 'mock_' + Date.now(), name: file.name })
  140. form.video = videoList.value[videoList.value.length - 1].id
  141. return false
  142. }
  143. function beforeUploadDoc(file) {
  144. const isDoc = /\.(ppt|pptx|doc|docx|pdf)$/i.test(file.name)
  145. const isLt10M = file.size / 1024 / 1024 < 10
  146. if (!isDoc) {
  147. message.error('仅支持ppt、word、pdf格式')
  148. return false
  149. }
  150. if (!isLt10M) {
  151. message.error('文件不能超过10MB')
  152. return false
  153. }
  154. form.docUrl = file.name
  155. return false
  156. }
  157. function beforeUploadSrt(file) {
  158. const isSrt = file.name.endsWith('.srt')
  159. const isLt1M = file.size / 1024 / 1024 < 1
  160. if (!isSrt) {
  161. message.error('仅支持srt格式')
  162. return false
  163. }
  164. if (!isLt1M) {
  165. message.error('文件不能超过1MB')
  166. return false
  167. }
  168. form.srtUrl = file.name
  169. return false
  170. }
  171. function dummyRequest({ onSuccess }) {
  172. setTimeout(() => {
  173. onSuccess && onSuccess()
  174. }, 500)
  175. }
  176. function handleOk() {
  177. formRef.value.validate().then(() => {
  178. emit('ok', { ...form })
  179. modalVisible.value = false
  180. })
  181. }
  182. function handleCancel() {
  183. modalVisible.value = false
  184. }
  185. </script>
  186. <style lang="less" scoped>
  187. .add-class-hours-modal {
  188. .ant-modal-content {
  189. border-radius: 10px;
  190. }
  191. .ant-modal-header {
  192. border-radius: 10px 10px 0 0;
  193. }
  194. .ant-form-item {
  195. margin-bottom: 24px;
  196. }
  197. .video-select-row {
  198. display: flex;
  199. align-items: center;
  200. }
  201. .cover-upload-row {
  202. display: flex;
  203. align-items: center;
  204. .cover-upload-box {
  205. width: 120px;
  206. height: 120px;
  207. background: #f7f8fa;
  208. border-radius: 8px;
  209. display: flex;
  210. align-items: center;
  211. justify-content: center;
  212. margin-right: 24px;
  213. border: 1px dashed #d9d9d9;
  214. cursor: pointer;
  215. .cover-img {
  216. width: 100%;
  217. height: 100%;
  218. object-fit: cover;
  219. border-radius: 8px;
  220. }
  221. .cover-placeholder {
  222. display: flex;
  223. align-items: center;
  224. justify-content: center;
  225. width: 100%;
  226. height: 100%;
  227. color: #bbb;
  228. font-size: 32px;
  229. }
  230. }
  231. .cover-tip {
  232. color: #888;
  233. font-size: 13px;
  234. }
  235. }
  236. .upload-tip {
  237. color: #888;
  238. font-size: 13px;
  239. margin-left: 12px;
  240. }
  241. .footer-btns {
  242. display: flex;
  243. justify-content: flex-end;
  244. gap: 16px;
  245. margin-top: 24px;
  246. }
  247. }
  248. </style>