Browse Source

更新优化

lex-xin 2 years ago
parent
commit
b7f13681d8
47 changed files with 2440 additions and 540 deletions
  1. 3 3
      README.md
  2. 0 0
      dist/assets/create-legacy.dbe30bc7.js
  3. 0 0
      dist/assets/index-legacy.6253f152.js
  4. 75 0
      src/business-components/calendar/index.module.less
  5. 122 0
      src/business-components/calendar/index.tsx
  6. 231 65
      src/business-components/subject-list/index.tsx
  7. 1 0
      src/business-components/user-detail/index.module.less
  8. 85 31
      src/business-components/user-detail/index.tsx
  9. BIN
      src/common/images/icon_arrow.png
  10. BIN
      src/common/images/icon_timer2.png
  11. 101 59
      src/components/col-upload-video/index.tsx
  12. 9 1
      src/router/routes-teacher.ts
  13. 14 0
      src/styles/index.less
  14. 50 0
      src/teacher/live-class/create-components/arrange.module.less
  15. 71 0
      src/teacher/live-class/create-components/arrange.tsx
  16. 20 0
      src/teacher/live-class/create-components/course-plan.module.less
  17. 68 0
      src/teacher/live-class/create-components/course-plan.tsx
  18. 88 0
      src/teacher/live-class/create-components/course-start.module.less
  19. 206 0
      src/teacher/live-class/create-components/course-start.tsx
  20. 17 0
      src/teacher/live-class/create-components/course.module.less
  21. 201 0
      src/teacher/live-class/create-components/course.tsx
  22. 21 0
      src/teacher/live-class/create-components/createState.ts
  23. 15 0
      src/teacher/live-class/create-components/detail.module.less
  24. 60 0
      src/teacher/live-class/create-components/detail.tsx
  25. 32 0
      src/teacher/live-class/create-components/steps.module.less
  26. 112 0
      src/teacher/live-class/create-components/steps.tsx
  27. 0 0
      src/teacher/live-class/create.module.less
  28. 72 0
      src/teacher/live-class/create.tsx
  29. BIN
      src/teacher/live-class/images/icon_arrange_active.png
  30. BIN
      src/teacher/live-class/images/icon_arrange_default.png
  31. BIN
      src/teacher/live-class/images/icon_course_active.png
  32. BIN
      src/teacher/live-class/images/icon_plan_active.png
  33. BIN
      src/teacher/live-class/images/icon_plan_default.png
  34. BIN
      src/teacher/live-class/images/icon_start_active.png
  35. BIN
      src/teacher/live-class/images/icon_start_default.png
  36. 0 0
      src/teacher/live-class/live-detail.module.less
  37. 43 0
      src/teacher/live-class/live-detail.tsx
  38. 0 34
      src/teacher/open-live/live-detail.tsx
  39. 51 24
      src/teacher/teacher-cert/steps.tsx
  40. 2 2
      src/teacher/teacher-cert/teacherState.ts
  41. 82 29
      src/teacher/video-class/class-content.tsx
  42. 23 0
      src/teacher/video-class/class-info.module.less
  43. 159 81
      src/teacher/video-class/class-info.tsx
  44. 115 59
      src/teacher/video-class/create.tsx
  45. 16 12
      src/teacher/video-class/createState.tsx
  46. 1 1
      src/views/order-detail/video/index.tsx
  47. 274 139
      src/views/protocol/privacy.tsx

+ 3 - 3
README.md

@@ -44,15 +44,15 @@ See [Configuration Reference](https://vitejs.dev/config/).
 2、图片裁切
 3、图形验证码
 4、协议
-5、视上传
+5、视上传
 
 /src/business-components -- 业务组件
 1、详情头部
 ```
 
 ### 公用尺寸
-1、所有的图片和视尺寸统一 3:2
-2、所有的金额四舍五入保留两位小数
+1、所有的图片和视尺寸统一 3:2
+2、所有的金额四舍五入保留两位小数, 所有的金额保留2位小数,有百分符
 
 ### native-message api
 postMessage({ api: 'chooseFile', content: {

File diff suppressed because it is too large
+ 0 - 0
dist/assets/create-legacy.dbe30bc7.js


File diff suppressed because it is too large
+ 0 - 0
dist/assets/index-legacy.6253f152.js


+ 75 - 0
src/business-components/calendar/index.module.less

@@ -0,0 +1,75 @@
+.calendar {
+  border-radius: 10px;
+
+  .subtitle {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    font-size: 18px;
+    font-weight: 500;
+    color: #333333;
+    line-height: 25px;
+    height: var(--van-calendar-header-title-height);
+    padding: 0 22px;
+
+    .right {
+      transform: rotateZ(180deg);
+    }
+
+    .disabled {
+      opacity: 0.6;
+    }
+  }
+  :global {
+    .van-calendar__header {
+      box-shadow: none;
+    }
+    .van-calendar__selected-day {
+      width: 36px !important;
+      height: 45px !important;
+      border-radius: 2px;
+      .van-calendar__bottom-info {
+        color: #fff !important;
+      }
+    }
+    .van-calendar__weekday {
+      color: #777;
+      font-size: 14px;
+    }
+    .van-calendar__day {
+      font-size: 15px;
+      &::after {
+        position: absolute;
+        top: 50%;
+        right: 0;
+        bottom: 0;
+        left: 50%;
+        width: 36px;
+        height: 45px;
+        background: #2dc7aa;
+        content: '';
+        opacity: 0.12;
+        transform: translate(-50%, -50%);
+        border-radius: 2px;
+      }
+    }
+    .van-calendar__days {
+      padding: 12px 0;
+    }
+    .van-calendar__bottom-info {
+      bottom: 3px;
+    }
+    .full {
+      .van-calendar__bottom-info {
+        color: #ff6363;
+      }
+    }
+    .van-calendar__day.full::after {
+      background-color: #ffd7a6;
+    }
+    // 禁用不显示背景
+    .van-calendar__day--disabled::after {
+      display: none !important;
+    }
+  }
+}

+ 122 - 0
src/business-components/calendar/index.tsx

@@ -0,0 +1,122 @@
+import { Calendar, Icon } from 'vant'
+import { defineComponent } from 'vue'
+import dayjs from 'dayjs'
+import styles from './index.module.less'
+import IconArrow from '@/common/images/icon_arrow.png'
+
+export default defineComponent({
+  name: 'calendar',
+  props: {
+    /**
+     * 点击并选中任意日期时触发
+     */
+    onSelect: {
+      type: Function,
+      default: (date: Date) => {}
+    },
+    /**
+     * 上一月,不能小于当月
+     */
+    prevMonth: {
+      type: Function,
+      default: (date: Date) => {}
+    },
+    /**
+     * 下一月,暂无限制
+     */
+    nextMonth: {
+      type: Function,
+      default: (date: Date) => {}
+    }
+  },
+  data() {
+    return {
+      minDate: new Date(),
+      maxDate: new Date(),
+      currentDate: new Date(), // 当前日历日期
+      subtitle: ''
+    }
+  },
+  computed: {
+    arrowStatus() {
+      // 上月箭头状态
+      // console.log(dayjs().subtract(1, 'date').format('YYYY年MM月DD日'))
+      // console.log(dayjs().isBefore(dayjs(this.currentDate), 'month'))
+      return !dayjs().isBefore(dayjs(this.currentDate), 'month')
+    }
+  },
+  mounted() {
+    // 初始化标题和最大显示日期
+    this.subtitle = dayjs().format('YYYY年MM月')
+    this.maxDate = new Date(dayjs().endOf('month').toDate())
+  },
+  methods: {
+    formatter(date: any) {
+      // console.log(date)
+      // date.bottomInfo = '满'
+      // date.className = 'full'
+      return date
+    },
+    onDateSelect(date: any) {
+      console.log(date)
+      this.onSelect && this.onSelect(date)
+    },
+    onPrevMonth() {
+      if (this.arrowStatus) return
+      const tempDate = dayjs(this.currentDate).subtract(1, 'month')
+      this._monthChange(tempDate)
+      this.prevMonth && this.prevMonth(tempDate.toDate())
+    },
+    onNextMonth() {
+      const tempDate = dayjs(this.currentDate).add(1, 'month')
+      this._monthChange(tempDate)
+      this.nextMonth && this.nextMonth(tempDate.toDate())
+    },
+    _monthChange(date: any) {
+      this.minDate = date.startOf('month').toDate()
+      this.maxDate = date.endOf('month').toDate()
+      this.currentDate = date.toDate()
+      this.subtitle = date.format('YYYY年MM月')
+    }
+  },
+  render() {
+    return (
+      <>
+        <Calendar
+          class={styles.calendar}
+          showTitle={false}
+          poppable={false}
+          showConfirm={false}
+          showMark={false}
+          firstDayOfWeek={1}
+          rowHeight={50}
+          minDate={this.minDate}
+          maxDate={this.maxDate}
+          color="var(--van-primary)"
+          formatter={this.formatter}
+          onSelect={this.onDateSelect}
+          v-slots={{
+            subtitle: () => (
+              <div class={styles.subtitle}>
+                <Icon
+                  name={IconArrow}
+                  size={18}
+                  class={this.arrowStatus && styles.disabled}
+                  onClick={this.onPrevMonth}
+                />
+                <span>{this.subtitle}</span>
+                <Icon
+                  name={IconArrow}
+                  size={18}
+                  class={styles.right}
+                  onClick={this.onNextMonth}
+                />
+              </div>
+            )
+            // 'bottom-info': (date: any) => <span>{date.type}</span>
+          }}
+        />
+      </>
+    )
+  }
+})

+ 231 - 65
src/business-components/subject-list/index.tsx

@@ -1,17 +1,26 @@
-import { Button, Checkbox, CheckboxGroup, Icon, Image, Loading, Radio, RadioGroup } from "vant";
-import { defineComponent, PropType } from "vue";
-import styles from './index.module.less';
+import {
+  Button,
+  Checkbox,
+  CheckboxGroup,
+  Icon,
+  Image,
+  Loading,
+  Radio,
+  RadioGroup
+} from 'vant'
+import { defineComponent, PropType } from 'vue'
+import styles from './index.module.less'
 
-import checkBoxActive from '@/teacher/teacher-cert/images/checkbox_active.png';
-import checkBoxDefault from '@/teacher/teacher-cert/images/checkbox_default.png';
-import ColResult from "@/components/col-result";
+import checkBoxActive from '@/teacher/teacher-cert/images/checkbox_active.png'
+import checkBoxDefault from '@/teacher/teacher-cert/images/checkbox_default.png'
+import ColResult from '@/components/col-result'
 
 export default defineComponent({
-  name: "SubjectList",
+  name: 'SubjectList',
   props: {
     onChoice: {
       type: Function,
-      default: (item: any) => { }
+      default: (item: any) => {}
     },
     choiceSubjectIds: {
       type: Array,
@@ -21,11 +30,13 @@ export default defineComponent({
       type: Array,
       default: []
     },
-    max: {// 最多可选数量
+    max: {
+      // 最多可选数量
       type: Number,
       default: 5
     },
-    selectType: {// 选择类型,Radio:单选,Checkbox:多选
+    selectType: {
+      // 选择类型,Radio:单选,Checkbox:多选
       type: String as PropType<'Checkbox' | 'Radio'>,
       default: 'Checkbox'
     }
@@ -34,18 +45,19 @@ export default defineComponent({
     return {
       checkBox: [],
       checkboxRefs: [] as any,
-      radio: null as any, // 单选
+      radio: null as any // 单选
     }
   },
   async mounted() {
     this.checkBox = this.choiceSubjectIds as never[]
+    console.log(this.subjectList)
   },
   methods: {
     onSelect(id: number) {
       if (this.selectType === 'Checkbox') {
-        this.checkboxRefs[id].toggle();
+        this.checkboxRefs[id].toggle()
       } else if (this.selectType === 'Radio') {
-        this.radio = id;
+        this.radio = id
       }
     }
   },
@@ -53,60 +65,214 @@ export default defineComponent({
     return (
       <div class={styles.subjects}>
         {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>
+          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>
-                  ))}
-                </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} />
-                      }} />
-                      <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 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>
+                )
+              )}
+            </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} />
+                                )
+                              }}
+                            />
+                            <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>
-              </> : null
-            ))}
-          </RadioGroup>
-        ) : <ColResult tips="暂无声部数据" btnStatus={false} />
-        }
+                  </>
+                ) : (
+                  <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}>
+                            <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>
+                )
+              )}
+            </RadioGroup>
+          )
+        ) : (
+          <ColResult tips="暂无声部数据" btnStatus={false} />
+        )}
 
-        < div class={styles["btn-group"]} >
-          <Button round block type="primary" onClick={() => this.onChoice(this.selectType === 'Checkbox' ? this.checkBox : this.radio)}>确定</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>
     )
   }
-})
+})

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

@@ -62,6 +62,7 @@
       font-size: 18px;
       color: #1a1a1a;
       font-weight: 500;
+      display: block !important;
     }
     :global {
       .van-cell {

+ 85 - 31
src/business-components/user-detail/index.tsx

@@ -1,9 +1,10 @@
-import { Cell, CellGroup, Icon, Image, Tag } from "vant";
-import { defineComponent, PropType } from "vue";
-import styles from "./index.module.less";
+import { Cell, CellGroup, Icon, Image, Tag } from 'vant'
+import { defineComponent, PropType } from 'vue'
+import styles from './index.module.less'
 
-import iconUserNum from '@common/images/icon_user_num.png';
-import defaultIcon from '@common/images/icon_teacher.png';
+import iconUserNum from '@common/images/icon_user_num.png'
+import defaultIcon from '@common/images/icon_teacher.png'
+import iconTimer from '@common/images/icon_timer2.png'
 
 /**
  * @description: 视频详情
@@ -18,48 +19,101 @@ import defaultIcon from '@common/images/icon_teacher.png';
  * @param {type} lessonName 课程名称
  */
 interface UserType {
-  headUrl: string;
-  username: string;
-  startTime?: string;
-  buyNum?: number;
-  lessonPrice: number;
-  lessonNum?: number;
-  lessonDesc?: string;
-  lessonCoverUrl: string;
-  lessonName: string;
+  headUrl: string
+  username: string
+  startTime?: string
+  buyNum?: number
+  lessonPrice: number
+  lessonNum?: number
+  lessonDesc?: string
+  lessonCoverUrl: string
+  lessonName: string
 }
 
 export default defineComponent({
-  name: "user-detail",
+  name: 'user-detail',
   props: {
     userInfo: {
       type: Object as PropType<UserType>,
       required: true
     },
     showType: {
-      type: String as PropType<"BUY" | "TIME">,
-      default: "BUY"
+      type: String as PropType<'BUY' | 'TIME'>,
+      default: 'BUY'
     }
   },
   render() {
     return (
       <div class={styles.userDetail}>
-        <Image class={[styles.banner]} src={this.userInfo.lessonCoverUrl} fit="cover" />
+        <Image
+          class={[styles.banner]}
+          src={this.userInfo.lessonCoverUrl}
+          fit="cover"
+        />
         <CellGroup class={styles.userInfo}>
-          <Cell title={this.userInfo.lessonName} titleClass={styles.userTitle} />
-          <Cell v-slots={{
-            icon: () => <Image class={styles.avatar} src={this.userInfo.headUrl || defaultIcon} />,
-            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>),
-            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
+            title={this.userInfo.lessonName}
+            titleClass={styles.userTitle}
+            v-slots={{
+              label: () => (
+                <span
+                  style={{
+                    display: 'flex',
+                    alignItems: 'center',
+                    fontSize: '13px'
+                  }}
+                >
+                  <Icon
+                    name={iconTimer}
+                    size="16"
+                    style={{ marginRight: '5px' }}
+                  />
+                  2022年4月18日13:58:55
+                </span>
+              )
+            }}
+          />
+          <Cell
+            v-slots={{
+              icon: () => (
+                <Image
+                  class={styles.avatar}
+                  src={this.userInfo.headUrl || defaultIcon}
+                />
+              ),
+              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>
+              ),
+              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>
         </CellGroup>
       </div>
     )
   }
-})
+})

BIN
src/common/images/icon_arrow.png


BIN
src/common/images/icon_timer2.png


+ 101 - 59
src/components/col-upload-video/index.tsx

@@ -1,15 +1,15 @@
-import request from "@/helpers/request";
-import { Icon, Toast, Uploader, Image } from "vant";
-import { defineComponent } from "vue";
-import styles from "./index.module.less";
-import { useCustomFieldValue } from '@vant/use';
-import { browser } from "@/helpers/utils";
+import request from '@/helpers/request'
+import { Icon, Toast, Uploader, Image } from 'vant'
+import { defineComponent } from 'vue'
+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 { postMessage } from "@/helpers/native-message";
+import iconUploader from '@common/images/icon_uploader_video.png'
+import { postMessage } from '@/helpers/native-message'
 
 export default defineComponent({
-  name: "ColUploadVideo",
+  name: 'ColUploadVideo',
   props: {
     modelValue: String,
     posterUrl: String,
@@ -17,7 +17,8 @@ export default defineComponent({
       type: String,
       default: '点击上传'
     },
-    nativeUpload: { // 是否使用原生上传, 且当前环境为app才会生效
+    nativeUpload: {
+      // 是否使用原生上传, 且当前环境为app才会生效
       type: Boolean,
       default: true
     },
@@ -32,44 +33,44 @@ export default defineComponent({
   },
   data() {
     return {
-      posterUrlInner: '',
+      posterUrlInner: ''
     }
   },
   async mounted() {
     if (this.modelValue) {
-      const urlImg = await this.getVideoBase64(this.modelValue);
-      this.posterUrlInner = urlImg as string;
+      const urlImg = await this.getVideoBase64(this.modelValue)
+      this.posterUrlInner = urlImg as string
       this.$emit('update:posterUrl', urlImg as string)
     }
-    this.posterUrlInner = this.posterUrlInner || this.posterUrl || '';
+    this.posterUrlInner = this.posterUrlInner || this.posterUrl || ''
   },
   methods: {
     beforeRead(file: any) {
       const isLt2M = file.size / 1024 / 1024 < this.size
       console.log(this.size)
       if (!isLt2M) {
-        Toast(`上传视大小不能超过 ${this.size}MB`)
+        Toast(`上传视大小不能超过 ${this.size}MB`)
         return false
       }
       return true
     },
-    beforeDelete(file: any, detail: { index: any; }) {
+    beforeDelete(file: any, detail: { index: any }) {
       // this.dataModel.splice(detail.index, 1)
       return true
     },
     async afterRead(file: any, detail: any) {
       try {
-        file.status = 'uploading';
-        file.message = '上传中...';
-        let formData = new FormData();
-        formData.append('file', file.file);
+        file.status = 'uploading'
+        file.message = '上传中...'
+        let formData = new FormData()
+        formData.append('file', file.file)
         let res = await request.post('/api-teacher/uploadFile', {
           data: formData
         })
         const url = res.data.url
         const urlImg = await this.getVideoBase64(url)
         this.posterUrlInner = urlImg as string
-        this.$emit('update:modelValue', url);
+        this.$emit('update:modelValue', url)
         this.$emit('update:posterUrl', urlImg as string)
       } catch (error) {
         //
@@ -77,57 +78,98 @@ export default defineComponent({
     },
     onClose(e: any) {
       this.posterUrlInner = ''
-      this.$emit('update:modelValue', null);
-      e.stopPropagation();
+      this.$emit('update:modelValue', null)
+      e.stopPropagation()
     },
     onNativeUpload() {
-      postMessage({ api: 'chooseFile', content: { type: 'video' } }, (res: any) => {
-        this.posterUrlInner = res.firstFrameImg
-        this.$emit('update:modelValue', res.fileUrl);
-        this.$emit('update:posterUrl', res.firstFrameImg)
-      })
+      postMessage(
+        { api: 'chooseFile', content: { type: 'video' } },
+        (res: any) => {
+          this.posterUrlInner = res.firstFrameImg
+          this.$emit('update:modelValue', res.fileUrl)
+          this.$emit('update:posterUrl', res.firstFrameImg)
+        }
+      )
     },
     getVideoBase64(url: string) {
       return new Promise(function (resolve) {
-        let dataURL = '';
-        const video = document.createElement('video');
-        video.setAttribute('crossOrigin', 'anonymous'); // 处理跨域
-        video.setAttribute('src', url);
-        video.setAttribute('preload', 'auto');
+        let dataURL = ''
+        const video = document.createElement('video')
+        video.setAttribute('crossOrigin', 'anonymous') // 处理跨域
+        video.setAttribute('src', url)
+        video.setAttribute('preload', 'auto')
         video.addEventListener('loadeddata', function () {
-          const canvas = document.createElement('canvas');
-          console.log('video.clientWidth', video.videoWidth);// 视频宽
-          console.log('video.clientHeight', video.videoHeight); // 视频高
-          const width = video.videoWidth || 750; // canvas的尺寸和图片一样
-          const height = video.videoHeight || 500;// 设置默认宽高为 750 * 500
-          canvas.width = width;
-          canvas.height = height;
-          (canvas as any).getContext('2d').drawImage(video, 0, 0, width, height); // 绘制canvas
-          dataURL = canvas.toDataURL('image/jpeg'); // 转换为base64
-          resolve(dataURL);
-        });
-      });
+          const canvas = document.createElement('canvas')
+          console.log('video.clientWidth', video.videoWidth) // 视频宽
+          console.log('video.clientHeight', video.videoHeight) // 视频高
+          const width = video.videoWidth || 750 // canvas的尺寸和图片一样
+          const height = video.videoHeight || 500 // 设置默认宽高为 750 * 500
+          canvas.width = width
+          canvas.height = height
+          ;(canvas as any)
+            .getContext('2d')
+            .drawImage(video, 0, 0, width, height) // 绘制canvas
+          dataURL = canvas.toDataURL('image/jpeg') // 转换为base64
+          resolve(dataURL)
+        })
+      })
     }
   },
   render() {
-    useCustomFieldValue(() => this.modelValue);
+    useCustomFieldValue(() => this.modelValue)
     return (
       <div class={styles['uploader-section']}>
-        {this.modelValue && this.deletable ? <Icon name="cross" onClick={this.onClose} class={styles["img-close"]} /> : null}
-        {browser().isApp && this.nativeUpload ? <div onClick={this.onNativeUpload} style={{ height: '100%' }}>{this.modelValue ? <video ref="videoUpload" class={styles.uploadImg} src={this.modelValue} poster={this.posterUrlInner} /> : <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}
-            v-slots={{
-              default: () => (this.modelValue ? <video ref="videoUpload" class={styles.uploadImg} src={this.modelValue} poster={this.posterUrlInner} /> : <div class={styles.uploader}>
+        {this.modelValue && this.deletable ? (
+          <Icon
+            name="cross"
+            onClick={this.onClose}
+            class={styles['img-close']}
+          />
+        ) : null}
+        {browser().isApp && this.nativeUpload ? (
+          <div onClick={this.onNativeUpload} style={{ height: '100%' }}>
+            {this.modelValue ? (
+              <video
+                ref="videoUpload"
+                class={styles.uploadImg}
+                src={this.modelValue}
+                poster={this.posterUrlInner}
+              />
+            ) : (
+              <div class={styles.uploader}>
                 <Icon name={iconUploader} size="32" />
                 <p class={styles.uploaderText}>{this.tips}</p>
-              </div>)
-            }}
-          /></>}
-
+              </div>
+            )}
+          </div>
+        ) : (
+          <>
+            {/* @ts-ignore */}
+            <Uploader
+              accept=".mp4"
+              afterRead={this.afterRead}
+              beforeRead={this.beforeRead}
+              beforeDelete={this.beforeDelete}
+              v-slots={{
+                default: () =>
+                  this.modelValue ? (
+                    <video
+                      ref="videoUpload"
+                      class={styles.uploadImg}
+                      src={this.modelValue}
+                      poster={this.posterUrlInner}
+                    />
+                  ) : (
+                    <div class={styles.uploader}>
+                      <Icon name={iconUploader} size="32" />
+                      <p class={styles.uploaderText}>{this.tips}</p>
+                    </div>
+                  )
+              }}
+            />
+          </>
+        )}
       </div>
     )
   }
-})
+})

+ 9 - 1
src/router/routes-teacher.ts

@@ -57,7 +57,7 @@ export default [
       {
         path: '/liveDetail',
         name: 'liveDetail',
-        component: () => import('@/teacher/open-live/live-detail'),
+        component: () => import('@/teacher/live-class/live-detail'),
         meta: {
           title: '直播课详情'
         }
@@ -93,6 +93,14 @@ export default [
         meta: {
           title: '视频课详情'
         }
+      },
+      {
+        path: '/liveCreate',
+        name: 'liveCreate',
+        component: () => import('@/teacher/live-class/create'),
+        meta: {
+          title: '视频课详情'
+        }
       }
     ]
   },

+ 14 - 0
src/styles/index.less

@@ -115,3 +115,17 @@ body {
 .mb12 {
   margin-bottom: 12px !important;
 }
+
+.btnGroup {
+  padding: 0 14px;
+  padding-bottom: 15px;
+}
+.btnMore {
+  display: flex;
+  justify-content: space-between;
+  :global {
+    .van-button {
+      width: 48%;
+    }
+  }
+}

+ 50 - 0
src/teacher/live-class/create-components/arrange.module.less

@@ -0,0 +1,50 @@
+.arrange {
+  margin: 0 14px;
+
+  .arrangeCell {
+    margin: 10px 0 0;
+    width: auto;
+    border-radius: 10px;
+    overflow: hidden;
+  }
+  .rTitle {
+    display: flex;
+    align-items: center;
+    font-size: 16px;
+    color: #333;
+    font-weight: 500;
+    &::before {
+      margin-right: 8px;
+      content: ' ';
+      display: inline-block;
+      width: 4px;
+      height: 14px;
+      background: #2dc7aa;
+      border-radius: 3px;
+    }
+  }
+  .rTag {
+    padding: 10px 0;
+    .tag {
+      background: #e9fff8;
+      margin-bottom: 8px;
+      & + .tag {
+        margin-left: 8px;
+      }
+    }
+  }
+
+  .btnGroup {
+    padding: 0 14px;
+    padding-bottom: 15px;
+  }
+  .btnMore {
+    display: flex;
+    justify-content: space-between;
+    :global {
+      .van-button {
+        width: 48%;
+      }
+    }
+  }
+}

+ 71 - 0
src/teacher/live-class/create-components/arrange.tsx

@@ -0,0 +1,71 @@
+import Calendar from '@/business-components/calendar'
+import { Button, Cell, Sticky, Tag } from 'vant'
+import { defineComponent } from 'vue'
+import styles from './arrange.module.less'
+import { createState } from './createState'
+
+export default defineComponent({
+  name: 'arrange',
+
+  render() {
+    return (
+      <div class={styles.arrange}>
+        <Calendar />
+
+        <Cell
+          class={[styles.arrangeCell, 'mb12']}
+          v-slots={{
+            title: () => (
+              <div class={styles.rTitle}>
+                <span>评测记录</span>
+              </div>
+            ),
+            label: () => (
+              <div class={styles.rTag}>
+                <Tag
+                  plain
+                  round
+                  closeable
+                  size="large"
+                  type="primary"
+                  class={styles.tag}
+                >
+                  周一 9:30~9:55
+                </Tag>
+                <Tag
+                  plain
+                  round
+                  closeable
+                  size="large"
+                  type="primary"
+                  class={styles.tag}
+                >
+                  周一 9:30~9:55
+                </Tag>
+              </div>
+            )
+          }}
+        ></Cell>
+
+        <Sticky offsetBottom={0} position="bottom">
+          <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>
+      </div>
+    )
+  }
+})

+ 20 - 0
src/teacher/live-class/create-components/course-plan.module.less

@@ -0,0 +1,20 @@
+.coursePlan {
+  .courseTime {
+    font-size: 14px;
+    color: #999999;
+  }
+
+  .btnGroup {
+    padding: 0 14px;
+    padding-bottom: 15px;
+  }
+  .btnMore {
+    display: flex;
+    justify-content: space-between;
+    :global {
+      .van-button {
+        width: 48%;
+      }
+    }
+  }
+}

+ 68 - 0
src/teacher/live-class/create-components/course-plan.tsx

@@ -0,0 +1,68 @@
+import { Button, Field, Form, Sticky } from 'vant'
+import { defineComponent } from 'vue'
+import { createState } from './createState'
+import styles from './course-plan.module.less'
+import ColField from '@/components/col-field'
+import ColFieldGroup from '@/components/col-field-group'
+
+export default defineComponent({
+  name: 'course-plan',
+  data() {
+    return {
+      list: [1, 2, 3]
+    }
+  },
+  render() {
+    return (
+      <Form
+        class={styles.coursePlan}
+        onSubmit={() => (createState.active = 3)}
+        scrollToError
+      >
+        {this.list.map((item: any, index: number) => (
+          <ColFieldGroup>
+            <ColField
+              title={`第${index + 1}课`}
+              required
+              border={false}
+              v-slots={{
+                right: () => (
+                  <span class={styles.courseTime}>2022年4月18日11:40:17</span>
+                )
+              }}
+            >
+              <Field
+                name="lessonDesc"
+                placeholder="请输入课程计划"
+                rows="3"
+                maxlength={200}
+                autosize
+                rules={[{ required: true, message: '请输入课程计划' }]}
+                type="textarea"
+              />
+            </ColField>
+          </ColFieldGroup>
+        ))}
+
+        <Sticky offsetBottom={0} position="bottom">
+          <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>
+    )
+  }
+})

+ 88 - 0
src/teacher/live-class/create-components/course-start.module.less

@@ -0,0 +1,88 @@
+.courseStart {
+  .btnGroup {
+    padding: 0 14px;
+    padding-bottom: 15px;
+  }
+  .btnMore {
+    display: flex;
+    justify-content: space-between;
+    :global {
+      .van-button {
+        width: 48%;
+      }
+    }
+  }
+
+  .infoField {
+    width: 50vw;
+    font-size: 16px;
+
+    :global {
+      .van-tab {
+        font-size: 16px;
+      }
+      .van-tabs__nav--line {
+        padding-left: 0;
+      }
+      .van-tab--active {
+        color: #000;
+      }
+    }
+  }
+  .photoTip {
+    font-size: 14px;
+    color: #999999;
+    line-height: 27px;
+    padding: 5px 0;
+  }
+  .boxStyle {
+    background: transparent;
+    width: 18px;
+    height: 18px;
+    border: transparent;
+  }
+  :global {
+    .van-radio {
+      display: inline-block;
+      align-items: inherit;
+      overflow: inherit;
+    }
+    .van-radio__icon {
+      height: 18px;
+      line-height: 18px;
+      display: inline-block;
+      vertical-align: sub;
+    }
+    .van-radio__label {
+      line-height: 18px;
+    }
+  }
+  .imgContainer {
+    width: 150px;
+    height: 100px;
+    border-radius: 10px;
+    overflow: hidden;
+    margin: 0 0 12px;
+    position: relative;
+
+    :global {
+      .van-radio {
+        position: absolute;
+        bottom: 10px;
+        right: 20px;
+        z-index: 9;
+      }
+    }
+  }
+  .stepTips {
+    padding: 7px 12px;
+    margin-bottom: 15px;
+    background: linear-gradient(139deg, #fff6ee 0%, #ffecdd 100%);
+    border-radius: 6px;
+    line-height: 22px;
+    font-size: 12px;
+    color: #e0945a;
+    display: flex;
+    align-items: center;
+  }
+}

+ 206 - 0
src/teacher/live-class/create-components/course-start.tsx

@@ -0,0 +1,206 @@
+import ColField from '@/components/col-field'
+import ColFieldGroup from '@/components/col-field-group'
+import {
+  Button,
+  Col,
+  Field,
+  Form,
+  Radio,
+  RadioGroup,
+  Row,
+  Sticky,
+  Tab,
+  Tabs,
+  Image,
+  Icon
+} from 'vant'
+import { defineComponent } from 'vue'
+import { createState } from './createState'
+import styles from './course-start.module.less'
+import ColUpload from '@/components/col-upload'
+
+import activeButtonIcon from '@common/images/icon_checkbox.png'
+import inactiveButtonIcon from '@common/images/icon_checkbox_default.png'
+
+export default defineComponent({
+  name: 'course-start',
+  methods: {
+    tabChange(name: number) {
+      createState.tabIndex = name
+    },
+    selectImg(val: string) {
+      createState.lessonGroup.lessonCoverUrl = ''
+      createState.lessonGroup.lessonCoverTemplateUrl = val
+    }
+  },
+  render() {
+    return (
+      <Form
+        class={styles.courseStart}
+        onSubmit={() => (createState.active = 5)}
+        scrollToError
+      >
+        <ColFieldGroup>
+          <ColField title="开售日期" required>
+            <Field
+              v-model={createState.lessonGroup.lessonName}
+              name="lessonName"
+              readonly
+              isLink
+              placeholder="请选择停售日期"
+              rules={[{ required: true, message: '请选择停售日期' }]}
+            />
+          </ColField>
+          <ColField title="停售日期" required>
+            <Field
+              name="lessonSubjectName"
+              readonly
+              isLink
+              rules={[{ required: true, message: '请选择停售日期' }]}
+              placeholder="请选择停售日期"
+            />
+          </ColField>
+        </ColFieldGroup>
+
+        <ColFieldGroup>
+          <ColField
+            title="最低开课人数"
+            required
+            style={{
+              marginBottom: '10px'
+            }}
+          >
+            <Field
+              v-model={createState.lessonGroup.lessonPrice}
+              name="lessonPrice"
+              placeholder="请输入最低开课人数"
+              type="number"
+              maxlength={8}
+              rules={[{ required: true, message: '请输入最低开课人数' }]}
+              v-slots={{
+                button: () => <span>人</span>
+              }}
+            />
+          </ColField>
+          <div class={styles.stepTips}>
+            课程停售时付费学员达到该人数可开课,若未达到该人数课程将会失效,已付费学员将自动退款
+          </div>
+        </ColFieldGroup>
+
+        <ColFieldGroup>
+          <ColField
+            required
+            border={false}
+            v-slots={{
+              title: () => (
+                <Tabs
+                  v-model:active={createState.tabIndex}
+                  class={styles.infoField}
+                  onChange={this.tabChange}
+                  shrink
+                  color="var(--van-primary)"
+                  lineWidth={20}
+                >
+                  <Tab title="图片模板" name={1}></Tab>
+                  <Tab title="自定义模板" name={2}></Tab>
+                </Tabs>
+              )
+            }}
+          >
+            <p class={styles.photoTip}>模板图片将作为改课程封面为学员展示</p>
+            {createState.tabIndex === 1 ? (
+              <Field
+                name="lessonCoverTemplateUrl"
+                rules={[{ required: true, message: '请选择课程声部' }]}
+                v-slots={{
+                  input: () => (
+                    <RadioGroup
+                      v-model={createState.lessonGroup.lessonCoverTemplateUrl}
+                    >
+                      <Row justify="space-between" style={{ width: '100%' }}>
+                        {createState.templateList.map((item: any) => (
+                          <Col
+                            span={12}
+                            class={styles.imgContainer}
+                            onClick={() => this.selectImg(item)}
+                          >
+                            <Image class={styles.imgContainer} src={item} />
+                            <Radio
+                              name={item}
+                              v-slots={{
+                                icon: (props: any) => (
+                                  <Icon
+                                    class={styles.boxStyle}
+                                    name={
+                                      props.checked
+                                        ? activeButtonIcon
+                                        : inactiveButtonIcon
+                                    }
+                                    size="18"
+                                  />
+                                )
+                              }}
+                            />
+                          </Col>
+                        ))}
+                      </Row>
+                    </RadioGroup>
+                  )
+                }}
+              />
+            ) : null}
+            {createState.tabIndex == 2 ? (
+              <Field
+                name="lessonCoverUrl"
+                rules={[{ required: true, message: '请选择课程声部' }]}
+                v-slots={{
+                  input: () => (
+                    <Row justify="space-between" style={{ width: '100%' }}>
+                      <Col span={12} class={styles.imgContainer}>
+                        <ColUpload
+                          cropper
+                          options={{
+                            fixedNumber: [3, 2],
+                            autoCropWidth: 750,
+                            autoCropHeight: 500
+                          }}
+                          onUploadChange={(val: any) => {
+                            if (val) {
+                              createState.lessonGroup.lessonCoverTemplateUrl =
+                                ''
+                            }
+                          }}
+                          v-model={createState.lessonGroup.lessonCoverUrl}
+                          class={styles.imgContainer}
+                        />
+                      </Col>
+                    </Row>
+                  )
+                }}
+              />
+            ) : null}
+          </ColField>
+        </ColFieldGroup>
+
+        <Sticky offsetBottom={0} position="bottom">
+          <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>
+    )
+  }
+})

+ 17 - 0
src/teacher/live-class/create-components/course.module.less

@@ -0,0 +1,17 @@
+.classInfo {
+  .class-info-tip {
+    font-size: 14px;
+    color: #999999;
+    line-height: 27px;
+    padding: 0 14px 12px;
+
+    span {
+      color: #ff4e19;
+    }
+  }
+
+  .btnGroup {
+    padding: 0 14px;
+    padding-bottom: 15px;
+  }
+}

+ 201 - 0
src/teacher/live-class/create-components/course.tsx

@@ -0,0 +1,201 @@
+import ColField from '@/components/col-field'
+import ColFieldGroup from '@/components/col-field-group'
+import ColPopup from '@/components/col-popup'
+import SubjectModel from '@/business-components/subject-list'
+import { ActionSheet, Button, Field, Form, Sticky } from 'vant'
+import { defineComponent } from 'vue'
+import styles from './course.module.less'
+import { createState } from './createState'
+import { verifyNumberIntegerAndFloat } from '@/helpers/toolsValidate'
+
+export default defineComponent({
+  name: 'course',
+  data() {
+    return {
+      subjectStatus: false,
+      classTimeStatus: false,
+      actions: [
+        {
+          name: '30'
+        },
+        {
+          name: '45'
+        },
+        {
+          name: '90'
+        }
+      ]
+    }
+  },
+  computed: {
+    choiceSubjectIds() {
+      // 选择的科目编号
+      let ids = createState.lessonGroup.lessonSubject
+        ? Number(createState.lessonGroup.lessonSubject)
+        : null
+      return ids ? [ids] : []
+    },
+    subjectList() {
+      // 学科列表
+      return createState.subjectList || []
+    },
+    lessonSubjectName() {
+      // 选择的科目
+      let tempStr = ''
+      this.subjectList.forEach((parent: any) => {
+        parent.subjects &&
+          parent.subjects.forEach((sub: any) => {
+            if (this.choiceSubjectIds.includes(sub.id)) {
+              tempStr = sub.name
+            }
+          })
+      })
+      return tempStr
+    },
+    calcRatePrice() {
+      // 计算手续费
+      let rate = createState.rate || 0
+      let price = createState.lessonGroup.lessonPrice || 0
+      return (price - (rate / 100) * price).toFixed(2)
+    }
+  },
+  async mounted() {},
+  methods: {
+    onChoice(id: number) {
+      // createState.lessonGroup.lessonSubject = id
+      this.subjectStatus = false
+    },
+    onFormatter(val: any) {
+      return verifyNumberIntegerAndFloat(val)
+    },
+    onSelect(action: any) {
+      console.log(action)
+    }
+  },
+  render() {
+    return (
+      <Form
+        class={styles.classInfo}
+        onSubmit={() => (createState.active = 2)}
+        scrollToError
+      >
+        <ColFieldGroup>
+          <ColField title="课程名称" required>
+            <Field
+              v-model={createState.lessonGroup.lessonName}
+              name="lessonName"
+              maxlength={50}
+              placeholder="请输入您的课程名称"
+              rules={[{ required: true, message: '请输入您的课程名称' }]}
+            />
+          </ColField>
+          <ColField title="课程声部" required>
+            <Field
+              modelValue={this.lessonSubjectName}
+              name="lessonSubjectName"
+              readonly
+              isLink
+              onClick={() => {
+                this.subjectStatus = true
+              }}
+              rules={[{ required: true, message: '请选择课程声部' }]}
+              placeholder="请选择课程声部"
+            />
+          </ColField>
+
+          <ColField title="课程介绍" required border={false}>
+            <Field
+              name="lessonDesc"
+              placeholder="请输入课程介绍"
+              rows="3"
+              maxlength={200}
+              autosize
+              rules={[{ required: true, message: '请输入课程介绍' }]}
+              type="textarea"
+            />
+          </ColField>
+        </ColFieldGroup>
+
+        <ColFieldGroup>
+          <ColField title="课时数" required>
+            <Field
+              v-model={createState.lessonGroup.lessonPrice}
+              name="lessonPrice"
+              placeholder="请输入您的课时数"
+              formatter={this.onFormatter}
+              type="number"
+              maxlength={8}
+              rules={[{ required: true, message: '请输入您的课时数' }]}
+              v-slots={{
+                button: () => <span>课时</span>
+              }}
+            />
+          </ColField>
+          <ColField title="单课时时长" required>
+            <Field
+              modelValue={this.lessonSubjectName}
+              name="lessonSubjectName"
+              readonly
+              isLink
+              onClick={() => {
+                this.classTimeStatus = true
+              }}
+              rules={[{ required: true, message: '请选择单课时时长' }]}
+              placeholder="请选择单课时时长"
+            />
+          </ColField>
+          <ColField title="课程组售价" required>
+            <Field
+              v-model={createState.lessonGroup.lessonPrice}
+              name="lessonPrice"
+              placeholder="请输入您的课程组售价"
+              formatter={this.onFormatter}
+              type="number"
+              maxlength={8}
+              rules={[{ required: true, message: '请输入您的课程组售价' }]}
+              v-slots={{
+                button: () => <span>元</span>
+              }}
+            />
+          </ColField>
+        </ColFieldGroup>
+
+        <div class={styles['class-info-tip']}>
+          <p>扣除手续费后您的课程预计收入为:</p>
+          <p>
+            单课时<span>{this.calcRatePrice}</span>元/人
+          </p>
+          <p>
+            课程组总收入<span>{this.calcRatePrice}</span>元/人
+          </p>
+          <p>您的课程收入将在课程结束后结算到您的账户中</p>
+        </div>
+
+        <Sticky offsetBottom={0} position="bottom">
+          <div class={'btnGroup'}>
+            <Button block round type="primary" native-type="submit">
+              下一步
+            </Button>
+          </div>
+        </Sticky>
+
+        <ColPopup v-model={this.subjectStatus}>
+          <SubjectModel
+            selectType="Radio"
+            subjectList={createState.subjectList}
+            choiceSubjectIds={this.choiceSubjectIds}
+            onChoice={this.onChoice}
+          />
+        </ColPopup>
+
+        <ActionSheet
+          v-model:show={this.classTimeStatus}
+          actions={createState.minutes}
+          cancelText="取消"
+          closeOnClickAction
+          onSelect={this.onSelect}
+        />
+      </Form>
+    )
+  }
+})

+ 21 - 0
src/teacher/live-class/create-components/createState.ts

@@ -0,0 +1,21 @@
+import { reactive } from 'vue'
+
+export const createState = reactive({
+  subjectList: [], // 声部列表
+  active: 1,
+  rate: 0,
+  minutes: [] as any,
+  tabIndex: 1,
+  templateList: [
+    'https://daya.ks3-cn-beijing.ksyun.com/202110/Sn2OAjr.png',
+    'https://daya.ks3-cn-beijing.ksyun.com/202108/ShHJ1Bb.png',
+    'https://daya.ks3-cn-beijing.ksyun.com/202110/Sn76BUQ.png'
+  ], // 模板列表
+  lessonGroup: {
+    lessonSubject: '',
+    lessonName: '',
+    lessonPrice: null as any,
+    lessonCoverTemplateUrl: '',
+    lessonCoverUrl: ''
+  }
+})

+ 15 - 0
src/teacher/live-class/create-components/detail.module.less

@@ -0,0 +1,15 @@
+.detail {
+  .btnGroup {
+    padding: 0 14px;
+    padding-bottom: 15px;
+  }
+  .btnMore {
+    display: flex;
+    justify-content: space-between;
+    :global {
+      .van-button {
+        width: 48%;
+      }
+    }
+  }
+}

+ 60 - 0
src/teacher/live-class/create-components/detail.tsx

@@ -0,0 +1,60 @@
+import CoursePlanStep from '@/business-components/course-plan-step'
+import SectionDetail from '@/business-components/section-detail'
+import UserDetail from '@/business-components/user-detail'
+import { Button, Sticky } from 'vant'
+import { defineComponent } from 'vue'
+import { createState } from './createState'
+import styles from './detail.module.less'
+
+export default defineComponent({
+  name: 'detail',
+  render() {
+    return (
+      <div class={[styles['detail']]}>
+        <UserDetail
+          userInfo={{
+            headUrl: 'https://img.yzcdn.cn/vant/cat.jpeg',
+            username: '小酷老师',
+            lessonPrice: 0,
+            lessonCoverUrl: 'https://img.yzcdn.cn/vant/cat.jpeg',
+            lessonName: '竖笛演奏'
+          }}
+        />
+        <SectionDetail>
+          <p class={styles.introduction}>
+            小酷老师带您零基础学习竖笛,通过4节课的学习掌握竖笛演奏的基本方式,培养娘好的吐息习惯。
+          </p>
+        </SectionDetail>
+
+        <SectionDetail
+          title="课程列表"
+          icon="courseList"
+          titleShow={false}
+          class={'mb12'}
+          contentStyle={{ paddingTop: '0' }}
+        >
+          <CoursePlanStep />
+        </SectionDetail>
+
+        <Sticky offsetBottom={0} position="bottom">
+          <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>
+      </div>
+    )
+  }
+})

+ 32 - 0
src/teacher/live-class/create-components/steps.module.less

@@ -0,0 +1,32 @@
+.steps {
+  padding-top: 15px;
+  padding-left: 14px;
+  .gridName {
+    font-size: 14px;
+    font-weight: 500;
+    color: #b4b4b4;
+    line-height: 20px;
+    padding-top: 4px;
+    &.active {
+      color: var(--van-primary);
+    }
+  }
+  :global {
+    .van-grid-item {
+      padding-right: 14px;
+      &:first-child {
+        padding-right: 10px;
+      }
+    }
+    .van-grid-item__content {
+      padding-top: 7px;
+      padding-bottom: 7px;
+      border-radius: 10px;
+      overflow: hidden;
+    }
+    .van-badge__wrapper {
+      display: flex;
+      align-items: center;
+    }
+  }
+}

+ 112 - 0
src/teacher/live-class/create-components/steps.tsx

@@ -0,0 +1,112 @@
+import { Grid, GridItem, Icon } from 'vant'
+import { defineComponent } from 'vue'
+import { createState } from './createState'
+import styles from './steps.module.less'
+
+export const getAssetsHomeFile = (fileName: string) => {
+  const path = `../images/${fileName}`
+  const modules = import.meta.globEager('../images/*')
+  return modules[path].default
+}
+
+export default defineComponent({
+  name: 'steps',
+  render() {
+    return (
+      <Grid class={styles.steps} border={false} columnNum="4">
+        <GridItem
+          v-slots={{
+            default: () => (
+              <>
+                <Icon
+                  name={getAssetsHomeFile('icon_course_active.png')}
+                  size={24}
+                />
+                <span
+                  class={[
+                    styles.gridName,
+                    createState.active >= 1 ? styles.active : null
+                  ]}
+                >
+                  课程信息
+                </span>
+              </>
+            )
+          }}
+        />
+        <GridItem
+          v-slots={{
+            default: () => (
+              <>
+                <Icon
+                  name={
+                    createState.active >= 2
+                      ? getAssetsHomeFile('icon_plan_active.png')
+                      : getAssetsHomeFile('icon_plan_default.png')
+                  }
+                  size={24}
+                />
+                <span
+                  class={[
+                    styles.gridName,
+                    createState.active >= 2 ? styles.active : null
+                  ]}
+                >
+                  教学计划
+                </span>
+              </>
+            )
+          }}
+        />
+        <GridItem
+          v-slots={{
+            default: () => (
+              <>
+                <Icon
+                  name={
+                    createState.active >= 3
+                      ? getAssetsHomeFile('icon_arrange_active.png')
+                      : getAssetsHomeFile('icon_arrange_default.png')
+                  }
+                  size={24}
+                />
+                <span
+                  class={[
+                    styles.gridName,
+                    createState.active >= 3 ? styles.active : null
+                  ]}
+                >
+                  课程安排
+                </span>
+              </>
+            )
+          }}
+        />
+        <GridItem
+          v-slots={{
+            default: () => (
+              <>
+                <Icon
+                  name={
+                    createState.active >= 4
+                      ? getAssetsHomeFile('icon_start_active.png')
+                      : getAssetsHomeFile('icon_start_default.png')
+                  }
+                  size={24}
+                />
+                <span
+                  class={[
+                    styles.gridName,
+                    createState.active >= 4 ? styles.active : null
+                  ]}
+                >
+                  开课条件
+                </span>
+              </>
+            )
+          }}
+        />
+      </Grid>
+    )
+  }
+})

+ 0 - 0
src/teacher/live-class/create.module.less


+ 72 - 0
src/teacher/live-class/create.tsx

@@ -0,0 +1,72 @@
+import { defineComponent } from 'vue'
+import Steps from './create-components/steps'
+import { createState } from './create-components/createState'
+import Course from './create-components/course'
+import CoursePlan from './create-components/course-plan'
+import CourseStart from './create-components/course-start'
+import Detail from './create-components/detail'
+import { Sticky } from 'vant'
+import Arrange from './create-components/arrange'
+import request from '@/helpers/request'
+
+export default defineComponent({
+  name: 'LiveCreate',
+  async mounted() {
+    try {
+      // 获取手续费和分钟数
+      let config = await request.get(
+        '/api-teacher/sysConfig/queryByParamNameList',
+        {
+          params: {
+            paramNames: 'live_service_rate,live_time_setting'
+          }
+        }
+      )
+      let configData = config.data || []
+      configData.forEach((item: any) => {
+        if (item.paramName === 'live_time_setting') {
+          let mins = item.paramValue ? JSON.parse(item.paramValue) : []
+          let tempArr = [] as any
+          mins.forEach((item: any) => {
+            tempArr.push({
+              ...item,
+              name: item.courseMinutes
+            })
+          })
+          createState.minutes = [...tempArr]
+        }
+        if (item.paramName === 'live_service_rate') {
+          createState.rate = item.paramValue
+        }
+      })
+
+      let teacher = await request.post('/api-teacher/teacher/querySubject')
+      createState.subjectList = teacher.data || []
+    } catch (err: any) {
+      console.log(err)
+    }
+  },
+  render() {
+    return (
+      <div class="live-create">
+        {createState.active !== 5 && (
+          <Sticky position="top" offsetTop={0}>
+            <Steps
+              style={{ backgroundColor: '#f6f8f9', paddingBottom: '12px' }}
+            />
+          </Sticky>
+        )}
+
+        {createState.active === 1 && <Course />}
+
+        {createState.active == 2 && <CoursePlan />}
+
+        {createState.active == 3 && <Arrange />}
+
+        {createState.active == 4 && <CourseStart />}
+
+        {createState.active == 5 && <Detail />}
+      </div>
+    )
+  }
+})

BIN
src/teacher/live-class/images/icon_arrange_active.png


BIN
src/teacher/live-class/images/icon_arrange_default.png


BIN
src/teacher/live-class/images/icon_course_active.png


BIN
src/teacher/live-class/images/icon_plan_active.png


BIN
src/teacher/live-class/images/icon_plan_default.png


BIN
src/teacher/live-class/images/icon_start_active.png


BIN
src/teacher/live-class/images/icon_start_default.png


+ 0 - 0
src/teacher/open-live/live-detail.module.less → src/teacher/live-class/live-detail.module.less


+ 43 - 0
src/teacher/live-class/live-detail.tsx

@@ -0,0 +1,43 @@
+import CoursePlanStep from '@/business-components/course-plan-step'
+import SectionDetail from '@/business-components/section-detail'
+import UserDetail from '@/business-components/user-detail'
+import UserList from '@/business-components/user-list'
+import { Tab, Tabs } from 'vant'
+import { defineComponent } from 'vue'
+import styles from './live-detail.module.less'
+
+export default defineComponent({
+  name: 'LiveDetail',
+  render() {
+    return (
+      <div class={[styles['live-detail'], 'mb12']}>
+        {/* <UserDetail /> */}
+        <SectionDetail>
+          <p class={styles.introduction}>
+            小酷老师带您零基础学习竖笛,通过4节课的学习掌握竖笛演奏的基本方式,培养娘好的吐息习惯。
+          </p>
+        </SectionDetail>
+
+        <SectionDetail
+          title="课程列表"
+          icon="courseList"
+          titleShow={false}
+          contentStyle={{ paddingTop: '0' }}
+        >
+          <Tabs color="var(--van-primary)" lineWidth={20} sticky>
+            <Tab title="全部课程" titleClass="van-hairline--bottom">
+              <CoursePlanStep />
+            </Tab>
+            <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 => (
+                  <UserList class="mb12" />
+                )
+              )}
+            </Tab>
+          </Tabs>
+        </SectionDetail>
+      </div>
+    )
+  }
+})

+ 0 - 34
src/teacher/open-live/live-detail.tsx

@@ -1,34 +0,0 @@
-import CoursePlanStep from "@/business-components/course-plan-step";
-import SectionDetail from "@/business-components/section-detail";
-import UserDetail from "@/business-components/user-detail";
-import UserList from "@/business-components/user-list";
-import { Tab, Tabs } from "vant";
-import { defineComponent } from "vue";
-import styles from "./live-detail.module.less";
-
-export default defineComponent({
-  name: "LiveDetail",
-  render() {
-    return (
-      <div class={[styles['live-detail'], 'mb12']}>
-        {/* <UserDetail /> */}
-        <SectionDetail>
-          <p class={styles.introduction}>小酷老师带您零基础学习竖笛,通过4节课的学习掌握竖笛演奏的基本方式,培养娘好的吐息习惯。</p>
-        </SectionDetail>
-
-        <SectionDetail title="课程列表" icon="courseList" titleShow={false} contentStyle={{ paddingTop: '0' }}>
-          <Tabs color="var(--van-primary)" lineWidth={20} sticky>
-            <Tab title="全部课程" titleClass="van-hairline--bottom">
-              <CoursePlanStep />
-            </Tab>
-            <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 => (
-                <UserList class="mb12" />
-              ))}
-            </Tab>
-          </Tabs>
-        </SectionDetail>
-      </div>
-    )
-  }
-})

+ 51 - 24
src/teacher/teacher-cert/steps.tsx

@@ -1,21 +1,21 @@
-import { defineComponent } from "vue";
-import { Icon } from 'vant';
+import { defineComponent } from 'vue'
+import { Icon } from 'vant'
 import { teacherState } from './teacherState'
-import styles from './steps.module.less';
-import baseDefault from './images/base_default.png';
-import baseActive from './images/base_active.png';
-import baseFinish from './images/base_finish.png';
-import nameDefault from './images/name_default.png';
-import nameActive from './images/name_active.png';
-import nameFinish from './images/name_finish.png';
-import educationDefault from './images/education_default.png';
-import educationActive from './images/education_active.png';
-import educationFinish from './images/education_finish.png';
+import styles from './steps.module.less'
+import baseDefault from './images/base_default.png'
+import baseActive from './images/base_active.png'
+import baseFinish from './images/base_finish.png'
+import nameDefault from './images/name_default.png'
+import nameActive from './images/name_active.png'
+import nameFinish from './images/name_finish.png'
+import educationDefault from './images/education_default.png'
+import educationActive from './images/education_active.png'
+import educationFinish from './images/education_finish.png'
 
 import horn from './images/icon_horn.png'
 
 export default defineComponent({
-  name: "steps",
+  name: 'steps',
   computed: {
     stepOne() {
       if (teacherState.active === 1) {
@@ -47,28 +47,55 @@ export default defineComponent({
   },
   render() {
     return (
-      <div class={[styles.steps, teacherState.active == 3 ? styles.paddingBottom12 : null]}>
+      <div
+        class={[
+          styles.steps,
+          teacherState.active == 3 ? styles.paddingBottom12 : null
+        ]}
+      >
         <div class={styles.stepContent}>
           <div class={[styles.stepItem]}>
             <Icon name={this.stepOne} size="38" />
-            <p class={this.stepOne != baseDefault ? styles.active : null}>实名认证</p>
+            <p class={this.stepOne != baseDefault ? styles.active : null}>
+              实名认证
+            </p>
           </div>
-          <div class={[styles.stepItem, styles.line, this.stepTwo != nameDefault ? styles.lineActive : null]}></div>
+          <div
+            class={[
+              styles.stepItem,
+              styles.line,
+              this.stepTwo != nameDefault ? styles.lineActive : null
+            ]}
+          ></div>
           <div class={[styles.stepItem]}>
             <Icon name={this.stepTwo} size="38" />
-            <p class={this.stepTwo != nameDefault ? styles.active : null}>基本信息</p>
+            <p class={this.stepTwo != nameDefault ? styles.active : null}>
+              基本信息
+            </p>
           </div>
-          <div class={[styles.stepItem, styles.line, this.stepThree != educationDefault ? styles.lineActive : null]}></div>
+          <div
+            class={[
+              styles.stepItem,
+              styles.line,
+              this.stepThree != educationDefault ? styles.lineActive : null
+            ]}
+          ></div>
           <div class={[styles.stepItem]}>
             <Icon name={this.stepThree} size="38" />
-            <p class={this.stepThree != educationDefault ? styles.active : null}>学历信息</p>
+            <p
+              class={this.stepThree != educationDefault ? styles.active : null}
+            >
+              学历信息
+            </p>
           </div>
         </div>
-        { teacherState.active == 3 ? <div class={styles.stepTips}>
-          <Icon name={horn} size="20" style={{ marginRight: '6px' }} />
-          完整填写学历信息有助于您更快的通过认证审核
-        </div> : null }
+        {teacherState.active == 3 ? (
+          <div class={styles.stepTips}>
+            <Icon name={horn} size="20" style={{ marginRight: '6px' }} />
+            完整填写学历信息有助于您更快的通过认证审核
+          </div>
+        ) : null}
       </div>
     )
   }
-})
+})

+ 2 - 2
src/teacher/teacher-cert/teacherState.ts

@@ -1,4 +1,4 @@
-import { reactive } from 'vue';
+import { reactive } from 'vue'
 
 export const teacherState = reactive({
   authStatus: false as boolean, // 是否立即认证
@@ -17,4 +17,4 @@ export const teacherState = reactive({
     degreeCertificate: null,
     teacherCertificate: null
   }
-});
+})

+ 82 - 29
src/teacher/video-class/class-content.tsx

@@ -1,23 +1,33 @@
-import ColField from "@/components/col-field";
-import ColFieldGroup from "@/components/col-field-group";
-import ColUpload from "@/components/col-upload";
-import ColUploadVideo from "@/components/col-upload-video";
-import { Button, Col, Dialog, Field, Form, Icon, Row, Sticky, Switch } from "vant";
-import { defineComponent } from "vue";
-import styles from './class-content.module.less';
-import { createState } from './createState';
+import ColField from '@/components/col-field'
+import ColFieldGroup from '@/components/col-field-group'
+import ColUpload from '@/components/col-upload'
+import ColUploadVideo from '@/components/col-upload-video'
+import {
+  Button,
+  Col,
+  Dialog,
+  Field,
+  Form,
+  Icon,
+  Row,
+  Sticky,
+  Switch
+} from 'vant'
+import { defineComponent } from 'vue'
+import styles from './class-content.module.less'
+import { createState } from './createState'
 
 export default defineComponent({
-  name: "ClassContent",
+  name: 'ClassContent',
   data() {
     return {
       url: '',
-      checked: null,
+      checked: null
     }
   },
   methods: {
     onSubmit(values: any) {
-      createState.active = 3;
+      createState.active = 3
     },
     addItem() {
       createState.lessonList.push({
@@ -25,7 +35,7 @@ export default defineComponent({
         videoContent: '',
         videoUrl: '',
         coverUrl: '',
-        posterUrl: '', // 视屏封面图
+        posterUrl: '' // 视频封面图
       })
     },
     removeItem(index: number) {
@@ -34,21 +44,31 @@ export default defineComponent({
       Dialog.confirm({
         title: '操作',
         message: `确定删除该条数据吗?`,
-        confirmButtonColor: '#2DC7AA',
+        confirmButtonColor: '#2DC7AA'
+      }).then(() => {
+        createState.lessonList.splice(index, 1)
       })
-        .then(() => {
-          createState.lessonList.splice(index, 1)
-        })
     }
   },
   render() {
     return (
-      <Form class={styles['class-content']} onSubmit={this.onSubmit} scrollToError>
+      <Form
+        class={styles['class-content']}
+        onSubmit={this.onSubmit}
+        scrollToError
+      >
         {createState.lessonList.map((item: any, index: number) => (
           <>
             <div class={styles.titleSection}>
               <span class={styles.title}>第{index + 1}课</span>
-              <Icon name="delete-o" class={createState.lessonList.length <= 1 ? styles.disabled : null} onClick={() => this.removeItem(index)} size={20} />
+              <Icon
+                name="delete-o"
+                class={
+                  createState.lessonList.length <= 1 ? styles.disabled : null
+                }
+                onClick={() => this.removeItem(index)}
+                size={20}
+              />
             </div>
             <ColFieldGroup>
               <ColField title="课程标题" required>
@@ -70,7 +90,10 @@ export default defineComponent({
                 />
               </ColField>
               <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}>
                     <Field
                       style={{ padding: 0 }}
@@ -78,7 +101,12 @@ export default defineComponent({
                       rules={[{ required: true, message: '请上传课程视频' }]}
                       v-slots={{
                         input: () => (
-                          <ColUploadVideo v-model={item.videoUrl} v-model:posterUrl={item.posterUrl} class={styles.upload} tips="点击上传视屏" />
+                          <ColUploadVideo
+                            v-model={item.videoUrl}
+                            v-model:posterUrl={item.posterUrl}
+                            class={styles.upload}
+                            tips="点击上传视频"
+                          />
                         )
                       }}
                     />
@@ -91,11 +119,17 @@ export default defineComponent({
                       error
                       v-slots={{
                         input: () => (
-                          <ColUpload class={styles.upload} cropper options={{
-                            fixedNumber: [3, 2],
-                            autoCropWidth: 750,
-                            autoCropHeight: 500,
-                          }} v-model={item.coverUrl} tips="点击上传视频封面" />
+                          <ColUpload
+                            class={styles.upload}
+                            cropper
+                            options={{
+                              fixedNumber: [3, 2],
+                              autoCropWidth: 750,
+                              autoCropHeight: 500
+                            }}
+                            v-model={item.coverUrl}
+                            tips="点击上传视频封面"
+                          />
                         )
                       }}
                     />
@@ -106,15 +140,34 @@ 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">
           <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>
+            <Button
+              block
+              round
+              type="primary"
+              plain
+              onClick={() => {
+                createState.active = 1
+              }}
+            >
+              上一步
+            </Button>
+            <Button block round type="primary" native-type="submit">
+              提交
+            </Button>
           </div>
         </Sticky>
       </Form>
     )
   }
-})
+})

+ 23 - 0
src/teacher/video-class/class-info.module.less

@@ -21,6 +21,29 @@
     }
   }
 
+  .boxStyle {
+    background: transparent;
+    width: 18px;
+    height: 18px;
+    border: transparent;
+  }
+  :global {
+    .van-radio {
+      display: inline-block;
+      align-items: inherit;
+      overflow: inherit;
+    }
+    .van-radio__icon {
+      height: 18px;
+      line-height: 18px;
+      display: inline-block;
+      vertical-align: sub;
+    }
+    .van-radio__label {
+      line-height: 18px;
+    }
+  }
+
   .imgContainer {
     width: 150px;
     height: 100px;

+ 159 - 81
src/teacher/video-class/class-info.tsx

@@ -1,52 +1,75 @@
-import ColField from "@/components/col-field";
-import ColFieldGroup from "@/components/col-field-group";
-import ColPopup from "@/components/col-popup";
-import ColUpload from "@/components/col-upload";
-import request from "@/helpers/request";
-import { verifyNumberIntegerAndFloat } from "@/helpers/toolsValidate";
-import { Button, Col, Field, Form, Image, Radio, RadioGroup, Row, Sticky, Tab, Tabs } from "vant";
-import { defineComponent } from "vue";
-import SubjectModel from "../../business-components/subject-list";
-import styles from "./class-info.module.less";
+import ColField from '@/components/col-field'
+import ColFieldGroup from '@/components/col-field-group'
+import ColPopup from '@/components/col-popup'
+import ColUpload from '@/components/col-upload'
+import request from '@/helpers/request'
+import { verifyNumberIntegerAndFloat } from '@/helpers/toolsValidate'
+import {
+  Button,
+  Col,
+  Field,
+  Form,
+  Icon,
+  Image,
+  Radio,
+  RadioGroup,
+  Row,
+  Sticky,
+  Tab,
+  Tabs
+} from 'vant'
+import { defineComponent } from 'vue'
+import SubjectModel from '../../business-components/subject-list'
+import styles from './class-info.module.less'
 import { createState } from './createState'
 
+import activeButtonIcon from '@common/images/icon_checkbox.png'
+import inactiveButtonIcon from '@common/images/icon_checkbox_default.png'
+
 export default defineComponent({
-  name: "ClassInfo",
+  name: 'ClassInfo',
   data() {
     return {
-      subjectStatus: false,
+      subjectStatus: false
     }
   },
   computed: {
-    choiceSubjectIds() { // 选择的科目编号
-      let ids = createState.lessonGroup.lessonSubject ? Number(createState.lessonGroup.lessonSubject) : null;
-      return ids ? [ids] : [];
+    choiceSubjectIds() {
+      // 选择的科目编号
+      let ids = createState.lessonGroup.lessonSubject
+        ? Number(createState.lessonGroup.lessonSubject)
+        : null
+      return ids ? [ids] : []
     },
-    subjectList() { // 学科列表
+    subjectList() {
+      // 学科列表
       return createState.subjectList || []
     },
-    lessonSubjectName() { // 选择的科目
+    lessonSubjectName() {
+      // 选择的科目
       let tempStr = ''
       this.subjectList.forEach((parent: any) => {
-        parent.subjects && parent.subjects.forEach((sub: any) => {
-          if (this.choiceSubjectIds.includes(sub.id)) {
-            tempStr = sub.name
-          }
-        })
+        parent.subjects &&
+          parent.subjects.forEach((sub: any) => {
+            if (this.choiceSubjectIds.includes(sub.id)) {
+              tempStr = sub.name
+            }
+          })
       })
       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 {
       if (createState.subjectList.length <= 0) {
         const res = await request.get('/api-teacher/subject/subjectSelect')
-        createState.subjectList = res.data || [];
+        createState.subjectList = res.data || []
       }
     } catch {
       //
@@ -69,8 +92,14 @@ export default defineComponent({
     }
   },
   render() {
-    return (
-      createState.loadingStatus ? <div></div> : <Form class={styles.classInfo} onSubmit={() => createState.active = 2} scrollToError>
+    return createState.loadingStatus ? (
+      <div></div>
+    ) : (
+      <Form
+        class={styles.classInfo}
+        onSubmit={() => (createState.active = 2)}
+        scrollToError
+      >
         <ColFieldGroup>
           <ColField title="课程名称" required>
             <Field
@@ -87,7 +116,9 @@ export default defineComponent({
               name="lessonSubjectName"
               readonly
               isLink
-              onClick={() => { this.subjectStatus = true }}
+              onClick={() => {
+                this.subjectStatus = true
+              }}
               rules={[{ required: true, message: '请选择课程声部' }]}
               placeholder="请选择课程声部"
             />
@@ -103,7 +134,7 @@ export default defineComponent({
               rows="3"
               maxlength={200}
               autosize
-              rules={[{ required: true, message: '请选择课程声部' }]}
+              rules={[{ required: true, message: '请输入课程介绍' }]}
               type="textarea"
             />
           </ColField>
@@ -120,7 +151,7 @@ export default defineComponent({
               maxlength={8}
               rules={[{ required: true, message: '请输入您的课程组售价' }]}
               v-slots={{
-                button: () => (<span>元</span>)
+                button: () => <span>元</span>
               }}
             />
           </ColField>
@@ -128,77 +159,124 @@ export default defineComponent({
 
         <div class={styles['class-info-tip']}>
           <p>扣除手续费后您的课程预计收入为:</p>
-          <p>课程组总收入<span>{this.calcRatePrice}</span>元/人</p>
+          <p>
+            课程组总收入<span>{this.calcRatePrice}</span>元/人
+          </p>
           <p>您的课程收入将在课程结束后结算到您的账户中</p>
         </div>
 
         <ColFieldGroup>
-          <ColField required border={false}
+          <ColField
+            required
+            border={false}
             v-slots={{
               title: () => (
-                <Tabs v-model:active={createState.tabIndex} class={styles.infoField} onChange={this.tabChange} shrink color="var(--van-primary)" lineWidth={20}>
+                <Tabs
+                  v-model:active={createState.tabIndex}
+                  class={styles.infoField}
+                  onChange={this.tabChange}
+                  shrink
+                  color="var(--van-primary)"
+                  lineWidth={20}
+                >
                   <Tab title="图片模板" name={1}></Tab>
                   <Tab title="自定义模板" name={2}></Tab>
                 </Tabs>
               )
-            }}>
+            }}
+          >
             <p class={styles.photoTip}>模板图片将作为改课程封面为学员展示</p>
-            {createState.tabIndex === 1 ? <Field
-              name='lessonCoverTemplateUrl'
-              rules={[{ required: true, message: '请选择课程声部' }]}
-              v-slots={{
-                input: () =>
-                (
-                  <RadioGroup v-model={createState.lessonGroup.lessonCoverTemplateUrl}>
+            {createState.tabIndex === 1 ? (
+              <Field
+                name="lessonCoverTemplateUrl"
+                rules={[{ required: true, message: '请选择课程声部' }]}
+                v-slots={{
+                  input: () => (
+                    <RadioGroup
+                      v-model={createState.lessonGroup.lessonCoverTemplateUrl}
+                    >
+                      <Row justify="space-between" style={{ width: '100%' }}>
+                        {createState.templateList.map((item: any) => (
+                          <Col
+                            span={12}
+                            class={styles.imgContainer}
+                            onClick={() => this.selectImg(item)}
+                          >
+                            <Image class={styles.imgContainer} src={item} />
+                            <Radio
+                              name={item}
+                              v-slots={{
+                                icon: (props: any) => (
+                                  <Icon
+                                    class={styles.boxStyle}
+                                    name={
+                                      props.checked
+                                        ? activeButtonIcon
+                                        : inactiveButtonIcon
+                                    }
+                                    size="18"
+                                  />
+                                )
+                              }}
+                            />
+                          </Col>
+                        ))}
+                      </Row>
+                    </RadioGroup>
+                  )
+                }}
+              />
+            ) : null}
+            {createState.tabIndex == 2 ? (
+              <Field
+                name="lessonCoverUrl"
+                rules={[{ required: true, message: '请选择课程声部' }]}
+                v-slots={{
+                  input: () => (
                     <Row justify="space-between" style={{ width: '100%' }}>
-                      {createState.templateList.map((item: any) => (
-                        <Col span={12} class={styles.imgContainer} onClick={() => this.selectImg(item)}>
-                          <Image class={styles.imgContainer} src={item} />
-                          <Radio name={item} />
-                        </Col>
-                      ))}
+                      <Col span={12} class={styles.imgContainer}>
+                        <ColUpload
+                          cropper
+                          options={{
+                            fixedNumber: [3, 2],
+                            autoCropWidth: 750,
+                            autoCropHeight: 500
+                          }}
+                          onUploadChange={(val: any) => {
+                            if (val) {
+                              createState.lessonGroup.lessonCoverTemplateUrl =
+                                ''
+                            }
+                          }}
+                          v-model={createState.lessonGroup.lessonCoverUrl}
+                          class={styles.imgContainer}
+                        />
+                      </Col>
                     </Row>
-                  </RadioGroup>
-                )
-              }}
-            /> : null}
-            {createState.tabIndex == 2 ? <Field
-              name='lessonCoverUrl'
-              rules={[{ required: true, message: '请选择课程声部' }]}
-              v-slots={{
-                input: () =>
-                (
-                  <Row justify="space-between" style={{ width: '100%' }}>
-                    <Col span={12} class={styles.imgContainer}>
-                      <ColUpload cropper options={{
-                        fixedNumber: [3, 2],
-                        autoCropWidth: 750,
-                        autoCropHeight: 500,
-                      }}
-                        onUploadChange={(val: any) => {
-                          if (val) {
-                            createState.lessonGroup.lessonCoverTemplateUrl = ''
-                          }
-                        }}
-                        v-model={createState.lessonGroup.lessonCoverUrl} class={styles.imgContainer} />
-                    </Col>
-                  </Row>
-                )
-              }}
-            /> : null}
+                  )
+                }}
+              />
+            ) : null}
           </ColField>
         </ColFieldGroup>
 
         <Sticky offsetBottom={0} position="bottom">
           <div class={[styles.btnGroup]}>
-            <Button block round type="primary" native-type="submit">下一步</Button>
+            <Button block round type="primary" native-type="submit">
+              下一步
+            </Button>
           </div>
         </Sticky>
 
         <ColPopup v-model={this.subjectStatus}>
-          <SubjectModel selectType="Radio" subjectList={createState.subjectList} choiceSubjectIds={this.choiceSubjectIds} onChoice={this.onChoice} />
+          <SubjectModel
+            selectType="Radio"
+            subjectList={createState.subjectList}
+            choiceSubjectIds={this.choiceSubjectIds}
+            onChoice={this.onChoice}
+          />
         </ColPopup>
-      </Form >
+      </Form>
     )
   }
-})
+})

+ 115 - 59
src/teacher/video-class/create.tsx

@@ -1,36 +1,50 @@
-import { Grid, GridItem, Icon } from "vant";
-import { defineComponent } from "vue";
-import styles from './create.module.less';
-import ClassInfo from "./class-info";
-import ClassContent from "./class-content";
-import { createState } from "./createState";
-import request from "@/helpers/request";
+import { Grid, GridItem, Icon } from 'vant'
+import { defineComponent } from 'vue'
+import styles from './create.module.less'
+import ClassInfo from './class-info'
+import ClassContent from './class-content'
+import { createState } from './createState'
+import request from '@/helpers/request'
 
 import nameActive from './images/icon_name_active.png'
 import education from './images/icon_education.png'
 import educationActive from './images/icon_education_active.png'
-import CreateSubmit from "./create-submit";
+import CreateSubmit from './create-submit'
 
 export default defineComponent({
   name: 'Create',
   async created() {
     const query = this.$route.query
-    createState.groupId = Number(query.groupId) || 0;
+    createState.groupId = Number(query.groupId) || 0
     // 判断是否是编辑
     if (!createState.groupId) {
       return false
     }
     try {
       createState.loadingStatus = true
-      const res = await request.get('/api-teacher/videoLessonGroup/selectVideoLesson', {
-        params: {
-          groupId: createState.groupId
+      const res = await request.get(
+        '/api-teacher/videoLessonGroup/selectVideoLesson',
+        {
+          params: {
+            groupId: createState.groupId
+          }
         }
-      })
+      )
       const result = res.data
-      const { auditStatus, lessonCoverUrl, lessonPrice, lessonDesc, lessonSubject, lessonName, id, ...group } = result.lessonGroup
+      const {
+        auditStatus,
+        lessonCoverUrl,
+        lessonPrice,
+        lessonDesc,
+        lessonSubject,
+        lessonName,
+        id,
+        ...group
+      } = result.lessonGroup
       // 判断模板图片是否在模板列表中,如果不在则是用户自己上传的图片
-      let statusUrl = createState.templateList.includes(lessonCoverUrl) ? true : false
+      let statusUrl = createState.templateList.includes(lessonCoverUrl)
+        ? true
+        : false
       createState.lessonGroup = {
         id: id,
         lessonName: lessonName,
@@ -41,18 +55,22 @@ export default defineComponent({
         lessonCoverUrl: statusUrl ? '' : lessonCoverUrl
       }
       createState.lessonList = []
-      result.detailList && result.detailList.forEach((item: any) => {
-        createState.lessonList.push({
-          videoTitle: item.videoTitle,
-          videoContent: item.videoContent,
-          videoUrl: item.videoUrl,
-          coverUrl: item.coverUrl,
-          posterUrl: item.posterUrl, // 视屏封面图
+      result.detailList &&
+        result.detailList.forEach((item: any) => {
+          createState.lessonList.push({
+            videoTitle: item.videoTitle,
+            videoContent: item.videoContent,
+            videoUrl: item.videoUrl,
+            coverUrl: item.coverUrl,
+            posterUrl: item.posterUrl // 视频封面图
+          })
         })
-      });
       createState.loadingStatus = false
-    } catch { }
-    if (createState.lessonGroup.lessonCoverUrl && !createState.templateList.includes(createState.lessonGroup.lessonCoverUrl)) {
+    } catch {}
+    if (
+      createState.lessonGroup.lessonCoverUrl &&
+      !createState.templateList.includes(createState.lessonGroup.lessonCoverUrl)
+    ) {
       createState.tabIndex = 2
     } else {
       createState.tabIndex = 1
@@ -60,49 +78,87 @@ export default defineComponent({
   },
   async mounted() {
     try {
-      const sysConfig = await request.get('/api-teacher/sysConfig/queryByParamName', {
-        params: {
-          paramName: 'video_lesson_service_fee'
+      const sysConfig = await request.get(
+        '/api-teacher/sysConfig/queryByParamName',
+        {
+          params: {
+            paramName: 'video_lesson_service_fee'
+          }
         }
-      })
+      )
       createState.rate = sysConfig.data.paramValue
-    } catch { }
+    } catch {}
   },
   render() {
     return (
       <div class={styles['video-create']}>
-        {createState.active <= 2 ? <Grid border={false} style={{ paddingTop: '15px' }} direction="horizontal" columnNum="2">
-          <GridItem v-slots={{
-            default: () => (
-              <>
-                <Icon name={nameActive} size={38} />
-                <span class={[styles.gridName, createState.active >= 1 ? styles.active : null]}>课程信息</span>
-              </>
-            )
-          }} />
-          <GridItem v-slots={{
-            default: () => (
-              <>
-                <Icon name={createState.active === 2 ? educationActive : education} size={38} />
-                <span class={[styles.gridName, createState.active === 2 ? styles.active : null]}>课程内容</span>
-              </>
-            )
-          }} />
-        </Grid> : null}
+        {createState.active <= 2 ? (
+          <Grid
+            border={false}
+            style={{ paddingTop: '15px' }}
+            direction="horizontal"
+            columnNum="2"
+          >
+            <GridItem
+              v-slots={{
+                default: () => (
+                  <>
+                    <Icon name={nameActive} size={38} />
+                    <span
+                      class={[
+                        styles.gridName,
+                        createState.active >= 1 ? styles.active : null
+                      ]}
+                    >
+                      课程信息
+                    </span>
+                  </>
+                )
+              }}
+            />
+            <GridItem
+              v-slots={{
+                default: () => (
+                  <>
+                    <Icon
+                      name={
+                        createState.active === 2 ? educationActive : education
+                      }
+                      size={38}
+                    />
+                    <span
+                      class={[
+                        styles.gridName,
+                        createState.active === 2 ? styles.active : null
+                      ]}
+                    >
+                      课程内容
+                    </span>
+                  </>
+                )
+              }}
+            />
+          </Grid>
+        ) : null}
         {/* 课程信息 */}
-        {createState.active === 1 ? <>
-          <ClassInfo />
-        </> : null}
+        {createState.active === 1 ? (
+          <>
+            <ClassInfo />
+          </>
+        ) : null}
         {/* 课程内容 */}
-        {createState.active === 2 ? <>
-          <ClassContent />
-        </> : null}
+        {createState.active === 2 ? (
+          <>
+            <ClassContent />
+          </>
+        ) : null}
         {/* 预览 */}
-        {createState.active === 3 ? <>
-          <CreateSubmit />
-        </> : null}
-
+        {createState.active === 3 ? (
+          <>
+            <CreateSubmit />
+          </>
+        ) : null}
       </div>
     )
   }
-})
+})

+ 16 - 12
src/teacher/video-class/createState.tsx

@@ -1,4 +1,4 @@
-import { reactive } from "vue";
+import { reactive } from 'vue'
 
 export const createState = reactive({
   groupId: 0,
@@ -7,9 +7,11 @@ export const createState = reactive({
   loadingStatus: false,
   rate: 0, // 手续费
   subjectList: [], // 声部列表
-  templateList: ['https://daya.ks3-cn-beijing.ksyun.com/202110/Sn2OAjr.png',
+  templateList: [
+    'https://daya.ks3-cn-beijing.ksyun.com/202110/Sn2OAjr.png',
     'https://daya.ks3-cn-beijing.ksyun.com/202108/ShHJ1Bb.png',
-    'https://daya.ks3-cn-beijing.ksyun.com/202110/Sn76BUQ.png'], // 模板列表
+    'https://daya.ks3-cn-beijing.ksyun.com/202110/Sn76BUQ.png'
+  ], // 模板列表
   lessonGroup: {
     id: null,
     lessonName: '',
@@ -17,15 +19,17 @@ export const createState = reactive({
     lessonDesc: '',
     lessonPrice: null as any,
     lessonCoverUrl: '',
-    lessonCoverTemplateUrl: '',
+    lessonCoverTemplateUrl: ''
   },
-  lessonList: [{
-    videoTitle: '',
-    videoContent: '',
-    videoUrl: '',
-    coverUrl: '',
-    posterUrl: '', // 视屏封面图
-  }]
+  lessonList: [
+    {
+      videoTitle: '',
+      videoContent: '',
+      videoUrl: '',
+      coverUrl: '',
+      posterUrl: '' // 视频封面图
+    }
+  ]
 })
 // videoUrl: 'https://daya.ks3-cn-beijing.ksyun.com/202204/T1kHuSh.mp4',
-// coverUrl: 'https://daya.ks3-cn-beijing.ksyun.com/202204/T1kK2ao.png'
+// coverUrl: 'https://daya.ks3-cn-beijing.ksyun.com/202204/T1kK2ao.png'

+ 1 - 1
src/views/order-detail/video/index.tsx

@@ -63,7 +63,7 @@ export default defineComponent({
           />
         </CellGroup>
       </div>
-      // 视
+      // 视
     )
   }
 })

+ 274 - 139
src/views/protocol/privacy.tsx

@@ -1,76 +1,138 @@
-import { defineComponent } from "vue";
-import styles from './index.module.less';
+import { defineComponent } from 'vue'
+import styles from './index.module.less'
 
 export default defineComponent({
   name: 'register',
   render() {
     return (
       <div class={styles.container}>
-        <strong>发布日期:2019-11-22</strong><br />
-        <strong>最新更新日期:2021-05-24</strong><br />
-
-        管乐迷(下称“管乐迷”或“本公司”)尤其重视用户(下称“用户”或“您”)的隐私和个人信息保护。《隐私保护政策》(下称“本政策”)旨在向用户说明本公司如何收集、使用、处理、存储用户个人信息以及为用户提供访问、删除、更正等处理事宜。<br />
-
-        <strong class={styles.line}>在使用本公司提供的软件及相关服务前,请您务必仔细阅读并充分理解本政策,尤其是以加粗、下划线等标识的条款。当您使用本公司提供的软件及相关服务时,即表示您已经同意本公司按照本政策来收集、使用、共享和保护您的相关个人信息;用户不同意本政策的任何条款或内容的,请立即停止使用本公司提供的软件及相关服务。</strong><br />
-
-        <strong class={styles.line}>【针对未成年用户及其监护人的特别提示】</strong><br />
-        <strong class={styles.line}>如果您是未满18周岁的未成年人,应在您的监护人的陪同下阅读本政策,并在监护人的解释和指导下理解本政策,在使用本公司提供的软件及服务或者提交信息之前获得您的监护人的指导并获得同意。未成年用户或者未成年用户的监护人点击同意本政策,或者未成年用户使用本公司提供的软件及服务,或者提交个人信息的,即表示未成年用户已经获得其监护人的同意,即监护人同意本公司按照本政策收集、使用、分享、转让、披露和存储未成年用户的个人信息。如果未成年用户或其监护人不同意本政策的任何条款或内容的,请立即停止使用本公司提供的软件及相关服务。</strong><br />
-        <strong class={styles.line}>如果您是未满14周岁的儿童用户,请务必通知您的监护人并共同阅读本公司的《儿童个人信息保护规则》,并务必在使用本公司提供的软件及服务或者提交信息之前获得监护人的指导并获得同意。</strong><br />
-
-        本公司收集、使用、共享和保护用户的个人信息,是在遵守国家法律法规规定的前提下,仅出于本政策所述的以下目的,遵循正当必要、知情同意、目的明确、安全保障、依法利用的原则收集、存储、使用、转移、披露儿童个人信息。<br />
-        如果您对本政策有任何疑问的,请通过本政策中公布的联系方式与本公司联系。<br />
-
+        <strong>发布日期:2019-11-22</strong>
+        <br />
+        <strong>最新更新日期:2021-05-24</strong>
+        <br />
+        管乐迷(下称“管乐迷”或“本公司”)尤其重视用户(下称“用户”或“您”)的隐私和个人信息保护。《隐私保护政策》(下称“本政策”)旨在向用户说明本公司如何收集、使用、处理、存储用户个人信息以及为用户提供访问、删除、更正等处理事宜。
+        <br />
+        <strong class={styles.line}>
+          在使用本公司提供的软件及相关服务前,请您务必仔细阅读并充分理解本政策,尤其是以加粗、下划线等标识的条款。当您使用本公司提供的软件及相关服务时,即表示您已经同意本公司按照本政策来收集、使用、共享和保护您的相关个人信息;用户不同意本政策的任何条款或内容的,请立即停止使用本公司提供的软件及相关服务。
+        </strong>
+        <br />
+        <strong class={styles.line}>
+          【针对未成年用户及其监护人的特别提示】
+        </strong>
+        <br />
+        <strong class={styles.line}>
+          如果您是未满18周岁的未成年人,应在您的监护人的陪同下阅读本政策,并在监护人的解释和指导下理解本政策,在使用本公司提供的软件及服务或者提交信息之前获得您的监护人的指导并获得同意。未成年用户或者未成年用户的监护人点击同意本政策,或者未成年用户使用本公司提供的软件及服务,或者提交个人信息的,即表示未成年用户已经获得其监护人的同意,即监护人同意本公司按照本政策收集、使用、分享、转让、披露和存储未成年用户的个人信息。如果未成年用户或其监护人不同意本政策的任何条款或内容的,请立即停止使用本公司提供的软件及相关服务。
+        </strong>
+        <br />
+        <strong class={styles.line}>
+          如果您是未满14周岁的儿童用户,请务必通知您的监护人并共同阅读本公司的《儿童个人信息保护规则》,并务必在使用本公司提供的软件及服务或者提交信息之前获得监护人的指导并获得同意。
+        </strong>
+        <br />
+        本公司收集、使用、共享和保护用户的个人信息,是在遵守国家法律法规规定的前提下,仅出于本政策所述的以下目的,遵循正当必要、知情同意、目的明确、安全保障、依法利用的原则收集、存储、使用、转移、披露儿童个人信息。
+        <br />
+        如果您对本政策有任何疑问的,请通过本政策中公布的联系方式与本公司联系。
+        <br />
         <h2>一、本公司收集和使用儿童用户个人信息</h2>
-        个人信息是指以电子或者其他方式记录的能够单独或者与其他信息结合识别特定自然人身份或者反映特定自然人活动情况的各种信息。<br />
-        1. 用户账户信息:<br />
-        用户使用本公司提供的软件及相关服务前,需要在本公司提供的软件注册账户,此时我们需要您主动填写和提供注册信息,包括姓名、性别、出生日期、所在学校、所在班级、手机号码、家庭住址、创建的用户名(ID)等信息,这些信息是本公司为用户提供考勤、教学、排课、陪练、打分等服务所必要的,如果您不提供给本公司,您可能无法使用本公司提供的软件或相关服务。<br />
-
-        2. 用户使用本公司提供的软件及相关服务信息:<br />
-        用户通过电脑、平板电脑或手机等智能设备访问本公司提供的软件及其相关服务时,服务器会自动记录用户的登录IP地址、网络服务商、接入网络的方式、类型及状态、网络质量数据、使用的语言、设备类别、设备型号、设备识别码、操作系统、分辨率、访问日期及时间长度等信息,以及用户登录和使用本公司提供的软件及相关服务时,用户使用的设备所在的地理位置信息。<br />
-        用户使用本公司提供的软件及相关服务参加直播教学、录播教学等课程时,我们会收集用户提交的信息以及系统自动记录和存储的信息,包括但不限于视频、录像、音频、照片、截图以及这些信息产生时对应的日期、时间、地点等。<br />
-
-        3. 交易信息和付款信息:<br />
-        用户使用本公司提供的软件及相关服务的过程中,购买课程或其他服务需要向本公司进行支付,<strong class={styles.line}>本公司建议由未成年用户的监护人完成支付交易的整个过程,如果支付交易是由未成年用户进行,则应当在向本公司提供支付信息之前,获得其监护人的同意、授权和指导。</strong>本公司会收集支付交易时使用的支付工具、支付账户、银行卡号、开户银行、支付状态信息,支付日期等信息。<strong class={styles.line}>如果您进行支付交易时直接跳转至第三方支付页面的,该第三方将会直接收集您的支付信息,此时您的支付信息的收集、使用、存储等规则将遵循该第三方的隐私政策,请您注意事先仔细查阅。</strong>如果您符合本公司相关服务协议里约定的退费情形,并要求退费时,本公司需要您提供姓名、银行卡号、开户银行等信息。<br />
-
-        4. 用户因请假、打卡签到或其他原因通过电话、电子邮件或其他线上或线下渠道与本公司取得联系时,本公司会收集和处理该用户向本公司提供或者反馈的信息,用户的姓名、联系方式,以及本公司为了处理该用户的请求而必须向其确认的事项,并可能会保留与用户的通话、通信记录,以便本公司及时与该用户联系和沟通并处理。<br />
-
-        5. 本公司还可能为了改进本公司服务,提高教学和服务质量等需要,用户联系和沟通,并在此过程中收集沟通时提供的相关信息。<br />
-
-        6. 本公司在教学和服务过程中必要时向用户发出与教学或服务有关的通知或推送。<br />
-
-        7. 检测和防止欺诈和IT安全威胁所需的信息:本公司收集检测,调查和预防欺诈,作弊、IT安全威胁、网络漏洞、黑客攻击、计算机病毒和其他违反用户协议和适用法律的行为所需的某些信息。此数据仅用于检测,调查,预防以及在适用的情况下对此类行为进行处理,并且仅在此目的所需的最短时间内存储。如果披露信息会影响本公司检测、调查、防止此类行为的机制,则可能无法向您披露为此目的而存储的特定信息。<br />
-
-        8. 如本公司要向用户的个人信息用于本政策未载明的其他用途时,将事先获得用户的同意;如本公司将基于特定目的而收集的用户个人信息用于其他目的时,将事先获得用户的同意。<br />
-
-        9. 为了保障您的账号安全、交易安全以及系统运行安全,满足法律法规和我们协议规则的相关要求,在您使用我们的产品/服务过程中,经您授权我们会获取您的设备信息,包括您使用的设备属性、连接和状态信息,例如设备型号、设备标识符(如IMEI/androidID/IDFA/OPENUDID/GUID/OAID、SIM卡IMSI、ICCID信息等)、设备MAC地址、软件列表、电信运营商等软硬件特征信息。<br />
-
-        10. 我们可能间接收集的个人信息<br />
-        您可以选择授权我们收集和使用个人信息的场景,为向您提供个性化的服务,您可以选择使用我们提供的拓展功能,我们会在符合法律规定并根据您具体授权的情况下收集并使用如下信息。这类信息将在您选择的具体功能和业务场景中进行收集,如果您不提供这些信息,不会影响您使用管乐迷的基本功能。<br />
-        10.1 您需要授权我们收集和使用个人信息的场景<br />
-        10.1.1 基于相机授权的拓展功能<br />
-        您可以选择开启系统的相机权限,通过使用拍照、录视频等功能授权管乐迷访问您的相机,以便于您通过拍摄照片或录制视频等方式发布内容,如果您需要录制并发布有声视频时,您还需开启麦克风权限。我们会收集您上传发布的上述信息,此项功能您可以在系统权限中关闭,一旦关闭您将可能无法通过拍摄图片、视频等方式进行更换头像、上传作业等,但不会影响您享受管乐迷的基本功能。<br />
-        10.1.2 基于相册授权的拓展功能<br />
-        您可以选择开启系统的相册权限,通过主动上传图片、视频等方式授权我们访问您的相册,以便于您通过上传照片或上传视频等方式发布内容。我们会收集您选择上传发布的上述信息,此项功能您可以在系统权限中关闭,一旦关闭您将可能无法通过上传图片、视频等方式进行更换头像、上传作业等,但不会影响您享受管乐迷的基本功能。<br />
-        10.1.3 基于麦克风授权的拓展功能<br />
-        您可以选择开启系统的麦克风权限,使用语音技术来实现录制音频作业等语音功能。我们会收集您在使用录音功能中录入的语音信息用于老师查看音频作业。此项功能您可以在系统权限中关闭,一旦关闭您将可能无法使用音频作业功能,但不会影响您享受管乐迷的基本功能。<br />
-        10.2 其他<br />
-        如果我们将信息用于本政策未载明的其他用途,或者将基于特定目的收集而来的信息用于其他目的时,会单独征求您的授权同意。<br />
-        10.3 征得同意的例外<br />
-        请您知悉,以下情形中,我们收集、使用个人信息无需征得您的授权同意:<br />
-        10.3.1 与国家安全、国防安全有关的;<br />
-        10.3.2 与公共安全、公共卫生、重大公共利益有关的;<br />
-        10.3.3 与犯罪侦查、起诉、审判和判决执行等有关的;<br />
-        10.3.4 出于维护个人信息主体或其他个人的生命、财产等重大合法权益但又很难得到本人同意的;<br />
-        10.3.5 所收集的个人信息是个人信息主体自行向社会公众公开的;<br />
-        10.3.6 从合法公开披露的信息中收集的您的个人信息的,如合法的新闻报道、政府信息公开等渠道;但是您明确拒绝或者处理该信息侵害您重大利益的除外。<br />
-        10.3.7 根据您的要求签订合同所必需的;<br />
-        10.3.8 用于维护所提供的产品与/或服务的安全稳定运行所必需的,例如发现、处置产品与/或服务的故障;<br />
-        10.3.9 为合法的新闻报道所必需的;<br />
-        10.3.10 学术研究机构基于公共利益开展统计或学术研究所必要,且对外提供学术研究或描述的结果时,对结果中所包含的个人信息进行去标识化处理的;<br />
-        10.3.11 法律法规规定的其他情形。<br />
-        请注意,单独或与其他信息相结合无法识别您的身份或者与您直接建立联系的信息,不属于个人信息。如果我们将单独无法与任何特定个人建立联系的信息与其他信息结合用于识别自然人个人身份,或者将其与个人信息结合使用,则在结合使用期间,此类信息将被视为个人信息。<br />
-        10.4 设备权限调用<br />
-        为确保相关业务功能的正常实现,我们需要根据具体的使用场景调用对应的必要权限,并在调用前向您弹窗询问,具体的权限调用说明请见下表:<br />
+        个人信息是指以电子或者其他方式记录的能够单独或者与其他信息结合识别特定自然人身份或者反映特定自然人活动情况的各种信息。
+        <br />
+        1. 用户账户信息:
+        <br />
+        用户使用本公司提供的软件及相关服务前,需要在本公司提供的软件注册账户,此时我们需要您主动填写和提供注册信息,包括姓名、性别、出生日期、所在学校、所在班级、手机号码、家庭住址、创建的用户名(ID)等信息,这些信息是本公司为用户提供考勤、教学、排课、陪练、打分等服务所必要的,如果您不提供给本公司,您可能无法使用本公司提供的软件或相关服务。
+        <br />
+        2. 用户使用本公司提供的软件及相关服务信息:
+        <br />
+        用户通过电脑、平板电脑或手机等智能设备访问本公司提供的软件及其相关服务时,服务器会自动记录用户的登录IP地址、网络服务商、接入网络的方式、类型及状态、网络质量数据、使用的语言、设备类别、设备型号、设备识别码、操作系统、分辨率、访问日期及时间长度等信息,以及用户登录和使用本公司提供的软件及相关服务时,用户使用的设备所在的地理位置信息。
+        <br />
+        用户使用本公司提供的软件及相关服务参加直播教学、录播教学等课程时,我们会收集用户提交的信息以及系统自动记录和存储的信息,包括但不限于视频、录像、音频、照片、截图以及这些信息产生时对应的日期、时间、地点等。
+        <br />
+        3. 交易信息和付款信息:
+        <br />
+        用户使用本公司提供的软件及相关服务的过程中,购买课程或其他服务需要向本公司进行支付,
+        <strong class={styles.line}>
+          本公司建议由未成年用户的监护人完成支付交易的整个过程,如果支付交易是由未成年用户进行,则应当在向本公司提供支付信息之前,获得其监护人的同意、授权和指导。
+        </strong>
+        本公司会收集支付交易时使用的支付工具、支付账户、银行卡号、开户银行、支付状态信息,支付日期等信息。
+        <strong class={styles.line}>
+          如果您进行支付交易时直接跳转至第三方支付页面的,该第三方将会直接收集您的支付信息,此时您的支付信息的收集、使用、存储等规则将遵循该第三方的隐私政策,请您注意事先仔细查阅。
+        </strong>
+        如果您符合本公司相关服务协议里约定的退费情形,并要求退费时,本公司需要您提供姓名、银行卡号、开户银行等信息。
+        <br />
+        4.
+        用户因请假、打卡签到或其他原因通过电话、电子邮件或其他线上或线下渠道与本公司取得联系时,本公司会收集和处理该用户向本公司提供或者反馈的信息,用户的姓名、联系方式,以及本公司为了处理该用户的请求而必须向其确认的事项,并可能会保留与用户的通话、通信记录,以便本公司及时与该用户联系和沟通并处理。
+        <br />
+        5.
+        本公司还可能为了改进本公司服务,提高教学和服务质量等需要,用户联系和沟通,并在此过程中收集沟通时提供的相关信息。
+        <br />
+        6.
+        本公司在教学和服务过程中必要时向用户发出与教学或服务有关的通知或推送。
+        <br />
+        7.
+        检测和防止欺诈和IT安全威胁所需的信息:本公司收集检测,调查和预防欺诈,作弊、IT安全威胁、网络漏洞、黑客攻击、计算机病毒和其他违反用户协议和适用法律的行为所需的某些信息。此数据仅用于检测,调查,预防以及在适用的情况下对此类行为进行处理,并且仅在此目的所需的最短时间内存储。如果披露信息会影响本公司检测、调查、防止此类行为的机制,则可能无法向您披露为此目的而存储的特定信息。
+        <br />
+        8.
+        如本公司要向用户的个人信息用于本政策未载明的其他用途时,将事先获得用户的同意;如本公司将基于特定目的而收集的用户个人信息用于其他目的时,将事先获得用户的同意。
+        <br />
+        9.
+        为了保障您的账号安全、交易安全以及系统运行安全,满足法律法规和我们协议规则的相关要求,在您使用我们的产品/服务过程中,经您授权我们会获取您的设备信息,包括您使用的设备属性、连接和状态信息,例如设备型号、设备标识符(如IMEI/androidID/IDFA/OPENUDID/GUID/OAID、SIM卡IMSI、ICCID信息等)、设备MAC地址、软件列表、电信运营商等软硬件特征信息。
+        <br />
+        10. 我们可能间接收集的个人信息
+        <br />
+        您可以选择授权我们收集和使用个人信息的场景,为向您提供个性化的服务,您可以选择使用我们提供的拓展功能,我们会在符合法律规定并根据您具体授权的情况下收集并使用如下信息。这类信息将在您选择的具体功能和业务场景中进行收集,如果您不提供这些信息,不会影响您使用管乐迷的基本功能。
+        <br />
+        10.1 您需要授权我们收集和使用个人信息的场景
+        <br />
+        10.1.1 基于相机授权的拓展功能
+        <br />
+        您可以选择开启系统的相机权限,通过使用拍照、录视频等功能授权管乐迷访问您的相机,以便于您通过拍摄照片或录制视频等方式发布内容,如果您需要录制并发布有声视频时,您还需开启麦克风权限。我们会收集您上传发布的上述信息,此项功能您可以在系统权限中关闭,一旦关闭您将可能无法通过拍摄图片、视频等方式进行更换头像、上传作业等,但不会影响您享受管乐迷的基本功能。
+        <br />
+        10.1.2 基于相册授权的拓展功能
+        <br />
+        您可以选择开启系统的相册权限,通过主动上传图片、视频等方式授权我们访问您的相册,以便于您通过上传照片或上传视频等方式发布内容。我们会收集您选择上传发布的上述信息,此项功能您可以在系统权限中关闭,一旦关闭您将可能无法通过上传图片、视频等方式进行更换头像、上传作业等,但不会影响您享受管乐迷的基本功能。
+        <br />
+        10.1.3 基于麦克风授权的拓展功能
+        <br />
+        您可以选择开启系统的麦克风权限,使用语音技术来实现录制音频作业等语音功能。我们会收集您在使用录音功能中录入的语音信息用于老师查看音频作业。此项功能您可以在系统权限中关闭,一旦关闭您将可能无法使用音频作业功能,但不会影响您享受管乐迷的基本功能。
+        <br />
+        10.2 其他
+        <br />
+        如果我们将信息用于本政策未载明的其他用途,或者将基于特定目的收集而来的信息用于其他目的时,会单独征求您的授权同意。
+        <br />
+        10.3 征得同意的例外
+        <br />
+        请您知悉,以下情形中,我们收集、使用个人信息无需征得您的授权同意:
+        <br />
+        10.3.1 与国家安全、国防安全有关的;
+        <br />
+        10.3.2 与公共安全、公共卫生、重大公共利益有关的;
+        <br />
+        10.3.3 与犯罪侦查、起诉、审判和判决执行等有关的;
+        <br />
+        10.3.4
+        出于维护个人信息主体或其他个人的生命、财产等重大合法权益但又很难得到本人同意的;
+        <br />
+        10.3.5 所收集的个人信息是个人信息主体自行向社会公众公开的;
+        <br />
+        10.3.6
+        从合法公开披露的信息中收集的您的个人信息的,如合法的新闻报道、政府信息公开等渠道;但是您明确拒绝或者处理该信息侵害您重大利益的除外。
+        <br />
+        10.3.7 根据您的要求签订合同所必需的;
+        <br />
+        10.3.8
+        用于维护所提供的产品与/或服务的安全稳定运行所必需的,例如发现、处置产品与/或服务的故障;
+        <br />
+        10.3.9 为合法的新闻报道所必需的;
+        <br />
+        10.3.10
+        学术研究机构基于公共利益开展统计或学术研究所必要,且对外提供学术研究或描述的结果时,对结果中所包含的个人信息进行去标识化处理的;
+        <br />
+        10.3.11 法律法规规定的其他情形。
+        <br />
+        请注意,单独或与其他信息相结合无法识别您的身份或者与您直接建立联系的信息,不属于个人信息。如果我们将单独无法与任何特定个人建立联系的信息与其他信息结合用于识别自然人个人身份,或者将其与个人信息结合使用,则在结合使用期间,此类信息将被视为个人信息。
+        <br />
+        10.4 设备权限调用
+        <br />
+        为确保相关业务功能的正常实现,我们需要根据具体的使用场景调用对应的必要权限,并在调用前向您弹窗询问,具体的权限调用说明请见下表:
+        <br />
         <table style={{ borderWidth: '1px' }}>
           <tr>
             <th>设备权限</th>
@@ -80,13 +142,13 @@ export default defineComponent({
           </tr>
           <tr>
             <td>相机</td>
-            <td>用于拍照、上传图片、录制视</td>
-            <td>用户主动拍照、上传图片、录制视时询问</td>
+            <td>用于拍照、上传图片、录制视</td>
+            <td>用户主动拍照、上传图片、录制视时询问</td>
             <td>是</td>
           </tr>
           <tr>
             <td>麦克风</td>
-            <td>用户语音消息、音频作业、视作业</td>
+            <td>用户语音消息、音频作业、视作业</td>
             <td>用户发起语音输入前询问</td>
             <td>是</td>
           </tr>
@@ -97,17 +159,25 @@ export default defineComponent({
             <td>是</td>
           </tr>
         </table>
-        您可以在设备的设置中选择关闭部分或者全部权限,这可能导致对应的业务功能无法实现或者无法达到预期效果。<br />
-
-        <strong class={styles.line}>本公司收集用户的个人信息后,可能会通过技术手段对已经收集的用户个人信息进行匿名化处理,使得用户个人信息主体无法被识别,且处理后的信息不能被复原,此时经过匿名化处理后的信息不再属于用户个人信息。本公司可能会将匿名化处理信息用于测试本公司的IT系统,研究,数据分析,改进服务,以及开发新服务、新产品和功能,以及向第三方分享、转让、披露,此时无需另行获得用户的授权同意。</strong><br />
-
+        您可以在设备的设置中选择关闭部分或者全部权限,这可能导致对应的业务功能无法实现或者无法达到预期效果。
+        <br />
+        <strong class={styles.line}>
+          本公司收集用户的个人信息后,可能会通过技术手段对已经收集的用户个人信息进行匿名化处理,使得用户个人信息主体无法被识别,且处理后的信息不能被复原,此时经过匿名化处理后的信息不再属于用户个人信息。本公司可能会将匿名化处理信息用于测试本公司的IT系统,研究,数据分析,改进服务,以及开发新服务、新产品和功能,以及向第三方分享、转让、披露,此时无需另行获得用户的授权同意。
+        </strong>
+        <br />
         <h2>二、本公司共享、转让、披露用户个人信息</h2>
-        1. 共享:<br />
-        1.1在获取用户的明确同意或授权的情况共享;<br />
-        1.2根据法律法规规定,或者按照政府主管部门的强制性要求;<br />
-        1.3为了向用户提供教学或服务,本公司可能会与关联方分享,但是本公司只会分享必要的个人信息,且此时受用户服务协议或本隐私政策中所声明的收集和使用之目的的约束,本公司的关联方如要改变个人信息的处理目的,将再次征求用户的授权同意;<br />
-        1.4本公司还可以与提供与本公司提供的软件及相关服务相关的必要功能和支持服务的第三方(例如,提供或处理支付业务、网络定位服务、推送通知、电子邮件服务、税务和会计服务、分析服务)共享用户的个人信息数据,此时本公司仅会处于合法、正当、必要、特定、明确的目的共享用户的个人信息,并且只会共享提供服务所必要的信息,且此时本公司会与这些第三方主体签订保密协议,要求这些第三方主体按照相关法律法规规定、本政策及其他任何相关的保密和安全措施保护用户的个人信息。您可以通过以下链接查看第三方的数据使用和保护规则。<br />
-        管乐迷接入第三方SDK目录<br />
+        1. 共享:
+        <br />
+        1.1在获取用户的明确同意或授权的情况共享;
+        <br />
+        1.2根据法律法规规定,或者按照政府主管部门的强制性要求;
+        <br />
+        1.3为了向用户提供教学或服务,本公司可能会与关联方分享,但是本公司只会分享必要的个人信息,且此时受用户服务协议或本隐私政策中所声明的收集和使用之目的的约束,本公司的关联方如要改变个人信息的处理目的,将再次征求用户的授权同意;
+        <br />
+        1.4本公司还可以与提供与本公司提供的软件及相关服务相关的必要功能和支持服务的第三方(例如,提供或处理支付业务、网络定位服务、推送通知、电子邮件服务、税务和会计服务、分析服务)共享用户的个人信息数据,此时本公司仅会处于合法、正当、必要、特定、明确的目的共享用户的个人信息,并且只会共享提供服务所必要的信息,且此时本公司会与这些第三方主体签订保密协议,要求这些第三方主体按照相关法律法规规定、本政策及其他任何相关的保密和安全措施保护用户的个人信息。您可以通过以下链接查看第三方的数据使用和保护规则。
+        <br />
+        管乐迷接入第三方SDK目录
+        <br />
         <table style={{ borderWidth: '1px' }}>
           <tr>
             <th style="width: .9rem;">使用目的</th>
@@ -119,97 +189,162 @@ export default defineComponent({
             <td rowspan="5">根据用户机型,为用户提供通知信息推送功能</td>
             <td>极光</td>
             <td>设备信息</td>
-            <td style="word-break: break-all;">https://www.jiguang.cn/license/privacy</td>
+            <td style="word-break: break-all;">
+              https://www.jiguang.cn/license/privacy
+            </td>
           </tr>
           <tr>
             <td>华为</td>
             <td>设备信息</td>
-            <td style="word-break: break-all;">https://developer.huawei.com/consumer/cn/service/hms/pushservice.html</td>
+            <td style="word-break: break-all;">
+              https://developer.huawei.com/consumer/cn/service/hms/pushservice.html
+            </td>
           </tr>
           <tr>
             <td>小米</td>
             <td>设备信息</td>
-            <td style="word-break: break-all;">https://dev.mi.com/console/doc/detail?pId=1822</td>
+            <td style="word-break: break-all;">
+              https://dev.mi.com/console/doc/detail?pId=1822
+            </td>
           </tr>
           <tr>
             <td>VIVO</td>
             <td>设备信息</td>
-            <td style="word-break: break-all;">https://www.umeng.com/page/policy</td>
+            <td style="word-break: break-all;">
+              https://www.umeng.com/page/policy
+            </td>
           </tr>
           <tr>
             <td>OPPO</td>
             <td>设备信息</td>
-            <td style="word-break: break-all;">https://open.oppomobile.com/wiki/doc#id=10194</td>
+            <td style="word-break: break-all;">
+              https://open.oppomobile.com/wiki/doc#id=10194
+            </td>
           </tr>
           <tr>
             <td>统计分析</td>
             <td>友盟SDK</td>
             <td>设备信息</td>
-            <td style="word-break: break-all;">https://open.oppomobile.com/wiki/doc#id=10194</td>
+            <td style="word-break: break-all;">
+              https://open.oppomobile.com/wiki/doc#id=10194
+            </td>
           </tr>
         </table>
-        2. 转让:<br />
-        2.1在获取用户的明确同意的情况下转让;<br />
-        2.2本公司发生合并、收购、资产转让或类似交易或者破产清算时,用户信息(包括用户的个人信息)可能会被转让给相关方,此时本公司会要求这些受让方继续遵守本政策的约束,如果这些受让方变更用户信息的使用目的时,应重新获得用户的同意和授权;<br />
-        本公司向第三方转让用户个人信息时,将自行或委托第三方机构进行安全评估。<br />
-        3. 披露:<br />
-        3.1在获取用户的要求或明确同意的情况下披露;<br />
-        3.2根据法律法规规定、法院或政府主管部门的强制性要求时;<br />
-        3.3本公司发生资本市场活动(例如IPO等)时,可能需要接受其他主体的尽职调查,此时本公司可能会根据尽职调查的需要将用户的个人信息提供给必要的主体,但本公司会与实施调查的主体签订保密协议,要求这些第三方主体按照相关法律法规规定、本政策及其他任何相关的保密和安全措施来保护用户的个人信息。<br />
-        4. 本公司在下列情形时,无需获得用户的授权同意,即可分享、转让、披露用户的信息:<br />
-        4.1与国家安全、国防安全相关的;<br />
-        4.2与公共安全、公共卫生、重大公共利益相关的;<br />
-        4.3与犯罪侦查、起诉、审判和判决执行等司法或行政执法相关的;<br />
-        4.4为了维护用户或其他个人的生命和财产等重大合法权利但又很难得到同意的情况下;<br />
-        4.5用户自行向社会公开的信息;<br />
-        4.6从合法公开渠道获得的信息,如新闻报道、政府信息公开等渠道。<br />
-
+        2. 转让:
+        <br />
+        2.1在获取用户的明确同意的情况下转让;
+        <br />
+        2.2本公司发生合并、收购、资产转让或类似交易或者破产清算时,用户信息(包括用户的个人信息)可能会被转让给相关方,此时本公司会要求这些受让方继续遵守本政策的约束,如果这些受让方变更用户信息的使用目的时,应重新获得用户的同意和授权;
+        <br />
+        本公司向第三方转让用户个人信息时,将自行或委托第三方机构进行安全评估。
+        <br />
+        3. 披露:
+        <br />
+        3.1在获取用户的要求或明确同意的情况下披露;
+        <br />
+        3.2根据法律法规规定、法院或政府主管部门的强制性要求时;
+        <br />
+        3.3本公司发生资本市场活动(例如IPO等)时,可能需要接受其他主体的尽职调查,此时本公司可能会根据尽职调查的需要将用户的个人信息提供给必要的主体,但本公司会与实施调查的主体签订保密协议,要求这些第三方主体按照相关法律法规规定、本政策及其他任何相关的保密和安全措施来保护用户的个人信息。
+        <br />
+        4.
+        本公司在下列情形时,无需获得用户的授权同意,即可分享、转让、披露用户的信息:
+        <br />
+        4.1与国家安全、国防安全相关的;
+        <br />
+        4.2与公共安全、公共卫生、重大公共利益相关的;
+        <br />
+        4.3与犯罪侦查、起诉、审判和判决执行等司法或行政执法相关的;
+        <br />
+        4.4为了维护用户或其他个人的生命和财产等重大合法权利但又很难得到同意的情况下;
+        <br />
+        4.5用户自行向社会公开的信息;
+        <br />
+        4.6从合法公开渠道获得的信息,如新闻报道、政府信息公开等渠道。
+        <br />
         <h2>三、用户个人信息的保护、存储、跨境传输</h2>
-        1. 本公司会努力采取各种符合业界标准的物理、电子和管理方面的安全措施来保护用户的个人信息,防止用户的个人信息遭到未经授权访问、公开披露、使用、修改、损坏或丢失。本公司会采取一切合理可行的措施,保护用户的个人信息。<br />
-        2. 本公司会采取加密等措施存储用户的个人信息,确保信息安全,并以最小授权为原则对本公司工作人员严格设置信息访问权限,控制用户个人信息知悉范围。工作人员需要访问用户个人信息的,应当经过本公司用户个人信息保护负责人或者其授权的管理人员审批,并且记录访问情况,采取技术措施,避免违法复制、下载用户个人信息。<br />
-        3. 本公司将会采取一切合理可行的措施,确保未收集无关的用户个人信息,并只会在达成本政策所述目的所需的期限内存储和保留用户的个人信息,不会超过本政策所述目的所必需的期限,除非需要延长保留期或受到法律的允许。<br />
-        4. 互联网并非绝对安全的环境,而且电子邮件、即时通讯、社交软件或其他服务软件等与其他用户交流的方式无法确定是否完全加密,因此本公司建议用户使用此类工具时注意保护信息安全,并使用复杂密码,协助本公司保证用户的账户安全。<br />
-        5. 互联网环境并非百分之白安全,本公司将尽力确保或担保用户发送给本公司的任何信息的安全性。如果本公司的物理、技术,或管理防护设施遭到破坏,导致信息被非授权访问、公开披露、篡改,或破坏,导致您的合法权益受损,本公司将承担相应的法律责任。<br />
-        6. 本公司发现用户个人信息发生或可能发生泄露、毁损、灭失的,本公司将将立即启动应急预案,采取补救措施;造成或者可能造成严重后果的,会立即向有关主管部门报告,并将事件相关情况以邮件、信函、电话、推送通知等方式告知受影响的用户,难以逐一告知的,将会采取合理、有效的方式发布相关警示信息。包括但不限于安全事件的基本情况和可能的影响、本公司已采取或将要采取的处置措施、用户可自主防范和降低风险的建议、对用户的补救措施等。同时,本公司还将按照监管部门要求,主动上报个人信息安全事件的处置情况。<br />
-        7. 本公司将用户的个人信息存储于中华人民共和国境内。如需跨境传输,本公司将依法征得用户的授权同意。<br />
-        8. 用户提供的上述信息,将在该用户使用本服务期间持续授权本公司使用。本公司停止运营软件或服务的,将会立即停止收集用户个人信息,并删除持有的用户个人信息,并将停止运营的通知及时通知用户;当该用户注销账号时,本公司将停止使用并删除上述信息。<br />
-
+        1.
+        本公司会努力采取各种符合业界标准的物理、电子和管理方面的安全措施来保护用户的个人信息,防止用户的个人信息遭到未经授权访问、公开披露、使用、修改、损坏或丢失。本公司会采取一切合理可行的措施,保护用户的个人信息。
+        <br />
+        2.
+        本公司会采取加密等措施存储用户的个人信息,确保信息安全,并以最小授权为原则对本公司工作人员严格设置信息访问权限,控制用户个人信息知悉范围。工作人员需要访问用户个人信息的,应当经过本公司用户个人信息保护负责人或者其授权的管理人员审批,并且记录访问情况,采取技术措施,避免违法复制、下载用户个人信息。
+        <br />
+        3.
+        本公司将会采取一切合理可行的措施,确保未收集无关的用户个人信息,并只会在达成本政策所述目的所需的期限内存储和保留用户的个人信息,不会超过本政策所述目的所必需的期限,除非需要延长保留期或受到法律的允许。
+        <br />
+        4.
+        互联网并非绝对安全的环境,而且电子邮件、即时通讯、社交软件或其他服务软件等与其他用户交流的方式无法确定是否完全加密,因此本公司建议用户使用此类工具时注意保护信息安全,并使用复杂密码,协助本公司保证用户的账户安全。
+        <br />
+        5.
+        互联网环境并非百分之白安全,本公司将尽力确保或担保用户发送给本公司的任何信息的安全性。如果本公司的物理、技术,或管理防护设施遭到破坏,导致信息被非授权访问、公开披露、篡改,或破坏,导致您的合法权益受损,本公司将承担相应的法律责任。
+        <br />
+        6.
+        本公司发现用户个人信息发生或可能发生泄露、毁损、灭失的,本公司将将立即启动应急预案,采取补救措施;造成或者可能造成严重后果的,会立即向有关主管部门报告,并将事件相关情况以邮件、信函、电话、推送通知等方式告知受影响的用户,难以逐一告知的,将会采取合理、有效的方式发布相关警示信息。包括但不限于安全事件的基本情况和可能的影响、本公司已采取或将要采取的处置措施、用户可自主防范和降低风险的建议、对用户的补救措施等。同时,本公司还将按照监管部门要求,主动上报个人信息安全事件的处置情况。
+        <br />
+        7.
+        本公司将用户的个人信息存储于中华人民共和国境内。如需跨境传输,本公司将依法征得用户的授权同意。
+        <br />
+        8.
+        用户提供的上述信息,将在该用户使用本服务期间持续授权本公司使用。本公司停止运营软件或服务的,将会立即停止收集用户个人信息,并删除持有的用户个人信息,并将停止运营的通知及时通知用户;当该用户注销账号时,本公司将停止使用并删除上述信息。
+        <br />
         <h2>四、用户的权利及其实现的途径和方法</h2>
-        1. 更正<br />
-        用户或一旦发现本公司收集、存储、使用、披露的用户个人信息有错误的,有权要求本公司予以更正,本公司将会及时采取措施予以更正。<br />
-        2. 删除<br />
-        用户要求本公司删除其收集、存储、使用、披露的用户个人信息时,本公司将会及时采取措施予以删除,包括但不限于以下情形:<br />
-        2.1本公司违反法律、行政法规的规定或者双方的约定收集、存储、使用、转移、披露用户个人信息的;<br />
-        2.2本公司超出目的范围或者必要期限收集、存储、使用、转移、披露用户个人信息的;<br />
-        2.3用户撤回授权同意的;<br />
-        2.4用户通过注销等方式终止使用本公司提供的软件及其相关服务的。<br />
-        当本公司决定相应用户的删除请求时,本公司还将同时通知从本公司获得用户个人信息的实体,并要求其及时删除,除非法律法规另有规定,或这些实体获得了用户的独立授权。<br />
+        1. 更正
+        <br />
+        用户或一旦发现本公司收集、存储、使用、披露的用户个人信息有错误的,有权要求本公司予以更正,本公司将会及时采取措施予以更正。
+        <br />
+        2. 删除
+        <br />
+        用户要求本公司删除其收集、存储、使用、披露的用户个人信息时,本公司将会及时采取措施予以删除,包括但不限于以下情形:
+        <br />
+        2.1本公司违反法律、行政法规的规定或者双方的约定收集、存储、使用、转移、披露用户个人信息的;
+        <br />
+        2.2本公司超出目的范围或者必要期限收集、存储、使用、转移、披露用户个人信息的;
+        <br />
+        2.3用户撤回授权同意的;
+        <br />
+        2.4用户通过注销等方式终止使用本公司提供的软件及其相关服务的。
+        <br />
+        当本公司决定相应用户的删除请求时,本公司还将同时通知从本公司获得用户个人信息的实体,并要求其及时删除,除非法律法规另有规定,或这些实体获得了用户的独立授权。
+        <br />
         当用户从本公司的软件中删除信息后,本公司可能不会立即备份系统中删除相应的信息,但会在备份更新时删除这些信息。
-        3. 访问<br />
-        用户有权访问该用户的个人信息,法律法规规定的例外情况除外。如果用户及其简化人希望访问、编辑、更改该用户的账户中的个人资料信息、更改密码、或关闭用户的账户等,均可以通过访问本公司提供的软件执行此类操作。如果用户无法通过本公司提供的软件访问这些个人信息,可以随时联系本公司。<br />
-        4. 在响应用户的上述请求时,为了保障安全,本公司可能要求提供书面请求,或以其他方式证明身份,本公司会验证用户身份后再处理该请求,并在30日内作出答复。在下列情形时,本公司无法响应用户的请求:<br />
-        4.1与国家安全、国防安全直接相关的;<br />
-        4.2与公共安全、公共卫生、重大公共利益直接相关的;<br />
-        4.3与犯罪侦查、起诉、审判、执行等直接相关的;<br />
-        4.4有充分的证据表明用户存在主观恶意或滥用权利的;<br />
-        4.5响应用户的请求可能导致用户或者其他个人、组织的合法权益受到严重损害的;<br />
-        4.6涉及商业秘密的。<br />
-
+        3. 访问
+        <br />
+        用户有权访问该用户的个人信息,法律法规规定的例外情况除外。如果用户及其简化人希望访问、编辑、更改该用户的账户中的个人资料信息、更改密码、或关闭用户的账户等,均可以通过访问本公司提供的软件执行此类操作。如果用户无法通过本公司提供的软件访问这些个人信息,可以随时联系本公司。
+        <br />
+        4.
+        在响应用户的上述请求时,为了保障安全,本公司可能要求提供书面请求,或以其他方式证明身份,本公司会验证用户身份后再处理该请求,并在30日内作出答复。在下列情形时,本公司无法响应用户的请求:
+        <br />
+        4.1与国家安全、国防安全直接相关的;
+        <br />
+        4.2与公共安全、公共卫生、重大公共利益直接相关的;
+        <br />
+        4.3与犯罪侦查、起诉、审判、执行等直接相关的;
+        <br />
+        4.4有充分的证据表明用户存在主观恶意或滥用权利的;
+        <br />
+        4.5响应用户的请求可能导致用户或者其他个人、组织的合法权益受到严重损害的;
+        <br />
+        4.6涉及商业秘密的。
+        <br />
         <h2>五、关于Cookie及同类技术</h2>
-        为了确保本公司提供的软件及相关服务的正常运转,本公司会在用户的电脑或移动设备上存储名为Cookie的小数据文件。Cookie通常包含标识符、站点名称以及一些号码和字符。本公司不会将Cookie用于本政策所述目的之外的任何用途,用户可根据自己的偏好管理或删除Cookie,有关详情请参见AboutCookies.org。用户可以通过更改设置清除电脑或移动设备上保存的所有Cookie。<br />
-
+        为了确保本公司提供的软件及相关服务的正常运转,本公司会在用户的电脑或移动设备上存储名为Cookie的小数据文件。Cookie通常包含标识符、站点名称以及一些号码和字符。本公司不会将Cookie用于本政策所述目的之外的任何用途,用户可根据自己的偏好管理或删除Cookie,有关详情请参见AboutCookies.org。用户可以通过更改设置清除电脑或移动设备上保存的所有Cookie。
+        <br />
         <h2>六、第三方链接及服务</h2>
-        <strong>本政策仅适用于本公司收集的用户个人信息,本政策不适用于用户通过本公司提供的软件或服务而接入到第三方链接或服务时,该第三方收集和使用用户个人信息的行为及其结果,此时应适用该第三方链接或服务的隐私政策。</strong><br />
-
+        <strong>
+          本政策仅适用于本公司收集的用户个人信息,本政策不适用于用户通过本公司提供的软件或服务而接入到第三方链接或服务时,该第三方收集和使用用户个人信息的行为及其结果,此时应适用该第三方链接或服务的隐私政策。
+        </strong>
+        <br />
         <h2>七、本政策的更新</h2>
-        本公司可能会根据软件及其服务或教学的需要或者根据法律法规的规定修改本政策的全部或部分。如该等更新造成用户在本政策下权利的实质减少或重大变更的,本公司将在更新生效前通过网站公告、推送通知、弹窗提示或其他方式进行通知。用户如果不同意该等更新或变更的,可以选择停止使用本公司的软件及相关服务;如果用户继续使用本公司的软件及相关服务的,即表示用户已充分阅读、理解并同意受该等更新或变更后的本政策的约束。<br />
-
+        本公司可能会根据软件及其服务或教学的需要或者根据法律法规的规定修改本政策的全部或部分。如该等更新造成用户在本政策下权利的实质减少或重大变更的,本公司将在更新生效前通过网站公告、推送通知、弹窗提示或其他方式进行通知。用户如果不同意该等更新或变更的,可以选择停止使用本公司的软件及相关服务;如果用户继续使用本公司的软件及相关服务的,即表示用户已充分阅读、理解并同意受该等更新或变更后的本政策的约束。
+        <br />
         <h2>八、本公司的联系方式</h2>
-        用户如有任何问题、意见或者建议,除本政策页首所述的联系方式以外,还可以通过以下方式与本公司联系。<br />
-        电子邮箱:lexiaoyaVIP@163.com<br />
-        联系电话:027-87718176<br />
+        用户如有任何问题、意见或者建议,除本政策页首所述的联系方式以外,还可以通过以下方式与本公司联系。
+        <br />
+        电子邮箱:lexiaoyaVIP@163.com
+        <br />
+        联系电话:027-87718176
+        <br />
         开发者:武汉乐小雅网络科技有限公司
       </div>
     )
   }
-})
+})

Some files were not shown because too many files changed in this diff