lex 2 年 前
コミット
f6b2ba94b9

+ 24 - 21
src/components/col-cropper/cropper.tsx

@@ -21,11 +21,11 @@ export default defineComponent({
   props: {
     cropperNo: {
       type: Function,
-      default: (data: any) => {}
+      default: () => {}
     },
     cropperOk: {
       type: Function,
-      default: () => {}
+      default: (data: any) => {}
     },
     bucket: {
       type: String,
@@ -62,7 +62,8 @@ export default defineComponent({
       previews: {} as any,
       url: {
         upload: '/sys/common/saveToImgByStr'
-      }
+      },
+      submitLoading: false
     }
   },
   methods: {
@@ -84,14 +85,10 @@ export default defineComponent({
      */
     okHandel() {
       ;(this as any).$refs.cropperRef.getCropBlob(async data => {
-        console.log(data, 'data')
-        console.log(this.previews, 'previews')
-        console.log(this.options, 'options')
+        this.submitLoading = true
         const options: any = this.options
         const fileName =
           (options.name ? options.name.split('.')[0] : +new Date()) + '.png'
-        // let form = new FormData()
-        // form.append('file', this.blobToFile(data, fileName), fileName)
         try {
           let key = new Date().getTime() + fileName
           let obj = {
@@ -104,21 +101,21 @@ export default defineComponent({
               unknowValueField: []
             }
           }
-          const { data } = await request.post('/api-website/getUploadSign', {
+          const res = await request.post('/api-website/getUploadSign', {
             data: obj
           })
           this.dataObj = {
-            policy: data.policy,
-            signature: data.signature,
+            policy: res.data.policy,
+            signature: res.data.signature,
             key: key,
-            KSSAccessKeyId: data.kssAccessKeyId,
+            KSSAccessKeyId: res.data.kssAccessKeyId,
             acl: 'public-read',
             name: fileName
           }
 
           let formData = new FormData()
-          for (let key in obj) {
-            formData.append(key, obj[key])
+          for (let key in this.dataObj) {
+            formData.append(key, this.dataObj[key])
           }
           formData.append('file', this.blobToFile(data, fileName), fileName)
           await umiRequest(this.ossUploadUrl, {
@@ -127,16 +124,13 @@ export default defineComponent({
           })
           console.log(this.ossUploadUrl + '/' + key)
           const uploadUrl = this.ossUploadUrl + '/' + key
-
-          // const res = await uploadFile(form)
-          // this.$emit('cropper-ok', res)
+          this.cropperOk(uploadUrl)
         } catch (err: any) {
           ElMessage.error(err)
         } finally {
+          this.submitLoading = false
           this.cancelHandel()
         }
-        // this.cropperOk(data)
-        // this.$emit('cropper-ok', data)
       })
     },
     //转成blob
@@ -187,8 +181,17 @@ export default defineComponent({
         v-slots={{
           footer: () => (
             <span class="dialog-footer !text-center block">
-              <ElButton onClick={this.cancelHandel}>取消</ElButton>
-              <ElButton type="primary" onClick={this.okHandel}>
+              <ElButton
+                onClick={this.cancelHandel}
+                disabled={this.submitLoading}
+              >
+                取消
+              </ElButton>
+              <ElButton
+                type="primary"
+                onClick={this.okHandel}
+                loading={this.submitLoading}
+              >
                 保 存
               </ElButton>
             </span>

+ 13 - 15
src/components/col-cropper/index.tsx

@@ -1,23 +1,16 @@
-import { ElIcon, ElImage, ElLoading, ElMessage, ElUpload } from 'element-plus'
-import { defineComponent, PropType } from 'vue'
-import { Document } from '@element-plus/icons-vue'
+import { ElImage, ElMessage, ElUpload } from 'element-plus'
+import { defineComponent } from 'vue'
 import styles from './index.module.less'
 import iconUpload from '../col-upload/images/icon_upload.png'
-import request from '@/helpers/request'
 import Cropper from './cropper'
 
 export default defineComponent({
-  name: 'col-upload',
+  name: 'col-cropper',
   props: {
     modelValue: {
       type: String,
       default: ''
     },
-    cropper: {
-      // 是否进行裁切
-      type: Boolean,
-      default: false
-    },
     options: {
       // 裁切需要参数
       type: Object,
@@ -59,11 +52,14 @@ export default defineComponent({
     extraTips: {
       type: String,
       default: '图片最大不能超过5MB'
+    },
+    cropUploadSuccess: {
+      type: Function,
+      default: (data: string) => {}
     }
   },
   data() {
     return {
-      fileList: [] as any,
       isStopRun: false,
       loading: false
     }
@@ -75,7 +71,6 @@ export default defineComponent({
     },
     //从本地选择文件
     async handleChange(info: any) {
-      // console.log(info)
       if (this.isStopRun) {
         return
       }
@@ -117,14 +112,17 @@ export default defineComponent({
       this.loading = false
     },
     remove() {
-      this.fileList = []
       this.onDelete()
     },
     //获取服务器返回的地址
-    handleCropperSuccess(data) {
+    handleCropperSuccess(data: any) {
       //将返回的数据回显
       this.loading = false
-      this.$emit('crop-upload-success', data)
+      console.log(data, 'success')
+      this.$emit('update:modelValue', data)
+      // this.cropUploadSuccess(data)
+      // this.$emit('cropUploadSuccess', data)
+      // console.log(this.modelValue, 'modelValue')
     },
     // 取消上传
     handleCropperClose() {

BIN
src/components/col-upload-video/images/icon_rate.png


BIN
src/components/col-upload-video/images/icon_upload.png


BIN
src/components/col-upload-video/images/icon_video.png


+ 60 - 0
src/components/col-upload-video/index.module.less

@@ -0,0 +1,60 @@
+.uploadSection {
+  width: 156px;
+  height: 106px;
+  background: #f8faf9;
+  border-radius: 4px;
+  border: 1px solid rgba(45, 199, 170, 0.26);
+  font-size: 14px;
+  color: #2dc7aa;
+  line-height: 20px;
+}
+
+.uploadFile {
+  width: 100%;
+  min-width: 300px;
+  height: 40px;
+  border: 1px solid rgba(142, 142, 142, 0.26);
+  border-radius: 4px;
+  display: flex;
+  align-items: center;
+  padding: 0 15px;
+  color: var(--el-text-color-regular);
+  :global {
+    .el-icon {
+      margin-right: 5px;
+    }
+  }
+}
+
+.fileUpload {
+  :global {
+    .el-upload--text {
+      @apply w-full;
+    }
+
+    .el-loading-spinner {
+      display: flex;
+      align-items: center;
+      height: 42px !important;
+      justify-content: center;
+      margin-top: -20px !important;
+      svg {
+        width: 20px;
+        height: 20px;
+        margin-right: 5px;
+      }
+    }
+  }
+}
+
+.uploadClass {
+  height: 106px;
+  width: 100%;
+
+  :global {
+    .el-loading-spinner {
+      margin-top: -43px;
+      height: 106px;
+    }
+  }
+}

+ 210 - 0
src/components/col-upload-video/index.tsx

@@ -0,0 +1,210 @@
+import {
+  ElButton,
+  ElIcon,
+  ElImage,
+  ElLoading,
+  ElMessage,
+  ElUpload
+} from 'element-plus'
+import { defineComponent, PropType } from 'vue'
+import { Document } from '@element-plus/icons-vue'
+import styles from './index.module.less'
+import iconVideo from './images/icon_video.png'
+import request from '@/helpers/request'
+
+export default defineComponent({
+  name: 'col-upload-video',
+  props: {
+    modelValue: {
+      type: String,
+      default: ''
+    },
+    disabled: {
+      type: Boolean,
+      default: false
+    },
+    bucket: {
+      type: String,
+      default: 'daya'
+    },
+    multiple: {
+      // 是否支持多文件上传
+      type: Boolean,
+      default: false
+    },
+    size: {
+      type: Number,
+      default: 50 // 默认5M
+    },
+    accept: {
+      type: String,
+      // ,.mov,.avi,.flv,.wmv,.mpg,.mpeg,.mpe,.mp3,.wav,.wma,.aac,.m4a,.m4r,.m4v,.3gp,.3g2,.mkv,.webm,.mov,.qt,.mxf,.asf,.asx,.rm,.ram,.rmvb
+      default: '.mp4'
+    },
+    tips: {
+      type: String,
+      default: '请上传视频'
+    },
+    extraTips: {
+      type: String,
+      default: '视频最大不能超过50MB'
+    }
+  },
+  data() {
+    return {
+      ossUploadUrl: 'https://ks3-cn-beijing.ksyuncs.com/' + this.bucket,
+      dataObj: {
+        policy: '',
+        signature: '',
+        key: '',
+        KSSAccessKeyId: '',
+        acl: 'public-read',
+        name: ''
+      },
+      fileList: [] as any,
+      loading: null as any
+    }
+  },
+  methods: {
+    handleSuccess() {
+      this.loading?.close()
+      let url = this.ossUploadUrl + '/' + this.dataObj.key
+
+      this.$emit('update:modelValue', url)
+    },
+    handleRemove() {
+      console.log('remove')
+    },
+    handleChange() {
+      console.log('handleChange')
+    },
+    handleProgress() {
+      console.log('handleProgress')
+    },
+    handleError() {
+      this.loading?.close()
+    },
+    async beforeUpload(file: any) {
+      // beforeUpload
+      console.log(file)
+      // let fileType = true
+      // if (props.rules.type && props.rules.type.length > 0) {
+      //   const fileExtension = file.name.split('.').pop().toUpperCase()
+      //   console.log(
+      //     props.rules.type,
+      //     fileExtension,
+      //     props.rules.type.indexOf(fileExtension) != -1
+      //   )
+      //   if (props.rules.type.indexOf(fileExtension) != -1) {
+      //     fileType = true
+      //   } else {
+      //     fileType = false
+      //     ElMessage.error('请上传正确的文件!')
+      //     return false
+      //   }
+      // }
+
+      let isLt2M = true
+      if (this.size) {
+        isLt2M = file.size / 1024 / 1024 < this.size
+        if (!isLt2M) {
+          ElMessage.error(`文件大小不能超过${this.size}M!`)
+          return false
+        }
+      }
+      this.loading = ElLoading.service({
+        target: this.$refs.uploadDom as HTMLElement,
+        lock: true,
+        fullscreen: false,
+        text: '上传中...',
+        background: 'rgba(0, 0, 0, 0.7)'
+      })
+      console.log(this.loading)
+      try {
+        let fileName = file.name.replaceAll(' ', '_')
+        let key = new Date().getTime() + fileName
+        let obj = {
+          filename: fileName,
+          bucketName: this.bucket,
+          postData: {
+            filename: fileName,
+            acl: 'public-read',
+            key: key,
+            unknowValueField: []
+          }
+        }
+        const { data } = await request.post('/api-website/getUploadSign', {
+          data: obj
+        })
+        this.dataObj = {
+          policy: data.policy,
+          signature: data.signature,
+          key: key,
+          KSSAccessKeyId: data.kssAccessKeyId,
+          acl: 'public-read',
+          name: fileName
+        }
+      } catch (e) {
+        this.loading.close()
+      }
+    },
+    fileName(name = '') {
+      return name.split('/').pop()
+    },
+    handleExceed() {}
+  },
+  render() {
+    return (
+      <div class={[styles.colUpload, 'w-full']}>
+        <ElUpload
+          disabled={this.disabled}
+          action={this.ossUploadUrl}
+          data={this.dataObj}
+          onSuccess={this.handleSuccess}
+          onRemove={this.handleRemove}
+          onChange={this.handleChange}
+          onProgress={this.handleProgress}
+          onError={this.handleError}
+          fileList={this.fileList}
+          showFileList={false}
+          accept={this.accept}
+          beforeUpload={this.beforeUpload}
+          onExceed={this.handleExceed}
+          ref="uploadRef"
+        >
+          <div
+            ref="uploadDom"
+            class={[styles.uploadClass, 'w-full']}
+            style={{ height: '106px' }}
+          >
+            {this.modelValue ? (
+              <ElImage
+                src={this.modelValue}
+                fit="cover"
+                class={styles.uploadSection}
+              />
+            ) : this.multiple ? (
+              <ElButton size="large" type="primary">
+                点击上传
+              </ElButton>
+            ) : (
+              <div
+                class={[
+                  styles.uploadSection,
+                  'flex items-center flex-col justify-center'
+                ]}
+              >
+                <img src={iconVideo} class="w-8 h-7 mb-3" />
+                <p>{this.tips}</p>
+              </div>
+            )}
+          </div>
+        </ElUpload>
+
+        {!this.multiple && (
+          <p class="text-3 text-[#999999] leading-6 pt-1">{this.extraTips}</p>
+        )}
+      </div>
+    )
+  }
+})

+ 68 - 4
src/views/user-info/video-operation/course-content/index.tsx

@@ -1,4 +1,13 @@
-import { ElButton, ElForm, ElFormItem, ElInput } from 'element-plus'
+import ColUpload from '@/components/col-upload'
+import ColUploadVideo from '@/components/col-upload-video'
+import {
+  ElButton,
+  ElCol,
+  ElForm,
+  ElFormItem,
+  ElInput,
+  ElRow
+} from 'element-plus'
 import { defineComponent } from 'vue'
 import { createState } from '../createState'
 
@@ -12,16 +21,71 @@ export default defineComponent({
           size="large"
           labelWidth={'90px'}
           labelPosition="left"
+          model={createState.lessonList}
         >
-          <ElFormItem label="课程名称">
-            <ElInput placeholder="请输入课程名称" />
+          <ElFormItem label="课程视频">
+            <ColUploadVideo multiple />
+            {/* <ElInput placeholder="请输入课程名称" /> */}
           </ElFormItem>
+          {createState.lessonList.map((item: any, index: number) => (
+            <div class="p-4 rounded-xl border border-dashed border-gray-300">
+              <ElRow>
+                <ElCol span={12}>
+                  <ElFormItem label="第一课"></ElFormItem>
+                </ElCol>
+                <ElCol span={12}>
+                  <ElFormItem
+                    label="视频封面"
+                    prop={`lessonList.${index}.coverUrl`}
+                  >
+                    <ColUpload v-model:modelValue={item.coverUrl} />
+                  </ElFormItem>
+                </ElCol>
+              </ElRow>
+              <ElFormItem
+                label="课程标题"
+                prop={`lessonList.${index}.videoTitle`}
+                rules={[
+                  {
+                    required: true,
+                    message: '请输入课程标题'
+                  }
+                ]}
+              >
+                <ElInput
+                  placeholder="请输入课程标题"
+                  v-model={item.videoTitle}
+                />
+              </ElFormItem>
+
+              <ElFormItem
+                label="课程介绍"
+                prop={`lessonList.${index}.videoContent`}
+                rules={[
+                  {
+                    required: true,
+                    message: '请输入课程介绍'
+                  }
+                ]}
+              >
+                <ElInput
+                  placeholder="请输入课程介绍"
+                  v-model={item.videoContent}
+                  type="textarea"
+                  // @ts-ignore
+                  maxlength={200}
+                  rows={4}
+                  showWordLimit
+                />
+              </ElFormItem>
+            </div>
+          ))}
         </ElForm>
         <div class="border-t border-t-[#E5E5E5] text-center pt-6 pb-7">
           <ElButton
             class="!w-40 !h-[38px]"
             onClick={() => {
-              createState.active = 1
+              createState.active = 0
             }}
           >
             上一步

+ 108 - 38
src/views/user-info/video-operation/course-info/index.tsx

@@ -1,4 +1,5 @@
 import ColCropper from '@/components/col-cropper'
+import ColUpload from '@/components/col-upload'
 import request from '@/helpers/request'
 import { verifyNumberIntegerAndFloat } from '@/helpers/toolsValidate'
 import {
@@ -21,6 +22,12 @@ import { createState } from '../createState'
 
 export default defineComponent({
   name: 'course-info',
+  data() {
+    return {
+      url: '',
+      calcRatePrice: 0 as any
+    }
+  },
   computed: {
     choiceSubjectIds() {
       // 选择的科目编号
@@ -42,13 +49,13 @@ export default defineComponent({
         }
       })
       return tempStr
-    },
-    calcRatePrice() {
-      // 计算手续费
-      let rate = createState.rate || 0
-      let price = createState.lessonGroup.lessonPrice || 0
-      return (price - (rate / 100) * price).toFixed(2)
     }
+    // calcRatePrice() {
+    //   // 计算手续费
+    //   let rate = createState.rate || 0
+    //   let price = createState.lessonGroup.lessonPrice || 0
+    //   return (price - (rate / 100) * price).toFixed(2)
+    // }
   },
   async mounted() {
     try {
@@ -56,6 +63,13 @@ export default defineComponent({
         const res = await request.post('/api-website/teacher/querySubject')
         createState.subjectList = res.data || []
       }
+
+      // setInterval(() => {
+      //   console.log(
+      //     createState.lessonGroup.lessonCoverTemplateUrl,
+      //     'createState.lessonGroup.lessonCoverTemplateUrl'
+      //   )
+      // }, 1000)
     } catch {
       //
     }
@@ -65,12 +79,17 @@ export default defineComponent({
     //   createState.lessonGroup.lessonSubject = id
     //   this.subjectStatus = false
     // },
-    onFormatter(val: any) {
-      return verifyNumberIntegerAndFloat(val)
+    onFormatter(e: any) {
+      e.target.value = verifyNumberIntegerAndFloat(e.target.value)
+
+      // 计算手续费
+      let rate = createState.rate || 0
+      let price = e.target.value || 0
+      this.calcRatePrice = (price - (rate / 100) * price).toFixed(2)
     },
     tabChange(name: number) {
-      ;(this as any).$refs.form.resetValidation('lessonCoverTemplateUrl')
-      ;(this as any).$refs.form.resetValidation('lessonCoverUrl')
+      ;(this as any).$refs.form.clearValidate('lessonCoverTemplateUrl')
+      ;(this as any).$refs.form.clearValidate('lessonCoverUrl')
       createState.tabIndex = name
     },
     selectImg(val: string) {
@@ -84,6 +103,7 @@ export default defineComponent({
         <ElForm
           class="px-[200px] pb-10 pt-7"
           size="large"
+          ref="form"
           labelWidth={'100px'}
           labelPosition="left"
           model={createState.lessonGroup}
@@ -109,20 +129,19 @@ export default defineComponent({
             rules={[
               {
                 required: true,
-                message: '请选择课程声部',
-                trigger: 'change'
+                message: '请选择课程声部'
               }
             ]}
           >
-            {/* <ElSelect
+            <ElSelect
               class="w-full"
               v-model={createState.lessonGroup.lessonSubject}
               placeholder="请选择课程声部"
             >
               {createState.subjectList.map((item: any) => (
-                <ElOption value={item.id}>{item.name}</ElOption>
+                <ElOption key={item.id} value={item.id} label={item.name} />
               ))}
-            </ElSelect> */}
+            </ElSelect>
           </ElFormItem>
           <ElFormItem
             label="课程介绍"
@@ -157,7 +176,8 @@ export default defineComponent({
             <ElInput
               placeholder="请输入课程组售价"
               v-model={createState.lessonGroup.lessonPrice}
-              formatter={this.onFormatter}
+              // @ts-ignore
+              onKeyup={this.onFormatter}
               v-slots={{
                 append: () => <span class="text-base text-[#333]">元</span>
               }}
@@ -172,38 +192,88 @@ export default defineComponent({
             <p>您的课程收入将在课程结束后结算到您的账户中 </p>
           </div>
           <ElFormItem label="课程封面" class="!mb-0">
-            <ElTabs v-model={createState.tabIndex}>
+            <ElTabs
+              v-model={createState.tabIndex}
+              onTab-change={(name: any) => {
+                this.tabChange(name)
+              }}
+            >
               <ElTabPane label="图片模板" name={1}></ElTabPane>
               <ElTabPane label="自定义模板" name={2}></ElTabPane>
             </ElTabs>
           </ElFormItem>
-          <ElFormItem>
-            {/* <ElRadioGroup>
-              <ElRow>
-                {createState.templateList.map((item: any) => (
-                  <ElCol span={10} class="mb-3 cursor-pointer">
-                    <div class="w-40 relative rounded-xl overflow-hidden border">
-                      <ElImage src={item} class="align-middle" />
-                      <ElRadio
-                        label={item}
-                        class="!absolute bottom-2 right-0 !h-auto"
-                      >
-                        {''}
-                      </ElRadio>
-                    </div>
-                  </ElCol>
-                ))}
-              </ElRow>
-            </ElRadioGroup> */}
-            <ColCropper />
-          </ElFormItem>
+
+          {createState.tabIndex === 1 && (
+            <ElFormItem
+              prop="lessonCoverUrl"
+              rules={[
+                {
+                  required: true,
+                  message: '请上传课程封面'
+                }
+              ]}
+            >
+              <ElRadioGroup v-model={createState.lessonGroup.lessonCoverUrl}>
+                <ElRow>
+                  {createState.templateList.map((item: any) => (
+                    <ElCol span={10} class="mb-3 cursor-pointer">
+                      <div class="w-40 relative rounded-xl overflow-hidden border">
+                        <ElImage src={item} class="align-middle" />
+                        <ElRadio
+                          label={item}
+                          class="!absolute bottom-2 right-0 !h-auto"
+                        >
+                          {''}
+                        </ElRadio>
+                      </div>
+                    </ElCol>
+                  ))}
+                </ElRow>
+              </ElRadioGroup>
+            </ElFormItem>
+          )}
+
+          {createState.tabIndex === 2 && (
+            <ElFormItem
+              prop="lessonCoverTemplateUrl"
+              rules={[
+                {
+                  required: true,
+                  message: '请上传课程封面',
+                  trigger: 'change'
+                }
+              ]}
+            >
+              <ColUpload
+                v-model:modelValue={
+                  createState.lessonGroup.lessonCoverTemplateUrl
+                }
+              />
+            </ElFormItem>
+          )}
+
+          {/* <ColCropper
+              v-model:modelValue={
+                createState.lessonGroup.lessonCoverTemplateUrl
+              }
+              bucket="video-course"
+              options={{
+                fixedNumber: [3, 2],
+                autoCropWidth: 300,
+                autoCropHeight: 200
+              }}
+            /> */}
         </ElForm>
         <div class="border-t border-t-[#E5E5E5] text-center pt-6 pb-7">
           <ElButton
             type="primary"
             class="!w-40 !h-[38px]"
             onClick={() => {
-              createState.active = 1
+              ;(this as any).$refs.form.validate(async (valid: boolean) => {
+                if (valid) {
+                  createState.active = 1
+                }
+              })
             }}
           >
             下一步

+ 8 - 8
src/views/user-info/video-operation/createState.tsx

@@ -2,7 +2,7 @@ import { reactive } from 'vue'
 
 export const createState = reactive({
   groupId: 0,
-  active: 0,
+  active: 1,
   tabIndex: 1,
   loadingStatus: false,
   rate: 0, // 手续费
@@ -23,12 +23,12 @@ export const createState = reactive({
     lessonCoverTemplateUrl: ''
   },
   lessonList: [
-    {
-      videoTitle: '',
-      videoContent: '',
-      videoUrl: '',
-      coverUrl: '',
-      posterUrl: '' // 视频封面图
-    }
+    // {
+    //   videoTitle: '',
+    //   videoContent: '',
+    //   videoUrl: '',
+    //   coverUrl: '',
+    //   posterUrl: '' // 视频封面图
+    // }
   ]
 })