Browse Source

更新组件

lex-xin 2 years ago
parent
commit
d52d9c96a9

+ 9 - 0
README.md

@@ -52,6 +52,15 @@ See [Configuration Reference](https://vitejs.dev/config/).
 
 
 ### 公用尺寸
 ### 公用尺寸
 1、所有的图片和视屏尺寸统一 3:2
 1、所有的图片和视屏尺寸统一 3:2
+2、所有的金额四舍五入保留两位小数
+
+### native-message api
+postMessage({ api: 'chooseFile', content: {
+  type: 'img' | 'video' | 'file' | 'midi' | 'mp3' | 'xml',
+  max: 1
+}}, (res) => {
+  <!-- fileUrl。 如果是视频 firstFrameImg -->
+})
 
 
 ### Rem Unit (default)
 ### Rem Unit (default)
 
 

+ 1 - 0
package.json

@@ -21,6 +21,7 @@
     "generate": "plop"
     "generate": "plop"
   },
   },
   "dependencies": {
   "dependencies": {
+    "@vant/use": "^1.3.6",
     "clean-deep": "^3.4.0",
     "clean-deep": "^3.4.0",
     "dayjs": "^1.10.7",
     "dayjs": "^1.10.7",
     "loaders.css": "^0.1.2",
     "loaders.css": "^0.1.2",

+ 94 - 0
src/business-components/course-plan-step/index.module.less

@@ -0,0 +1,94 @@
+.col-steps {
+  padding: 0 0 0 28px;
+  overflow: hidden;
+  background-color: #fff;
+
+  .col-step {
+    display: block;
+    float: none;
+    padding: 10px 0;
+    line-height: 18px;
+    position: relative;
+    flex: 1;
+    color: #7a7a7a;
+    font-size: 13px;
+
+    &:last-child .col-step__line {
+      border: 0;
+    }
+  }
+
+  .col-step_circle {
+    position: absolute;
+    top: 19px;
+    left: -18px;
+    z-index: 1;
+    width: 18px;
+    height: 18px;
+    font-size: 12px;
+    border-radius: 50%;
+    background: var(--van-primary);
+    transform: translate(-50%, -50%);
+    text-align: center;
+    line-height: 20px;
+    color: #fff;
+  }
+
+  .col-step__line {
+    top: 16px;
+    left: -19px;
+    width: 0px;
+    border-left: 1px dashed var(--van-primary);
+    height: 100%;
+    position: absolute;
+    transform: background-color 0.3s;
+  }
+
+  .stepSection {
+    .stepTitle {
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      font-size: 13px;
+      color: #999;
+
+      .stepTitleNum {
+        font-size: 14px;
+        color: var(--van-primary);
+        font-weight: 500;
+      }
+    }
+    .stepContent {
+      padding-top: 10px;
+      font-size: 13px;
+      color: #7a7a7a;
+      line-height: 20px;
+    }
+  }
+
+  .videoImg {
+    margin-top: 10px;
+    width: 150px;
+    height: 100px;
+    position: relative;
+    border-radius: 4px;
+    overflow: hidden;
+    :global {
+      .van-image {
+        width: inherit;
+        height: inherit;
+      }
+    }
+
+    .videoStop {
+      position: absolute;
+      top: 0;
+      right: 0;
+      left: 0;
+      bottom: 0;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+    }
+  }
+}

+ 37 - 0
src/business-components/course-plan-step/index.tsx

@@ -0,0 +1,37 @@
+import { defineComponent } from "vue";
+import styles from './index.module.less';
+import { Icon, Image } from "vant";
+import videoStop from '@common/images/icon_video_stop.png';
+
+export default defineComponent({
+  name: "CoursePlanStep",
+  render() {
+    return (
+      <div class={styles['col-steps']}>
+        <div class={styles['col-steps__items']}>
+          {[1, 2].map((item: any, index: number) => (
+            <div class={styles['col-step']}>
+              <div class={styles['col-step__title']}>
+                {this.$slots.content ? this.$slots.content() : <div class={styles.stepSection}>
+                  <div class={styles.stepTitle}>
+                    <span class={styles.stepTitleNum}>第 {index + 1} 课时</span>
+                    <span class={styles.stepTitleText}>2022-4-1 11:14:46</span>
+                  </div>
+                  <div class={styles.stepContent}>
+                    <p>小酷老师带您零基础学习竖笛,通过4节课的学习掌握竖笛演奏的基本方式,培养娘好的吐息习惯。</p>
+                    <div class={styles.videoImg}>
+                      <Image src='https://daya.ks3-cn-beijing.ksyun.com/202201/SvB6tqR.png' fit="cover" />
+                      <Icon class={styles.videoStop} name={videoStop} size={32} />
+                    </div>
+                  </div>
+                </div>}
+              </div>
+              <div class={styles['col-step_circle']}>{index + 1}</div>
+              <div class={styles['col-step__line']}></div>
+            </div>
+          ))}
+        </div>
+      </div>
+    )
+  }
+})

+ 2 - 2
src/business-components/course-video-item/index.tsx

@@ -11,7 +11,7 @@ import videoStop from '@common/images/icon_video_stop.png';
  * @param {content} 视频内容
  * @param {content} 视频内容
  */
  */
 interface IDetail {
 interface IDetail {
-  id: number;
+  id?: number;
   title: string;
   title: string;
   content?: string;
   content?: string;
   imgUrl?: string;
   imgUrl?: string;
@@ -26,7 +26,7 @@ export default defineComponent({
     },
     },
     onPlay: {
     onPlay: {
       type: Function as PropType<(detail: IDetail) => void>,
       type: Function as PropType<(detail: IDetail) => void>,
-      default: () => {}
+      default: () => { }
     }
     }
   },
   },
   render() {
   render() {

+ 4 - 2
src/business-components/subject-list/index.module.less

@@ -76,13 +76,15 @@
     }
     }
 
 
     :global {
     :global {
-      .van-checkbox__icon {
+      .van-checkbox__icon,
+      .van-radio__icon {
         height: 22px;
         height: 22px;
         .van-icon {
         .van-icon {
           border: 0;
           border: 0;
         }
         }
       }
       }
-      .van-checkbox__icon--checked .van-icon {
+      .van-checkbox__icon--checked .van-icon,
+      .van-radio__icon--checked .van-icon {
         background: transparent;
         background: transparent;
         border: transparent;
         border: transparent;
       }
       }

+ 64 - 29
src/business-components/subject-list/index.tsx

@@ -1,5 +1,5 @@
-import { Button, Checkbox, CheckboxGroup, Icon, Image, Loading } from "vant";
-import { defineComponent } from "vue";
+import { Button, Checkbox, CheckboxGroup, Icon, Image, Loading, Radio, RadioGroup } from "vant";
+import { defineComponent, PropType } from "vue";
 import styles from './index.module.less';
 import styles from './index.module.less';
 
 
 import checkBoxActive from '@/teacher/teacher-cert/images/checkbox_active.png';
 import checkBoxActive from '@/teacher/teacher-cert/images/checkbox_active.png';
@@ -24,12 +24,17 @@ export default defineComponent({
     max: {// 最多可选数量
     max: {// 最多可选数量
       type: Number,
       type: Number,
       default: 5
       default: 5
+    },
+    selectType: {// 选择类型,Radio:单选,Checkbox:多选
+      type: String as PropType<'Checkbox' | 'Radio'>,
+      default: 'Checkbox'
     }
     }
   },
   },
   data() {
   data() {
     return {
     return {
       checkBox: [],
       checkBox: [],
       checkboxRefs: [] as any,
       checkboxRefs: [] as any,
+      radio: null as any, // 单选
     }
     }
   },
   },
   async mounted() {
   async mounted() {
@@ -37,41 +42,71 @@ export default defineComponent({
   },
   },
   methods: {
   methods: {
     onSelect(id: number) {
     onSelect(id: number) {
-      this.checkboxRefs[id].toggle();
+      if (this.selectType === 'Checkbox') {
+        this.checkboxRefs[id].toggle();
+      } else if (this.selectType === 'Radio') {
+        this.radio = id;
+      }
     }
     }
   },
   },
   render() {
   render() {
     return (
     return (
       <div class={styles.subjects}>
       <div class={styles.subjects}>
-        {this.subjectList.length ? <CheckboxGroup v-model={this.checkBox} max={this.max}>
-          {this.subjectList.map((item: any) => (
-            item.subjects && item.subjects.length > 0 ? <>
-              <div class={styles.title}>{item.name}</div>
-              <div class={styles['subject-list']}>
-                {item.subjects && item.subjects.map((sub: any) => (
-                  <div class={styles['subject-item']} onClick={() => this.onSelect(sub.id)}>
-                    <Image src={sub.img} width="100%" height="100%" fit="cover" v-slots={{
-                      loading: () => <Loading type="spinner" size={20} />
-                    }} />
-                    <div class={styles.topBg}>
-                      <Checkbox name={sub.id} class={styles.checkbox} ref={(el: any) => this.checkboxRefs[sub.id] = el} v-slots={{
-                        icon: (props: any) => (
-                          <Icon name={props.checked ? checkBoxActive : checkBoxDefault} size="22" />
-                        )
+        {this.subjectList.length ? (
+          this.selectType === 'Checkbox' ? <CheckboxGroup v-model={this.checkBox} max={this.max}>
+            {this.subjectList.map((item: any) => (
+              item.subjects && item.subjects.length > 0 ? <>
+                <div class={styles.title}>{item.name}</div>
+                <div class={styles['subject-list']}>
+                  {item.subjects && item.subjects.map((sub: any) => (
+                    <div class={styles['subject-item']} onClick={() => this.onSelect(sub.id)}>
+                      <Image src={sub.img} width="100%" height="100%" fit="cover" v-slots={{
+                        loading: () => <Loading type="spinner" size={20} />
+                      }} />
+                      <div class={styles.topBg}>
+                        <Checkbox name={sub.id} class={styles.checkbox} ref={(el: any) => this.checkboxRefs[sub.id] = el} v-slots={{
+                          icon: (props: any) => (
+                            <Icon name={props.checked ? checkBoxActive : checkBoxDefault} size="22" />
+                          )
+                        }} />
+                        <p class={styles.name}>{sub.name}</p>
+                      </div>
+                    </div>
+                  ))}
+                </div>
+              </> : null
+            ))}
+          </CheckboxGroup> : <RadioGroup v-model={this.radio} >
+            {this.subjectList.map((item: any) => (
+              item.subjects && item.subjects.length > 0 ? <>
+                <div class={styles.title}>{item.name}</div>
+                <div class={styles['subject-list']}>
+                  {item.subjects && item.subjects.map((sub: any) => (
+                    <div class={styles['subject-item']} onClick={() => this.onSelect(sub.id)}>
+                      <Image src={sub.img} width="100%" height="100%" fit="cover" v-slots={{
+                        loading: () => <Loading type="spinner" size={20} />
                       }} />
                       }} />
-                      <p class={styles.name}>{sub.name}</p>
+                      <div class={styles.topBg}>
+                        <Radio name={sub.id} class={styles.checkbox} v-slots={{
+                          icon: (props: any) => (
+                            <Icon name={props.checked ? checkBoxActive : checkBoxDefault} size="22" />
+                          )
+                        }} />
+                        <p class={styles.name}>{sub.name}</p>
+                      </div>
                     </div>
                     </div>
-                  </div>
-                ))}
-              </div>
-            </> : null
-          ))}
-        </CheckboxGroup> : <ColResult tips="暂无声部数据" btnStatus={false} />}
+                  ))}
+                </div>
+              </> : null
+            ))}
+          </RadioGroup>
+        ) : <ColResult tips="暂无声部数据" btnStatus={false} />
+        }
 
 
-        <div class={styles["btn-group"]}>
-          <Button round block type="primary" onClick={() => this.onChoice(this.checkBox)}>确定</Button>
-        </div>
-      </div>
+        < div class={styles["btn-group"]} >
+          <Button round block type="primary" onClick={() => this.onChoice(this.selectType === 'Checkbox' ? this.checkBox : this.radio)}>确定</Button>
+        </div >
+      </div >
     )
     )
   }
   }
 })
 })

+ 11 - 1
src/business-components/user-detail/index.module.less

@@ -27,7 +27,7 @@
     }
     }
 
 
     .buyNum {
     .buyNum {
-      color: #FF802C;
+      color: #ff802c;
       font-size: 14px;
       font-size: 14px;
       display: flex;
       display: flex;
       align-items: center;
       align-items: center;
@@ -41,6 +41,16 @@
       }
       }
     }
     }
 
 
+    .buyNumInfo {
+      font-size: 12px;
+      color: #6a6a6a;
+      display: flex;
+      align-items: center;
+      .iconBuy {
+        margin-right: 5px;
+      }
+    }
+
     .info {
     .info {
       font-size: 16px;
       font-size: 16px;
       font-weight: 400;
       font-weight: 400;

+ 38 - 26
src/business-components/user-detail/index.tsx

@@ -1,50 +1,62 @@
-import { userInfo } from "os";
-import { Cell, CellGroup, Image, Tag } from "vant";
-import { defineComponent } from "vue";
+import { Cell, CellGroup, Icon, Image, Tag } from "vant";
+import { defineComponent, PropType } from "vue";
 import styles from "./index.module.less";
 import styles from "./index.module.less";
 
 
+import iconUserNum from '@common/images/icon_user_num.png';
+
 /**
 /**
  * @description: 视频详情
  * @description: 视频详情
- * @param {type}
+ * @param {type} headUrl 头像
+ * @param {type} username 姓名
+ * @param {type} startTime 开始时间
+ * @param {type} buyNum 购买用户数
+ * @param {type} lessonPrice 价格
+ * @param {type} lessonCoverUrl 视频封面
+ * @param {type} lessonDesc 课程描述
+ * @param {type} lessonNum 课程数
+ * @param {type} lessonName 课程名称
  */
  */
 interface UserType {
 interface UserType {
   headUrl: string;
   headUrl: string;
   username: string;
   username: string;
-  startTime: string;
-  price: number;
-  classNum: number;
+  startTime?: string;
+  buyNum?: number;
+  lessonPrice: number;
+  lessonNum?: number;
+  lessonDesc?: string;
+  lessonCoverUrl: string;
+  lessonName: string;
 }
 }
 
 
 export default defineComponent({
 export default defineComponent({
   name: "user-detail",
   name: "user-detail",
   props: {
   props: {
-    userInfo: Object as () => UserType
+    userInfo: {
+      type: Object as PropType<UserType>,
+      required: true
+    },
+    showType: {
+      type: String as PropType<"BUY" | "TIME">,
+      default: "BUY"
+    }
   },
   },
   render() {
   render() {
     return (
     return (
       <div class={styles.userDetail}>
       <div class={styles.userDetail}>
-        <Image class={[styles.banner]} src="https://daya.ks3-cn-beijing.ksyun.com/202201/SvB6tqR.png" fit="cover" />
+        <Image class={[styles.banner]} src={this.userInfo.lessonCoverUrl} fit="cover" />
         <CellGroup class={styles.userInfo}>
         <CellGroup class={styles.userInfo}>
-          <Cell title="从0开始学竖笛视频课" titleClass={styles.userTitle} />
+          <Cell title={this.userInfo.lessonName} titleClass={styles.userTitle} />
           <Cell v-slots={{
           <Cell v-slots={{
-            icon: () => <Image class={styles.avatar} src="https://daya.ks3-cn-beijing.ksyun.com/202201/SvB6tqR.png" />,
-            title: () => (<div class={styles.name}>张星
-
-              <div class={styles.buyNum}>6人已购买</div>
-              {/* <Tag style={{ marginLeft: '8px' }} color="#FFF1DE" textColor="#FF9300">12课时</Tag> */}
+            icon: () => <Image class={styles.avatar} src={this.userInfo.headUrl} />,
+            title: () => (<div class={styles.name}>{this.userInfo.username}
+              {this.showType === "TIME" ? <Tag style={{ marginLeft: '8px' }} color="#FFF1DE" textColor="#FF9300">{this.userInfo.lessonNum}课时</Tag> : <div class={styles.buyNum}>{this.userInfo.buyNum}人已购买</div>}
             </div>),
             </div>),
-            value: () => (<div class={styles.info}>¥120/4课时</div>),
+            value: () => (
+              this.showType === "TIME" ? <div class={styles.buyNumInfo}>
+                <Icon name={iconUserNum} size={14} class={styles.iconBuy} /> 已购 {this.userInfo.buyNum} 人
+              </div> : <div class={styles.info}>¥{this.userInfo.lessonPrice}/{this.userInfo.lessonNum}课时</div>
+            ),
           }}></Cell>
           }}></Cell>
-          {/* <Cell v-slots={{
-            title: () => (<div class={styles.price}>
-              <div class={styles.number}>
-                <span class={styles.priceText}>¥</span>
-                <span class={styles.priceNum}>99</span>
-              </div>
-              
-            </div>),
-            value: () => (<div class={styles.info}>已购 6 人</div>),
-          }}></Cell> */}
         </CellGroup>
         </CellGroup>
       </div>
       </div>
     )
     )

BIN
src/common/images/icon_user_num.png


+ 4 - 2
src/components/col-upload-video/index.module.less

@@ -35,7 +35,8 @@
       display: flex;
       display: flex;
       justify-content: center;
       justify-content: center;
     }
     }
-    .van-uploader__wrapper, .van-uploader__input-wrapper {
+    .van-uploader__wrapper,
+    .van-uploader__input-wrapper {
       display: flex;
       display: flex;
       align-items: center;
       align-items: center;
       justify-content: center;
       justify-content: center;
@@ -49,6 +50,7 @@
     align-items: center;
     align-items: center;
     justify-content: center;
     justify-content: center;
     flex-direction: column;
     flex-direction: column;
+    height: 100%;
     .uploaderText {
     .uploaderText {
       font-size: 14px;
       font-size: 14px;
       color: #999999;
       color: #999999;
@@ -62,4 +64,4 @@
     // border-radius: 10px;
     // border-radius: 10px;
     overflow: hidden;
     overflow: hidden;
   }
   }
-}
+}

+ 21 - 3
src/components/col-upload-video/index.tsx

@@ -2,8 +2,11 @@ import request from "@/helpers/request";
 import { Icon, Toast, Uploader, Image } from "vant";
 import { Icon, Toast, Uploader, Image } from "vant";
 import { defineComponent } from "vue";
 import { defineComponent } from "vue";
 import styles from "./index.module.less";
 import styles from "./index.module.less";
+import { useCustomFieldValue } from '@vant/use';
+import { browser } from "@/helpers/utils";
 
 
 import iconUploader from '@common/images/icon_uploader_video.png';
 import iconUploader from '@common/images/icon_uploader_video.png';
+import { postMessage } from "@/helpers/native-message";
 
 
 export default defineComponent({
 export default defineComponent({
   name: "ColUploadVideo",
   name: "ColUploadVideo",
@@ -13,6 +16,10 @@ export default defineComponent({
       type: String,
       type: String,
       default: '点击上传'
       default: '点击上传'
     },
     },
+    nativeUpload: { // 是否使用原生上传, 且当前环境为app才会生效
+      type: Boolean,
+      default: true
+    },
     size: {
     size: {
       type: Number,
       type: Number,
       default: 30
       default: 30
@@ -54,19 +61,30 @@ export default defineComponent({
       this.$emit('update:modelValue', null);
       this.$emit('update:modelValue', null);
       e.stopPropagation();
       e.stopPropagation();
     },
     },
+    onNativeUpload() {
+      postMessage({ api: 'chooseFile', content: { type: 'video' } }, (res: any) => {
+        this.$emit('update:modelValue', res.fileUrl);
+      })
+    }
   },
   },
   render() {
   render() {
+    useCustomFieldValue(() => this.modelValue);
     return (
     return (
       <div class={styles['uploader-section']}>
       <div class={styles['uploader-section']}>
-          {/* @ts-ignore */}
+        {this.modelValue && this.deletable ? <Icon name="cross" onClick={this.onClose} class={styles["img-close"]} /> : null}
+        {browser().isApp && this.nativeUpload ? <div onClick={this.onNativeUpload}>{this.modelValue ? <video class={styles.uploadImg} src={this.modelValue} /> : <div class={styles.uploader}>
+          <Icon name={iconUploader} size="32" />
+          <p class={styles.uploaderText}>{this.tips}</p>
+        </div>}</div> : <>{/* @ts-ignore */}
           <Uploader accept=".mp4" afterRead={this.afterRead} beforeRead={this.beforeRead} beforeDelete={this.beforeDelete}
           <Uploader accept=".mp4" afterRead={this.afterRead} beforeRead={this.beforeRead} beforeDelete={this.beforeDelete}
             v-slots={{
             v-slots={{
-              default: () => (this.modelValue ? <Image fit="cover" position="center" class={styles.uploadImg} src={this.modelValue} /> : <div class={styles.uploader}>
+              default: () => (this.modelValue ? <video class={styles.uploadImg} src={this.modelValue} /> : <div class={styles.uploader}>
                 <Icon name={iconUploader} size="32" />
                 <Icon name={iconUploader} size="32" />
                 <p class={styles.uploaderText}>{this.tips}</p>
                 <p class={styles.uploaderText}>{this.tips}</p>
               </div>)
               </div>)
             }}
             }}
-          />
+          /></>}
+
       </div>
       </div>
     )
     )
   }
   }

+ 2 - 3
src/components/col-upload/index.tsx

@@ -2,6 +2,7 @@ import { Icon, Image, Toast, Uploader } from "vant";
 import { defineComponent } from "vue";
 import { defineComponent } from "vue";
 import styles from "./index.module.less";
 import styles from "./index.module.less";
 import ColCropper from "../col-cropper";
 import ColCropper from "../col-cropper";
+import { useCustomFieldValue } from '@vant/use';
 
 
 import iconUploader from '@common/images/icon_uploader.png';
 import iconUploader from '@common/images/icon_uploader.png';
 import request from "@/helpers/request";
 import request from "@/helpers/request";
@@ -27,9 +28,6 @@ export default defineComponent({
       default: {}
       default: {}
     }
     }
   },
   },
-  mounted() {
-    // console.log(this.modelValue, 'uploader');
-  },
   methods: {
   methods: {
     beforeRead(file: any) {
     beforeRead(file: any) {
       const isLt2M = file.size / 1024 / 1024 < 5
       const isLt2M = file.size / 1024 / 1024 < 5
@@ -76,6 +74,7 @@ export default defineComponent({
     }
     }
   },
   },
   render() {
   render() {
+    useCustomFieldValue(() => this.modelValue);
     return (
     return (
       <div class={styles['uploader-section']}>
       <div class={styles['uploader-section']}>
         {this.modelValue && this.deletable ? <Icon name="cross" onClick={this.onClose} class={styles["img-close"]} /> : null}
         {this.modelValue && this.deletable ? <Icon name="cross" onClick={this.onClose} class={styles["img-close"]} /> : null}

+ 393 - 0
src/helpers/toolsValidate.ts

@@ -0,0 +1,393 @@
+/* eslint-disable no-useless-escape */
+/**
+ * 验证百分比(不可以小数)
+ * @param val 当前值字符串
+ * @returns 返回处理后的字符串
+ */
+export function verifyNumberPercentage(val: string): string {
+  // 匹配空格
+  let v = val.replace(/(^\s*)|(\s*$)/g, '')
+  // 只能是数字和小数点,不能是其他输入
+  v = v.replace(/[^\d]/g, '')
+  // 不能以0开始
+  v = v.replace(/^0/g, '')
+  // 数字超过100,赋值成最大值100
+  v = v.replace(/^[1-9]\d\d{1,3}$/, '100')
+  // 返回结果
+  return v
+}
+
+/**
+ * 验证百分比(可以小数)
+ * @param val 当前值字符串
+ * @returns 返回处理后的字符串
+ */
+export function verifyNumberPercentageFloat(val: string): string {
+  let v = verifyNumberIntegerAndFloat(val)
+  // 数字超过100,赋值成最大值100
+  v = v.replace(/^[1-9]\d\d{1,3}$/, '100')
+  // 超过100之后不给再输入值
+  v = v.replace(/^100\.$/, '100')
+  // 返回结果
+  return v
+}
+
+/**
+ * 小数或整数(不可以负数)
+ * @param val 当前值字符串
+ * @returns 返回处理后的字符串
+ */
+export function verifyNumberIntegerAndFloat(val: string) {
+  // 匹配空格
+  let v = val.replace(/(^\s*)|(\s*$)/g, '')
+  // 只能是数字和小数点,不能是其他输入
+  v = v.replace(/[^\d.]/g, '')
+  // 以0开始只能输入一个
+  v = v.replace(/^0{2}$/g, '0')
+  // 保证第一位只能是数字,不能是点
+  v = v.replace(/^\./g, '')
+  // 小数只能出现1位
+  v = v.replace('.', '$#$').replace(/\./g, '').replace('$#$', '.')
+  // 小数点后面保留2位
+  v = v.replace(/^(\-)*(\d+)\.(\d\d).*$/, '$1$2.$3')
+  // 返回结果
+  return v
+}
+
+/**
+ * 正整数验证
+ * @param val 当前值字符串
+ * @returns 返回处理后的字符串
+ */
+export function verifiyNumberInteger(val: string) {
+  // 匹配空格
+  let v = val.replace(/(^\s*)|(\s*$)/g, '')
+  // 去掉 '.' , 防止贴贴的时候出现问题 如 0.1.12.12
+  v = v.replace(/[\.]*/g, '')
+  // 去掉以 0 开始后面的数, 防止贴贴的时候出现问题 如 00121323
+  v = v.replace(/(^0[\d]*)$/g, '0')
+  // 首位是0,只能出现一次
+  v = v.replace(/^0\d$/g, '0')
+  // 只匹配数字
+  v = v.replace(/[^\d]/g, '')
+  // 返回结果
+  return v
+}
+
+/**
+ * 去掉中文及空格
+ * @param val 当前值字符串
+ * @returns 返回处理后的字符串
+ */
+export function verifyCnAndSpace(val: string) {
+  // 匹配中文与空格
+  let v = val.replace(/[\u4e00-\u9fa5\s]+/g, '')
+  // 匹配空格
+  v = v.replace(/(^\s*)|(\s*$)/g, '')
+  // 返回结果
+  return v
+}
+
+/**
+ * 去掉英文及空格
+ * @param val 当前值字符串
+ * @returns 返回处理后的字符串
+ */
+export function verifyEnAndSpace(val: string) {
+  // 匹配英文与空格
+  let v = val.replace(/[a-zA-Z]+/g, '')
+  // 匹配空格
+  v = v.replace(/(^\s*)|(\s*$)/g, '')
+  // 返回结果
+  return v
+}
+
+/**
+ * 禁止输入空格
+ * @param val 当前值字符串
+ * @returns 返回处理后的字符串
+ */
+export function verifyAndSpace(val: string) {
+  // 匹配空格
+  const v = val.replace(/(^\s*)|(\s*$)/g, '')
+  // 返回结果
+  return v
+}
+
+/**
+ * 金额用 `,` 区分开
+ * @param val 当前值字符串
+ * @returns 返回处理后的字符串
+ */
+export function verifyNumberComma(val: string) {
+  // 调用小数或整数(不可以负数)方法
+  let v: any = verifyNumberIntegerAndFloat(val)
+  // 字符串转成数组
+  v = v.toString().split('.')
+  // \B 匹配非单词边界,两边都是单词字符或者两边都是非单词字符
+  v[0] = v[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',')
+  // 数组转字符串
+  v = v.join('.')
+  // 返回结果
+  return v
+}
+
+/**
+ * 匹配文字变色(搜索时)
+ * @param val 当前值字符串
+ * @param text 要处理的字符串值
+ * @param color 搜索到时字体高亮颜色
+ * @returns 返回处理后的字符串
+ */
+export function verifyTextColor(val: string, text = '', color = 'red') {
+  // 返回内容,添加颜色
+  const v = text.replace(
+    new RegExp(val, 'gi'),
+    `<span style='color: ${color}'>${val}</span>`
+  )
+  // 返回结果
+  return v
+}
+
+/**
+ * 数字转中文大写
+ * @param val 当前值字符串
+ * @param unit 默认:仟佰拾亿仟佰拾万仟佰拾元角分
+ * @returns 返回处理后的字符串
+ */
+export function verifyNumberCnUppercase(
+  val: any,
+  unit = '仟佰拾亿仟佰拾万仟佰拾元角分',
+  v = ''
+) {
+  // 当前内容字符串添加 2个0,为什么??
+  val += '00'
+  // 返回某个指定的字符串值在字符串中首次出现的位置,没有出现,则该方法返回 -1
+  const lookup = val.indexOf('.')
+  // substring:不包含结束下标内容,substr:包含结束下标内容
+  if (lookup >= 0) val = val.substring(0, lookup) + val.substr(lookup + 1, 2)
+  // 根据内容 val 的长度,截取返回对应大写
+  unit = unit.substr(unit.length - val.length)
+  // 循环截取拼接大写
+  for (let i = 0; i < val.length; i++) {
+    v += '零壹贰叁肆伍陆柒捌玖'.substr(val.substr(i, 1), 1) + unit.substr(i, 1)
+  }
+  // 正则处理
+  v = v
+    .replace(/零角零分$/, '整')
+    .replace(/零[仟佰拾]/g, '零')
+    .replace(/零{2,}/g, '零')
+    .replace(/零([亿|万])/g, '$1')
+    .replace(/零+元/, '元')
+    .replace(/亿零{0,3}万/, '亿')
+    .replace(/^元/, '零元')
+  // 返回结果
+  return v
+}
+
+/**
+ * 手机号码
+ * @param val 当前值字符串
+ * @returns 返回 true: 手机号码正确
+ */
+export function verifyPhone(val: string) {
+  // false: 手机号码不正确
+  if (
+    !/^((12[0-9])|(13[0-9])|(14[5|7])|(15([0-3]|[5-9]))|(18[0,5-9]))\d{8}$/.test(
+      val
+    )
+  )
+    return false
+  // true: 手机号码正确
+  else return true
+}
+
+/**
+ * 国内电话号码
+ * @param val 当前值字符串
+ * @returns 返回 true: 国内电话号码正确
+ */
+export function verifyTelPhone(val: string) {
+  // false: 国内电话号码不正确
+  if (!/\d{3}-\d{8}|\d{4}-\d{7}/.test(val)) return false
+  // true: 国内电话号码正确
+  else return true
+}
+
+/**
+ * 登录账号 (字母开头,允许5-16字节,允许字母数字下划线)
+ * @param val 当前值字符串
+ * @returns 返回 true: 登录账号正确
+ */
+export function verifyAccount(val: string) {
+  // false: 登录账号不正确
+  if (!/^[a-zA-Z][a-zA-Z0-9_]{4,15}$/.test(val)) return false
+  // true: 登录账号正确
+  else return true
+}
+
+/**
+ * 密码 (以字母开头,长度在6~16之间,只能包含字母、数字和下划线)
+ * @param val 当前值字符串
+ * @returns 返回 true: 密码正确
+ */
+export function verifyPassword(val: string) {
+  // false: 密码不正确
+  if (!/^[a-zA-Z]\w{5,15}$/.test(val)) return false
+  // true: 密码正确
+  else return true
+}
+
+/**
+ * 强密码 (字母+数字+特殊字符,长度在6-16之间)
+ * @param val 当前值字符串
+ * @returns 返回 true: 强密码正确
+ */
+export function verifyPasswordPowerful(val: string) {
+  // false: 强密码不正确
+  if (
+    !/^(?![a-zA-z]+$)(?!\d+$)(?![!@#$%^&\.*]+$)(?![a-zA-z\d]+$)(?![a-zA-z!@#$%^&\.*]+$)(?![\d!@#$%^&\.*]+$)[a-zA-Z\d!@#$%^&\.*]{6,16}$/.test(
+      val
+    )
+  )
+    return false
+  // true: 强密码正确
+  else return true
+}
+
+/**
+ * 密码强度
+ * @param val 当前值字符串
+ * @description 弱:纯数字,纯字母,纯特殊字符
+ * @description 中:字母+数字,字母+特殊字符,数字+特殊字符
+ * @description 强:字母+数字+特殊字符
+ * @returns 返回处理后的字符串:弱、中、强
+ */
+export function verifyPasswordStrength(val: string) {
+  let v = ''
+  // 弱:纯数字,纯字母,纯特殊字符
+  if (/^(?:\d+|[a-zA-Z]+|[!@#$%^&\.*]+){6,16}$/.test(val)) v = '弱'
+  // 中:字母+数字,字母+特殊字符,数字+特殊字符
+  if (
+    /^(?![a-zA-z]+$)(?!\d+$)(?![!@#$%^&\.*]+$)[a-zA-Z\d!@#$%^&\.*]{6,16}$/.test(
+      val
+    )
+  )
+    v = '中'
+  // 强:字母+数字+特殊字符
+  if (
+    /^(?![a-zA-z]+$)(?!\d+$)(?![!@#$%^&\.*]+$)(?![a-zA-z\d]+$)(?![a-zA-z!@#$%^&\.*]+$)(?![\d!@#$%^&\.*]+$)[a-zA-Z\d!@#$%^&\.*]{6,16}$/.test(
+      val
+    )
+  )
+    v = '强'
+  // 返回结果
+  return v
+}
+
+/**
+ * IP地址
+ * @param val 当前值字符串
+ * @returns 返回 true: IP地址正确
+ */
+export function verifyIPAddress(val: string) {
+  // false: IP地址不正确
+  if (
+    !/^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/.test(
+      val
+    )
+  )
+    return false
+  // true: IP地址正确
+  else return true
+}
+
+/**
+ * 邮箱
+ * @param val 当前值字符串
+ * @returns 返回 true: 邮箱正确
+ */
+export function verifyEmail(val: string) {
+  // false: 邮箱不正确
+  const reg =
+    /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
+  if (!reg.test(val)) return false
+  // true: 邮箱正确
+  else return true
+}
+
+/**
+ * 身份证
+ * @param val 当前值字符串
+ * @returns 返回 true: 身份证正确
+ */
+export function verifyIdCard(val: string) {
+  // false: 身份证不正确
+  if (
+    !/^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/.test(
+      val
+    )
+  )
+    return false
+  // true: 身份证正确
+  else return true
+}
+
+/**
+ * 姓名
+ * @param val 当前值字符串
+ * @returns 返回 true: 姓名正确
+ */
+export function verifyFullName(val: string) {
+  // false: 姓名不正确
+  if (!/^[\u4e00-\u9fa5]{1,6}(·[\u4e00-\u9fa5]{1,6}){0,2}$/.test(val))
+    return false
+  // true: 姓名正确
+  else return true
+}
+
+/**
+ * 邮政编码
+ * @param val 当前值字符串
+ * @returns 返回 true: 邮政编码正确
+ */
+export function verifyPostalCode(val: string) {
+  // false: 邮政编码不正确
+  if (!/^[1-9][0-9]{5}$/.test(val)) return false
+  // true: 邮政编码正确
+  else return true
+}
+
+/**
+ * url 处理
+ * @param val 当前值字符串
+ * @returns 返回 true: url 正确
+ */
+export function verifyUrl(val: string) {
+  // false: url不正确
+  if (
+    !/^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})).?)(?::\d{2,5})?(?:[/?#]\S*)?$/i.test(
+      val
+    )
+  )
+    return false
+  // true: url正确
+  else return true
+}
+
+/**
+ * 车牌号
+ * @param val 当前值字符串
+ * @returns 返回 true:车牌号正确
+ */
+export function verifyCarNum(val: string) {
+  // false: 车牌号不正确
+  if (
+    !/^(([京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-Z](([0-9]{5}[DF])|([DF]([A-HJ-NP-Z0-9])[0-9]{4})))|([京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-Z][A-HJ-NP-Z0-9]{4}[A-HJ-NP-Z0-9挂学警港澳使领]))$/.test(
+      val
+    )
+  )
+    return false
+  // true:车牌号正确
+  else return true
+}

+ 22 - 22
src/helpers/utils.ts

@@ -1,8 +1,8 @@
-import { Toast } from 'vant';
-import { state as helpState } from './helpState';
+import { Toast } from 'vant'
+import { state as helpState } from './helpState'
 
 
 export const browser = () => {
 export const browser = () => {
-  const u = navigator.userAgent;
+  const u = navigator.userAgent
   //   app = navigator.appVersion;
   //   app = navigator.appVersion;
   return {
   return {
     trident: u.indexOf('Trident') > -1, //IE内核
     trident: u.indexOf('Trident') > -1, //IE内核
@@ -23,20 +23,20 @@ export const browser = () => {
     weixin: u.indexOf('MicroMessenger') > -1, //是否微信 (2015-01-22新增)
     weixin: u.indexOf('MicroMessenger') > -1, //是否微信 (2015-01-22新增)
     huawei: !!u.match(/huawei/i) || !!u.match(/honor/i),
     huawei: !!u.match(/huawei/i) || !!u.match(/honor/i),
     xiaomi: !!u.match(/mi\s/i) || !!u.match(/redmi/i) || !!u.match(/mix/i)
     xiaomi: !!u.match(/mi\s/i) || !!u.match(/redmi/i) || !!u.match(/mix/i)
-  };
-};
+  }
+}
 
 
 export const getRandomKey = () => {
 export const getRandomKey = () => {
-  const key = '' + new Date().getTime() + Math.floor(Math.random() * 1000000);
-  return key;
-};
+  const key = '' + new Date().getTime() + Math.floor(Math.random() * 1000000)
+  return key
+}
 
 
 /**
 /**
  * 删除token
  * 删除token
  */
  */
 export const removeAuth = () => {
 export const removeAuth = () => {
-  sessionStorage.removeItem('Authorization');
-};
+  sessionStorage.removeItem('Authorization')
+}
 
 
 /**
 /**
  * 设置token
  * 设置token
@@ -44,41 +44,41 @@ export const removeAuth = () => {
  * @returns {void}
  * @returns {void}
  */
  */
 export const setAuth = (token: any) => {
 export const setAuth = (token: any) => {
-  sessionStorage.setItem('Authorization', token);
-};
+  sessionStorage.setItem('Authorization', token)
+}
 
 
 /**
 /**
  * 获取token
  * 获取token
  */
  */
 export const getAuth = () => {
 export const getAuth = () => {
-  sessionStorage.getItem('Authorization');
-};
+  sessionStorage.getItem('Authorization')
+}
 
 
 /**
 /**
  * 开始加载
  * 开始加载
  */
  */
 export const openLoading = () => {
 export const openLoading = () => {
   if (helpState.loadingCount === 0) {
   if (helpState.loadingCount === 0) {
-    helpState.loadingCount++;
+    helpState.loadingCount++
     Toast.loading({
     Toast.loading({
       message: '加载中...',
       message: '加载中...',
       forbidClick: true,
       forbidClick: true,
       loadingType: 'spinner',
       loadingType: 'spinner',
       duration: 0
       duration: 0
-    });
+    })
   }
   }
-};
+}
 
 
 /**
 /**
  * 关闭加载
  * 关闭加载
  */
  */
 export const closeLoading = () => {
 export const closeLoading = () => {
   // console.log(helpState.loadingCount, +new Date());
   // console.log(helpState.loadingCount, +new Date());
-  if (helpState.loadingCount <= 0) return;
+  if (helpState.loadingCount <= 0) return
   setTimeout(() => {
   setTimeout(() => {
-    helpState.loadingCount--;
+    helpState.loadingCount--
     if (helpState.loadingCount === 0) {
     if (helpState.loadingCount === 0) {
-      Toast.clear();
+      Toast.clear()
     }
     }
-  }, 200);
-};
+  }, 200)
+}

+ 6 - 6
src/styles/index.less

@@ -12,8 +12,8 @@
   // --van-gray-8: #323233;
   // --van-gray-8: #323233;
   // --van-red: #ee0a24;
   // --van-red: #ee0a24;
   // --van-blue: #1989fa;
   // --van-blue: #1989fa;
-  --van-primary: #2DC7AA !important;
-  --van-picker-confirm-action-color: #2DC7AA !important;
+  --van-primary: #2dc7aa !important;
+  --van-picker-confirm-action-color: #2dc7aa !important;
   // --van-orange: #ff976a;
   // --van-orange: #ff976a;
   // --van-orange-dark: #ed6a0c;
   // --van-orange-dark: #ed6a0c;
   // --van-orange-light: #fffbe8;
   // --van-orange-light: #fffbe8;
@@ -76,12 +76,12 @@
   // --van-border-radius-lg: 8px;
   // --van-border-radius-lg: 8px;
   // --van-border-radius-max: 999px;
   // --van-border-radius-max: 999px;
 
 
-  --col-background-color: #F6F8F9;
+  --col-background-color: #f6f8f9;
 
 
   --white: #fff;
   --white: #fff;
 
 
   --tips-color: #999;
   --tips-color: #999;
-  --strong--color: #FF4E19;
+  --strong--color: #ff4e19;
   --box-shadow-color: rgba(0, 0, 0, 0.05);
   --box-shadow-color: rgba(0, 0, 0, 0.05);
 }
 }
 
 
@@ -100,9 +100,9 @@
 }
 }
 
 
 body {
 body {
-  background-color: #F6F8F9;
+  background-color: #f6f8f9;
 }
 }
 
 
 .mb12 {
 .mb12 {
-  margin-bottom: 12px;
+  margin-bottom: 12px !important;
 }
 }

+ 3 - 10
src/teacher/open-live/live-detail.tsx

@@ -1,3 +1,4 @@
+import CoursePlanStep from "@/business-components/course-plan-step";
 import SectionDetail from "@/business-components/section-detail";
 import SectionDetail from "@/business-components/section-detail";
 import UserDetail from "@/business-components/user-detail";
 import UserDetail from "@/business-components/user-detail";
 import UserList from "@/business-components/user-list";
 import UserList from "@/business-components/user-list";
@@ -10,7 +11,7 @@ export default defineComponent({
   render() {
   render() {
     return (
     return (
       <div class={[styles['live-detail'], 'mb12']}>
       <div class={[styles['live-detail'], 'mb12']}>
-        <UserDetail />
+        {/* <UserDetail /> */}
         <SectionDetail>
         <SectionDetail>
           <p class={styles.introduction}>小酷老师带您零基础学习竖笛,通过4节课的学习掌握竖笛演奏的基本方式,培养娘好的吐息习惯。</p>
           <p class={styles.introduction}>小酷老师带您零基础学习竖笛,通过4节课的学习掌握竖笛演奏的基本方式,培养娘好的吐息习惯。</p>
         </SectionDetail>
         </SectionDetail>
@@ -18,15 +19,7 @@ export default defineComponent({
         <SectionDetail title="课程列表" icon="courseList" titleShow={false} contentStyle={{ paddingTop: '0' }}>
         <SectionDetail title="课程列表" icon="courseList" titleShow={false} contentStyle={{ paddingTop: '0' }}>
           <Tabs color="var(--van-primary)" lineWidth={20} sticky>
           <Tabs color="var(--van-primary)" lineWidth={20} sticky>
             <Tab title="全部课程" titleClass="van-hairline--bottom">
             <Tab title="全部课程" titleClass="van-hairline--bottom">
-              {/* {[1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3].map(item => (
-                <CourseVideoItem class={'mb12'} detail={{
-                  id: 1,
-                  title: "竖笛演奏基础",
-                  content: "使学生初步了解竖笛的构造,学会吹奏姿势掌握基本呼吸掌握基本呼吸掌握基本呼吸方法。",
-                  imgUrl: 'https://daya.ks3-cn-beijing.ksyun.com/202201/SvB6tqR.png'
-                }} onPlay={this.onPlay} />
-              ))} */}
-              1111
+              <CoursePlanStep />
             </Tab>
             </Tab>
             <Tab title="已购课程" titleClass="van-hairline--bottom">
             <Tab title="已购课程" titleClass="van-hairline--bottom">
               {[1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3].map(item => (
               {[1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3].map(item => (

+ 3 - 3
src/teacher/video-class/class-content.module.less

@@ -6,7 +6,7 @@
     height: 100px;
     height: 100px;
     border-radius: 10px;
     border-radius: 10px;
     overflow: hidden;
     overflow: hidden;
-    margin: 0 0 12px;
+    // margin: 0 0 12px;
   }
   }
 
 
   .disabled {
   .disabled {
@@ -26,7 +26,7 @@
       display: inline-block;
       display: inline-block;
       width: 3px;
       width: 3px;
       height: 16px;
       height: 16px;
-      background: #2DC7AA;
+      background: #2dc7aa;
       border-radius: 3px;
       border-radius: 3px;
       margin-right: 8px;
       margin-right: 8px;
       vertical-align: text-bottom;
       vertical-align: text-bottom;
@@ -64,4 +64,4 @@
       }
       }
     }
     }
   }
   }
-}
+}

+ 68 - 22
src/teacher/video-class/class-content.tsx

@@ -2,64 +2,110 @@ import ColField from "@/components/col-field";
 import ColFieldGroup from "@/components/col-field-group";
 import ColFieldGroup from "@/components/col-field-group";
 import ColUpload from "@/components/col-upload";
 import ColUpload from "@/components/col-upload";
 import ColUploadVideo from "@/components/col-upload-video";
 import ColUploadVideo from "@/components/col-upload-video";
-import { Button, Col, Dialog, Field, Icon, Row, Sticky } from "vant";
+import { Button, Col, Dialog, Field, Form, Icon, Row, Sticky, Switch } from "vant";
 import { defineComponent } from "vue";
 import { defineComponent } from "vue";
 import styles from './class-content.module.less';
 import styles from './class-content.module.less';
+import { createState } from './createState';
+
+
 
 
 export default defineComponent({
 export default defineComponent({
   name: "ClassContent",
   name: "ClassContent",
   data() {
   data() {
     return {
     return {
-      dataList: [1]
+      url: '',
+      checked: null,
     }
     }
   },
   },
   methods: {
   methods: {
+    onSubmit(values: any) {
+      console.log(values, '123')
+      createState.active = 3;
+    },
+    onFailed(err: any) {
+      console.log(err)
+      console.log('onFailed', this.url)
+      // console.log('onFailed', createState.createVideo)
+    },
     addItem() {
     addItem() {
-      this.dataList.push(this.dataList.length + 1)
+      createState.createVideo.videoLessonList.push({
+        videoTitle: '',
+        videoContent: '',
+        videoUrl: 'https://daya.ks3-cn-beijing.ksyun.com/202204/T1kHuSh.mp4',
+        coverUrl: 'https://daya.ks3-cn-beijing.ksyun.com/202204/T1kK2ao.png'
+      })
     },
     },
     removeItem(index: number) {
     removeItem(index: number) {
       // 最少一节课
       // 最少一节课
-      if(this.dataList.length <= 1) return
+      if (createState.createVideo.videoLessonList.length <= 1) return
       Dialog.confirm({
       Dialog.confirm({
         title: '操作',
         title: '操作',
         message: `确定删除该条数据吗?`,
         message: `确定删除该条数据吗?`,
         confirmButtonColor: '#2DC7AA',
         confirmButtonColor: '#2DC7AA',
       })
       })
-      .then(() => {
-        // on confirm
-        this.dataList.splice(index, 1)
-      })
+        .then(() => {
+          createState.createVideo.videoLessonList.splice(index, 1)
+        })
     }
     }
   },
   },
   render() {
   render() {
     return (
     return (
-      <div class={styles['class-content']}>
-        {this.dataList.map((item: any, index: number) => (
+      <Form class={styles['class-content']} onSubmit={this.onSubmit} onFailed={this.onFailed} scrollToError>
+        {createState.createVideo.videoLessonList.map((item: any, index: number) => (
           <>
           <>
             <div class={styles.titleSection}>
             <div class={styles.titleSection}>
-              <span class={styles.title}>第{ index + 1 }课</span>
-              <Icon name="delete-o" class={this.dataList.length <= 1 ? styles.disabled : null} onClick={() => this.removeItem(index)} size={20} />
+              <span class={styles.title}>第{index + 1}课</span>
+              <Icon name="delete-o" class={createState.createVideo.videoLessonList.length <= 1 ? styles.disabled : null} onClick={() => this.removeItem(index)} size={20} />
             </div>
             </div>
             <ColFieldGroup>
             <ColFieldGroup>
               <ColField title="课程标题" required>
               <ColField title="课程标题" required>
                 <Field
                 <Field
-                  name="课程标题"
+                  v-model={item.videoTitle}
+                  maxlength={50}
+                  rules={[{ required: true, message: '请输入课程标题' }]}
+                  name="videoTitle"
                   placeholder="请输入您的课程标题"
                   placeholder="请输入您的课程标题"
                 />
                 />
               </ColField>
               </ColField>
               <ColField title="课程内容" required>
               <ColField title="课程内容" required>
                 <Field
                 <Field
-                  name="课程内容"
+                  v-model={item.videoContent}
+                  maxlength={200}
+                  rules={[{ required: true, message: '请输入课程内容' }]}
+                  name="videoContent"
                   placeholder="请输入您的课程内容"
                   placeholder="请输入您的课程内容"
                 />
                 />
               </ColField>
               </ColField>
               <ColField title="课程视频及视频封面" required border={false}>
               <ColField title="课程视频及视频封面" required border={false}>
                 <Row justify="space-between" style={{ width: '100%', paddingTop: '12px' }}>
                 <Row justify="space-between" style={{ width: '100%', paddingTop: '12px' }}>
                   <Col span={12}>
                   <Col span={12}>
-                    <ColUploadVideo class={styles.upload} tips="点击上传视屏" />
+                    <Field
+                      style={{ padding: 0 }}
+                      name="videoUrl"
+                      rules={[{ required: true, message: '请上传课程视频' }]}
+                      v-slots={{
+                        input: () => (
+                          <ColUploadVideo v-model={item.videoUrl} class={styles.upload} tips="点击上传视屏" />
+                        )
+                      }}
+                    />
                   </Col>
                   </Col>
                   <Col span={12}>
                   <Col span={12}>
-                    <ColUpload class={styles.upload} tips="点击上传视频封面" />
+                    <Field
+                      style={{ padding: 0 }}
+                      name="coverUrl"
+                      rules={[{ required: true, message: '请上传课程封面' }]}
+                      error
+                      v-slots={{
+                        input: () => (
+                          <ColUpload class={styles.upload} cropper options={{
+                            fixedNumber: [3, 2],
+                            autoCropWidth: 750,
+                            autoCropHeight: 500,
+                          }} v-model={item.coverUrl} tips="点击上传视频封面" />
+                        )
+                      }}
+                    />
                   </Col>
                   </Col>
                 </Row>
                 </Row>
               </ColField>
               </ColField>
@@ -70,12 +116,12 @@ export default defineComponent({
         <Button class={styles['add-item']} block icon="add-o" onClick={this.addItem}>添加课程</Button>
         <Button class={styles['add-item']} block icon="add-o" onClick={this.addItem}>添加课程</Button>
 
 
         <Sticky offsetBottom={0} position="bottom">
         <Sticky offsetBottom={0} position="bottom">
-            <div class={[styles.btnGroup, styles.btnMore]}>
-              <Button block round type="primary" plain>上一步</Button>
-              <Button block round type="primary">提交</Button>
-            </div>
-          </Sticky>
-      </div>
+          <div class={[styles.btnGroup, styles.btnMore]}>
+            <Button block round type="primary" plain onClick={() => { createState.active = 1 }}>上一步</Button>
+            <Button block round type="primary" native-type="submit">提交</Button>
+          </div>
+        </Sticky>
+      </Form>
     )
     )
   }
   }
 })
 })

+ 2 - 3
src/teacher/video-class/class-info.module.less

@@ -22,7 +22,7 @@
   padding: 0 12px 12px;
   padding: 0 12px 12px;
 
 
   span {
   span {
-    color: #FF4E19;
+    color: #ff4e19;
   }
   }
 }
 }
 
 
@@ -30,6 +30,5 @@
   font-size: 14px;
   font-size: 14px;
   color: #999999;
   color: #999999;
   line-height: 27px;
   line-height: 27px;
-  padding: 5px 0 12px;
+  padding: 5px 0;
 }
 }
-

+ 50 - 26
src/teacher/video-class/class-info.tsx

@@ -3,13 +3,14 @@ import ColFieldGroup from "@/components/col-field-group";
 import ColPopup from "@/components/col-popup";
 import ColPopup from "@/components/col-popup";
 import ColUpload from "@/components/col-upload";
 import ColUpload from "@/components/col-upload";
 import request from "@/helpers/request";
 import request from "@/helpers/request";
-import { create } from "domain";
+import { verifyNumberIntegerAndFloat } from "@/helpers/toolsValidate";
 import { Button, Col, Field, Form, Image, Row, Sticky } from "vant";
 import { Button, Col, Field, Form, Image, Row, Sticky } from "vant";
 import { defineComponent } from "vue";
 import { defineComponent } from "vue";
 import SubjectModel from "../../business-components/subject-list";
 import SubjectModel from "../../business-components/subject-list";
 import styles from "./class-info.module.less";
 import styles from "./class-info.module.less";
 import { createState } from './createState'
 import { createState } from './createState'
 
 
+
 export default defineComponent({
 export default defineComponent({
   name: "ClassInfo",
   name: "ClassInfo",
   data() {
   data() {
@@ -19,23 +20,28 @@ export default defineComponent({
   },
   },
   computed: {
   computed: {
     choiceSubjectIds() { // 选择的科目编号
     choiceSubjectIds() { // 选择的科目编号
-      let ids = createState.createVideo.subjectIds ? createState.createVideo.subjectIds.split(',') : [];
-      ids = ids.map((item: any) => Number(item))
-      return ids
+      let ids = createState.createVideo.lessonSubject ? Number(createState.createVideo.lessonSubject) : null;
+      return ids ? [ids] : [];
     },
     },
     subjectList() { // 学科列表
     subjectList() { // 学科列表
       return createState.subjectList || []
       return createState.subjectList || []
     },
     },
-    choiceSubject() { // 选择的科目
-      let tempArr: any[] = []
+    lessonSubjectName() { // 选择的科目
+      let tempStr = ''
       this.subjectList.forEach((parent: any) => {
       this.subjectList.forEach((parent: any) => {
         parent.subjects && parent.subjects.forEach((sub: any) => {
         parent.subjects && parent.subjects.forEach((sub: any) => {
           if (this.choiceSubjectIds.includes(sub.id)) {
           if (this.choiceSubjectIds.includes(sub.id)) {
-            tempArr.push(sub as never)
+            tempStr = sub.name
           }
           }
         })
         })
       })
       })
-      return tempArr
+      return tempStr
+    },
+    calcRatePrice() { // 计算手续费
+      console.log(createState.rate)
+      let rate = createState.rate || 0;
+      let price = createState.createVideo.lessonPrice || 0;
+      return ((rate / 100) * price).toFixed(2);
     }
     }
   },
   },
   async mounted() {
   async mounted() {
@@ -52,16 +58,30 @@ export default defineComponent({
     } catch {
     } catch {
       //
       //
     }
     }
+
+    // lessonName: '',
+    // lessonSubject: null as any,
+    // lessonDesc: '',
+    // lessonPrice: null,
+    // lessonCoverUrl: '',
+
+    createState.createVideo.lessonName = '测试视屏信息';
+    createState.createVideo.lessonSubject = 5;
+    createState.createVideo.lessonDesc = '测试视屏信息内容';
+    createState.createVideo.lessonPrice = 50;
+    createState.createVideo.lessonCoverUrl = 'https://daya.ks3-cn-beijing.ksyun.com/202204/T1kK2ao.png';
   },
   },
   methods: {
   methods: {
     onSubmit(values: any) {
     onSubmit(values: any) {
-      console.log(values)
+      createState.active = 2
     },
     },
-    onChoice(item: any) {
-      createState.createVideo.subjectIds = item.join(',') || ''
-
+    onChoice(id: number) {
+      createState.createVideo.lessonSubject = id
       this.subjectStatus = false
       this.subjectStatus = false
     },
     },
+    onFormatter(val: any) {
+      return verifyNumberIntegerAndFloat(val)
+    }
   },
   },
   render() {
   render() {
     return (
     return (
@@ -69,16 +89,17 @@ export default defineComponent({
         <ColFieldGroup>
         <ColFieldGroup>
           <ColField title="课程名称" required>
           <ColField title="课程名称" required>
             <Field
             <Field
-              v-model={createState.createVideo.courseName}
-              name="courseName"
+              v-model={createState.createVideo.lessonName}
+              name="lessonName"
+              maxlength={50}
               placeholder="请输入您的课程名称"
               placeholder="请输入您的课程名称"
               rules={[{ required: true, message: '请输入您的课程名称' }]}
               rules={[{ required: true, message: '请输入您的课程名称' }]}
             />
             />
           </ColField>
           </ColField>
           <ColField title="课程声部" required>
           <ColField title="课程声部" required>
             <Field
             <Field
-              v-model={createState.createVideo.subjectIds}
-              name="subjectIds"
+              modelValue={this.lessonSubjectName}
+              name="lessonSubjectName"
               readonly
               readonly
               isLink
               isLink
               onClick={() => { this.subjectStatus = true }}
               onClick={() => { this.subjectStatus = true }}
@@ -91,10 +112,11 @@ export default defineComponent({
         <ColFieldGroup>
         <ColFieldGroup>
           <ColField title="课程介绍" required border={false}>
           <ColField title="课程介绍" required border={false}>
             <Field
             <Field
-              v-model={createState.createVideo.courseRemark}
-              name="courseRemark"
+              v-model={createState.createVideo.lessonDesc}
+              name="lessonDesc"
               placeholder="请输入课程介绍"
               placeholder="请输入课程介绍"
               rows="3"
               rows="3"
+              maxlength={200}
               autosize
               autosize
               rules={[{ required: true, message: '请选择课程声部' }]}
               rules={[{ required: true, message: '请选择课程声部' }]}
               type="textarea"
               type="textarea"
@@ -105,11 +127,13 @@ export default defineComponent({
         <ColFieldGroup>
         <ColFieldGroup>
           <ColField title="课程组售价" required>
           <ColField title="课程组售价" required>
             <Field
             <Field
-              v-model={createState.createVideo.coursePrice}
-              name="coursePrice"
+              v-model={createState.createVideo.lessonPrice}
+              name="lessonPrice"
               placeholder="请输入您的课程组售价"
               placeholder="请输入您的课程组售价"
+              formatter={this.onFormatter}
               type="number"
               type="number"
-              rules={[{ required: true, message: '请选择课程声部' }]}
+              maxlength={8}
+              rules={[{ required: true, message: '请输入您的课程组售价' }]}
               v-slots={{
               v-slots={{
                 button: () => (<span>元</span>)
                 button: () => (<span>元</span>)
               }}
               }}
@@ -119,7 +143,7 @@ export default defineComponent({
 
 
         <div class={styles['class-info-tip']}>
         <div class={styles['class-info-tip']}>
           <p>扣除手续费后您的课程预计收入为:</p>
           <p>扣除手续费后您的课程预计收入为:</p>
-          <p>课程组总收入<span>114</span>元/人</p>
+          <p>课程组总收入<span>{this.calcRatePrice}</span>元/人</p>
           <p>您的课程收入将在课程结束后结算到您的账户中</p>
           <p>您的课程收入将在课程结束后结算到您的账户中</p>
         </div>
         </div>
 
 
@@ -128,8 +152,8 @@ export default defineComponent({
             <p class={styles.photoTip}>模板图片将作为改课程封面为学员展示</p>
             <p class={styles.photoTip}>模板图片将作为改课程封面为学员展示</p>
 
 
             <Field
             <Field
-              v-model={createState.createVideo.courseImg}
-              name="courseImg"
+              v-model={createState.createVideo.lessonCoverUrl}
+              name="lessonCoverUrl"
               rules={[{ required: true, message: '请选择课程声部' }]}
               rules={[{ required: true, message: '请选择课程声部' }]}
               v-slots={{
               v-slots={{
                 input: () => (
                 input: () => (
@@ -148,7 +172,7 @@ export default defineComponent({
                         fixedNumber: [3, 2],
                         fixedNumber: [3, 2],
                         autoCropWidth: 750,
                         autoCropWidth: 750,
                         autoCropHeight: 500,
                         autoCropHeight: 500,
-                      }} v-model={createState.createVideo.courseImg} class={styles.imgContainer} />
+                      }} v-model={createState.createVideo.lessonCoverUrl} class={styles.imgContainer} />
                     </Col>
                     </Col>
                   </Row>
                   </Row>
                 )
                 )
@@ -164,7 +188,7 @@ export default defineComponent({
         </Sticky>
         </Sticky>
 
 
         <ColPopup v-model={this.subjectStatus}>
         <ColPopup v-model={this.subjectStatus}>
-          <SubjectModel max={1} subjectList={this.subjectList} choiceSubjectIds={this.choiceSubjectIds} onChoice={this.onChoice} />
+          <SubjectModel selectType="Radio" subjectList={this.subjectList} choiceSubjectIds={this.choiceSubjectIds} onChoice={this.onChoice} />
         </ColPopup>
         </ColPopup>
       </Form>
       </Form>
     )
     )

+ 35 - 15
src/teacher/video-class/create-submit.tsx

@@ -4,35 +4,55 @@ import UserDetail from "@/business-components/user-detail";
 import SectionDetail from "@/business-components/section-detail";
 import SectionDetail from "@/business-components/section-detail";
 import CourseVideoItem from "@/business-components/course-video-item";
 import CourseVideoItem from "@/business-components/course-video-item";
 import { Button, Sticky } from "vant";
 import { Button, Sticky } from "vant";
+import { createState } from "./createState";
+import { state } from "@/state";
 
 
 
 
 export default defineComponent({
 export default defineComponent({
   name: "CreateSubmit",
   name: "CreateSubmit",
+  computed: {
+    userInfo() {
+      const videoDetail = createState.createVideo;
+      const users = state.user.data || {};
+      return {
+        username: users.username,
+        headUrl: users.headUrl,
+        lessonName: videoDetail.lessonName,
+        buyNum: 0,
+        lessonDesc: videoDetail.lessonDesc,
+        lessonPrice: videoDetail.lessonPrice,
+        lessonCoverUrl: videoDetail.lessonCoverUrl,
+        lessonNum: videoDetail.videoLessonList.length
+      }
+    },
+    lessonList() {
+      return createState.createVideo.videoLessonList || [];
+    }
+  },
   render() {
   render() {
     return (
     return (
-      <div class={[styles.createSubmit, 'mb12']}>
-        <UserDetail />
+      <div class={[styles.createSubmit]}>
+        <UserDetail userInfo={this.userInfo} />
         <SectionDetail>
         <SectionDetail>
-          <p class={styles.introduction}>小酷老师带您零基础学习竖笛,通过4节课的学习掌握竖笛演奏的基本方式,培养娘好的吐息习惯。</p>
+          <p class={styles.introduction}>{this.userInfo.lessonDesc}</p>
         </SectionDetail>
         </SectionDetail>
 
 
-        <SectionDetail title="课程列表" icon="courseList">
-          { [1,2,3,1,2,3,1,2,3].map(item => (
+        <SectionDetail title="课程列表" icon="courseList" class='mb12'>
+          {this.lessonList.map((item: any) => (
             <CourseVideoItem class={'mb12'} detail={{
             <CourseVideoItem class={'mb12'} detail={{
-              id: 1,
-              title: "竖笛演奏基础",
-              content: "使学生初步了解竖笛的构造,学会吹奏姿势掌握基本呼吸掌握基本呼吸掌握基本呼吸方法。",
-              imgUrl: 'https://daya.ks3-cn-beijing.ksyun.com/202201/SvB6tqR.png'
+              title: item.videoTitle,
+              content: item.videoContent,
+              imgUrl: item.coverUrl
             }} />
             }} />
-          )) }
+          ))}
         </SectionDetail>
         </SectionDetail>
 
 
         <Sticky offsetBottom={0} position="bottom">
         <Sticky offsetBottom={0} position="bottom">
-            <div class={[styles.btnGroup, styles.btnMore]}>
-              <Button block round type="primary" plain>返回编辑</Button>
-              <Button block round type="primary">创建完成</Button>
-            </div>
-          </Sticky>
+          <div class={[styles.btnGroup, styles.btnMore]}>
+            <Button block round type="primary" plain onClick={() => { createState.active = 2 }}>返回编辑</Button>
+            <Button block round type="primary">创建完成</Button>
+          </div>
+        </Sticky>
       </div>
       </div>
     )
     )
   }
   }

+ 19 - 13
src/teacher/video-class/create.tsx

@@ -1,9 +1,10 @@
-import ColUpload from "@/components/col-upload";
-import { Button, Grid, GridItem, Icon, Sticky } from "vant";
+import { Grid, GridItem, Icon } from "vant";
 import { defineComponent } from "vue";
 import { defineComponent } from "vue";
 import styles from './create.module.less';
 import styles from './create.module.less';
 import ClassInfo from "./class-info";
 import ClassInfo from "./class-info";
 import ClassContent from "./class-content";
 import ClassContent from "./class-content";
+import { createState } from "./createState";
+import request from "@/helpers/request";
 
 
 import nameActive from './images/icon_name_active.png'
 import nameActive from './images/icon_name_active.png'
 import education from './images/icon_education.png'
 import education from './images/icon_education.png'
@@ -12,42 +13,47 @@ import CreateSubmit from "./create-submit";
 
 
 export default defineComponent({
 export default defineComponent({
   name: 'Create',
   name: 'Create',
-  data() {
-    return {
-      active: 1,
-    }
+  async mounted() {
+    try {
+      const sysConfig = await request.get('/api-teacher/sysConfig/queryByParamName', {
+        params: {
+          paramName: 'video_lesson_service_fee'
+        }
+      })
+      createState.rate = sysConfig.data.paramValue
+    } catch { }
   },
   },
   render() {
   render() {
     return (
     return (
       <div class={styles['video-create']}>
       <div class={styles['video-create']}>
-        {this.active <= 2 ? <Grid style={{ paddingTop: '15px' }} direction="horizontal" columnNum="2">
+        {createState.active <= 2 ? <Grid style={{ paddingTop: '15px' }} direction="horizontal" columnNum="2">
           <GridItem v-slots={{
           <GridItem v-slots={{
             default: () => (
             default: () => (
               <>
               <>
                 <Icon name={nameActive} size={38} />
                 <Icon name={nameActive} size={38} />
-                <span class={[styles.gridName, this.active >= 1 ? styles.active : null]}>课程信息</span>
+                <span class={[styles.gridName, createState.active >= 1 ? styles.active : null]}>课程信息</span>
               </>
               </>
             )
             )
           }} />
           }} />
           <GridItem v-slots={{
           <GridItem v-slots={{
             default: () => (
             default: () => (
               <>
               <>
-                <Icon name={this.active === 2 ? educationActive : education} size={38} />
-                <span class={[styles.gridName, this.active === 2 ? styles.active : null]}>课程内容</span>
+                <Icon name={createState.active === 2 ? educationActive : education} size={38} />
+                <span class={[styles.gridName, createState.active === 2 ? styles.active : null]}>课程内容</span>
               </>
               </>
             )
             )
           }} />
           }} />
         </Grid> : null}
         </Grid> : null}
         {/* 课程信息 */}
         {/* 课程信息 */}
-        {this.active === 1 ? <>
+        {createState.active === 1 ? <>
           <ClassInfo />
           <ClassInfo />
         </> : null}
         </> : null}
         {/* 课程内容 */}
         {/* 课程内容 */}
-        {this.active === 2 ? <>
+        {createState.active === 2 ? <>
           <ClassContent />
           <ClassContent />
         </> : null}
         </> : null}
         {/* 预览 */}
         {/* 预览 */}
-        {this.active === 3 ? <>
+        {createState.active === 3 ? <>
           <CreateSubmit />
           <CreateSubmit />
         </> : null}
         </> : null}
 
 

+ 17 - 5
src/teacher/video-class/createState.tsx

@@ -2,12 +2,24 @@ import { reactive } from "vue";
 
 
 export const createState = reactive({
 export const createState = reactive({
   active: 1,
   active: 1,
+  rate: 0, // 手续费
   subjectList: [], // 声部列表
   subjectList: [], // 声部列表
   createVideo: {
   createVideo: {
-    courseName: '',
-    subjectIds: null as any,
-    courseRemark: '',
-    coursePrice: null,
-    courseImg: '',
+    lessonName: '',
+    lessonSubject: null as any,
+    lessonDesc: '',
+    lessonPrice: null as any,
+    lessonCoverUrl: '',
+    videoLessonList: [{
+      videoTitle: '视屏标题',
+      videoContent: '视屏内容',
+      videoUrl: 'https://daya.ks3-cn-beijing.ksyun.com/202204/T1kHuSh.mp4',
+      coverUrl: 'https://daya.ks3-cn-beijing.ksyun.com/202204/T1kK2ao.png'
+    }, {
+      videoTitle: '视屏标题2',
+      videoContent: '视屏内容2',
+      videoUrl: 'https://daya.ks3-cn-beijing.ksyun.com/202204/T1kHuSh.mp4',
+      coverUrl: 'https://daya.ks3-cn-beijing.ksyun.com/202204/T1kK2ao.png'
+    }]
   }
   }
 })
 })

+ 19 - 1
src/teacher/video-class/video-detail.tsx

@@ -4,10 +4,28 @@ import UserDetail from "@/business-components/user-detail";
 import UserList from "@/business-components/user-list";
 import UserList from "@/business-components/user-list";
 import { Tab, Tabs } from "vant";
 import { Tab, Tabs } from "vant";
 import { defineComponent } from "vue";
 import { defineComponent } from "vue";
+import { createState } from "./createState";
+import { state } from "@/state";
 import styles from './video-detail.module.less';
 import styles from './video-detail.module.less';
 
 
 export default defineComponent({
 export default defineComponent({
   name: 'VideoDetail',
   name: 'VideoDetail',
+  computed: {
+    userInfo() {
+      const videoDetail = createState.createVideo;
+      const users = state.user.data || {};
+      return {
+        username: users.username,
+        headUrl: users.headUrl,
+        lessonName: videoDetail.lessonName,
+        buyNum: 0,
+        lessonDesc: videoDetail.lessonDesc,
+        lessonPrice: videoDetail.lessonPrice,
+        lessonCoverUrl: videoDetail.lessonCoverUrl,
+        lessonNum: videoDetail.videoLessonList.length
+      }
+    },
+  },
   methods: {
   methods: {
     onPlay(detail: any) {
     onPlay(detail: any) {
       console.log(detail);
       console.log(detail);
@@ -16,7 +34,7 @@ export default defineComponent({
   render() {
   render() {
     return (
     return (
       <div class={[styles['video-detail'], 'mb12']}>
       <div class={[styles['video-detail'], 'mb12']}>
-        <UserDetail />
+        {this.userInfo && <UserDetail userInfo={this.userInfo} />}
         <SectionDetail>
         <SectionDetail>
           <p class={styles.introduction}>小酷老师带您零基础学习竖笛,通过4节课的学习掌握竖笛演奏的基本方式,培养娘好的吐息习惯。</p>
           <p class={styles.introduction}>小酷老师带您零基础学习竖笛,通过4节课的学习掌握竖笛演奏的基本方式,培养娘好的吐息习惯。</p>
         </SectionDetail>
         </SectionDetail>