lex 2 vuotta sitten
vanhempi
commit
513be574e4

+ 61 - 0
package-lock.json

@@ -10,6 +10,7 @@
       "license": "MIT",
       "dependencies": {
         "@element-plus/icons-vue": "^2.0.5",
+        "@vant/touch-emulator": "^1.3.2",
         "@vant/use": "^1.3.6",
         "@vueuse/core": "^8.4.1",
         "browserslist": "^4.20.2",
@@ -28,6 +29,7 @@
         "query-string": "^7.1.1",
         "swiper": "^8.2.4",
         "umi-request": "^1.4.0",
+        "vant": "^3.5.2",
         "vconsole": "^3.14.6",
         "vue": "^3.2.26",
         "vue-cropper": "^1.0.3",
@@ -1932,6 +1934,24 @@
         "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
       }
     },
+    "node_modules/@vant/icons": {
+      "version": "1.8.0",
+      "resolved": "https://registry.npmmirror.com/@vant/icons/-/icons-1.8.0.tgz",
+      "integrity": "sha512-sKfEUo2/CkQFuERxvkuF6mGQZDKu3IQdj5rV9Fm0weJXtchDSSQ+zt8qPCNUEhh9Y8shy5PzxbvAfOOkCwlCXg=="
+    },
+    "node_modules/@vant/popperjs": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmmirror.com/@vant/popperjs/-/popperjs-1.2.1.tgz",
+      "integrity": "sha512-qzQlrPE4aOsBzfrktDVwzQy/QICCTKifmjrruhY58+Q2fobUYp/T9QINluIafzsD3VJwgP8+HFVLBsyDmy3VZQ==",
+      "dependencies": {
+        "@popperjs/core": "^2.9.2"
+      }
+    },
+    "node_modules/@vant/touch-emulator": {
+      "version": "1.3.2",
+      "resolved": "https://registry.npmmirror.com/@vant/touch-emulator/-/touch-emulator-1.3.2.tgz",
+      "integrity": "sha512-Om6e8kCAnmk/q8byngKreff7Hyn6XxwOGr8yedP3y3LEVoE+iyj8/+Mn+AYvGEQ00GK0MlgAfyaV4emXAYj1Hw=="
+    },
     "node_modules/@vant/use": {
       "version": "1.4.1",
       "resolved": "https://registry.npmjs.org/@vant/use/-/use-1.4.1.tgz",
@@ -7949,6 +7969,19 @@
         "node": ">= 10.13.0"
       }
     },
+    "node_modules/vant": {
+      "version": "3.5.2",
+      "resolved": "https://registry.npmmirror.com/vant/-/vant-3.5.2.tgz",
+      "integrity": "sha512-RR2SEE/ZHH30Z7a9iLBxskvSzs8rwaU2vDhR7HtBjgXreADzGjU75XxV2ogwrtaj2YU1IaBYC8xXjn3VXRxREw==",
+      "dependencies": {
+        "@vant/icons": "^1.8.0",
+        "@vant/popperjs": "^1.2.1",
+        "@vant/use": "^1.4.1"
+      },
+      "peerDependencies": {
+        "vue": "^3.0.0"
+      }
+    },
     "node_modules/vconsole": {
       "version": "3.14.6",
       "resolved": "https://registry.npmjs.org/vconsole/-/vconsole-3.14.6.tgz",
@@ -10100,6 +10133,24 @@
         "eslint-visitor-keys": "^3.3.0"
       }
     },
+    "@vant/icons": {
+      "version": "1.8.0",
+      "resolved": "https://registry.npmmirror.com/@vant/icons/-/icons-1.8.0.tgz",
+      "integrity": "sha512-sKfEUo2/CkQFuERxvkuF6mGQZDKu3IQdj5rV9Fm0weJXtchDSSQ+zt8qPCNUEhh9Y8shy5PzxbvAfOOkCwlCXg=="
+    },
+    "@vant/popperjs": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmmirror.com/@vant/popperjs/-/popperjs-1.2.1.tgz",
+      "integrity": "sha512-qzQlrPE4aOsBzfrktDVwzQy/QICCTKifmjrruhY58+Q2fobUYp/T9QINluIafzsD3VJwgP8+HFVLBsyDmy3VZQ==",
+      "requires": {
+        "@popperjs/core": "^2.9.2"
+      }
+    },
+    "@vant/touch-emulator": {
+      "version": "1.3.2",
+      "resolved": "https://registry.npmmirror.com/@vant/touch-emulator/-/touch-emulator-1.3.2.tgz",
+      "integrity": "sha512-Om6e8kCAnmk/q8byngKreff7Hyn6XxwOGr8yedP3y3LEVoE+iyj8/+Mn+AYvGEQ00GK0MlgAfyaV4emXAYj1Hw=="
+    },
     "@vant/use": {
       "version": "1.4.1",
       "resolved": "https://registry.npmjs.org/@vant/use/-/use-1.4.1.tgz",
@@ -15044,6 +15095,16 @@
       "integrity": "sha512-83N0OkTbn6gOjJ2awNuzuK4czeGxwEwBoTqlhBZhnp8o0IJ72mXRQKphj/azwRf3acbDJZYZhbOPEJHd884ELg==",
       "dev": true
     },
+    "vant": {
+      "version": "3.5.2",
+      "resolved": "https://registry.npmmirror.com/vant/-/vant-3.5.2.tgz",
+      "integrity": "sha512-RR2SEE/ZHH30Z7a9iLBxskvSzs8rwaU2vDhR7HtBjgXreADzGjU75XxV2ogwrtaj2YU1IaBYC8xXjn3VXRxREw==",
+      "requires": {
+        "@vant/icons": "^1.8.0",
+        "@vant/popperjs": "^1.2.1",
+        "@vant/use": "^1.4.1"
+      }
+    },
     "vconsole": {
       "version": "3.14.6",
       "resolved": "https://registry.npmjs.org/vconsole/-/vconsole-3.14.6.tgz",

+ 2 - 0
package.json

@@ -22,6 +22,7 @@
   },
   "dependencies": {
     "@element-plus/icons-vue": "^2.0.5",
+    "@vant/touch-emulator": "^1.3.2",
     "@vant/use": "^1.3.6",
     "@vueuse/core": "^8.4.1",
     "browserslist": "^4.20.2",
@@ -40,6 +41,7 @@
     "query-string": "^7.1.1",
     "swiper": "^8.2.4",
     "umi-request": "^1.4.0",
+    "vant": "^3.5.2",
     "vconsole": "^3.14.6",
     "vue": "^3.2.26",
     "vue-cropper": "^1.0.3",

BIN
src/common/images/icon_arrow.png


BIN
src/common/images/icon_clock.png


+ 142 - 0
src/components/col-calendar/index.module.less

@@ -0,0 +1,142 @@
+.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: 38px !important;
+      height: 45px !important;
+      border-radius: 5px;
+      overflow: hidden;
+      .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: 38px;
+        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,
+    .van-calendar__day--disabled.full::after {
+      display: inline-block !important;
+      background-color: #ffd7a6;
+    }
+    // 禁用不显示背景
+    .van-calendar__day--disabled::after {
+      display: none !important;
+    }
+  }
+}
+
+.calenderPopup {
+  border-radius: 8px;
+  overflow: hidden;
+  padding: 18px 12px 28px;
+  .popup {
+    width: 312px;
+    background: #ffffff;
+  }
+  .title {
+    display: flex;
+    align-items: center;
+    justify-content: space-around;
+    font-size: 18px;
+    font-weight: 500;
+    color: #333333;
+    line-height: 25px;
+    padding: 0 20px 25px;
+  }
+  .container {
+    display: flex;
+    align-items: center;
+    flex-wrap: wrap;
+    padding-bottom: 14px;
+    & > div {
+      flex-basis: 33.33%;
+      text-align: center;
+    }
+  }
+  .noDay {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    flex: 1;
+    padding: 25px 0 35px;
+    .clock {
+      width: 30px;
+    }
+    span {
+      padding-left: 10px;
+      font-size: 14px;
+      font-weight: 500;
+      color: #999;
+    }
+  }
+  .tag {
+    padding: 8px 10px;
+    margin-bottom: 10px;
+    font-size: 12px;
+    color: #333333 !important;
+    border-color: #d8d8d8 !important;
+
+    &.active {
+      color: var(--van-primary) !important;
+      background: #e0f7f3;
+      border-color: var(--van-primary) !important;
+    }
+  }
+
+  .dayBtn {
+    display: flex;
+    align-items: center;
+  }
+}

+ 332 - 9
src/components/col-calendar/index.tsx

@@ -1,9 +1,332 @@
-import { defineComponent } from 'vue'
-import styles from './index.module.less'
-
-export default defineComponent({
-  name: 'col-calendar',
-  render() {
-    return <></>
-  }
-})
+import { Button, Calendar, Icon, Image, Popup, Tag } from 'vant'
+import 'vant/es/calendar/style'
+import { defineComponent } from 'vue'
+import dayjs from 'dayjs'
+import styles from './index.module.less'
+import IconArrow from '@/common/images/icon_arrow.png'
+import IconClock from '@/common/images/icon_clock.png'
+import { ElMessage } from 'element-plus'
+
+export default defineComponent({
+  name: 'calendar',
+  props: {
+    calendarDate: {
+      type: Date,
+      default: () => new Date()
+    },
+    // 选中的数据
+    selectList: {
+      type: Array,
+      default: []
+    },
+    // 接口数据
+    list: {
+      type: Object,
+      default: {}
+    },
+    /**
+     * 每天选择课程最大数
+     */
+    maxDays: {
+      type: [Number, String],
+      default: 0
+    },
+    /**
+     * 点击并选中任意日期时触发
+     */
+    onSelect: {
+      type: Function,
+      default: (date: Date) => {}
+    },
+    /**
+     * 上一月,不能小于当月
+     */
+    prevMonth: {
+      type: Function,
+      default: (date: Date) => {}
+    },
+    /**
+     * 下一月,暂无限制
+     */
+    nextMonth: {
+      type: Function,
+      default: (date: Date) => {}
+    },
+    /**
+     * 日期选择结束时触发
+     */
+    selectDay: {
+      type: Function,
+      default: (obj: any) => {}
+    },
+    isSkipHolidays: {
+      // 是否跳过节假日
+      type: Boolean,
+      default: false
+    }
+  },
+  data() {
+    return {
+      minDate: new Date(),
+      maxDate: new Date(),
+      currentDate: new Date(), // 当前日历日期
+      subtitle: '',
+      show: false,
+      dayList: [],
+      selectDays: [] as any
+    }
+  },
+  computed: {
+    arrowStatus() {
+      // 上月箭头状态
+      return !dayjs().isBefore(dayjs(this.currentDate), 'month')
+    },
+    selectDayTitle() {
+      // 选中日期标题
+      return dayjs(this.currentDate).format('YYYY-MM-DD')
+    },
+    isPrevDay() {
+      // 是否可以点击上一天
+      return dayjs(this.currentDate)
+        .subtract(1, 'day')
+        .isBefore(dayjs(this.minDate), 'day')
+    },
+    isNextDay() {
+      // 是否可以点击下一天
+      return dayjs(this.currentDate)
+        .add(1, 'day')
+        .isAfter(dayjs(this.maxDate), 'day')
+    }
+  },
+  mounted() {
+    // 初始化标题和最大显示日期
+    this.subtitle = dayjs().format('YYYY年MM月')
+    this.maxDate = dayjs().endOf('month').toDate()
+    this.minDate = dayjs().add(1, 'day').toDate()
+
+    // 初始化日历
+    // console.log(this.list, 323, this.maxDays)
+  },
+  methods: {
+    formatter(date: any) {
+      const dateStr = dayjs(date.date).format('YYYY-MM-DD')
+      const dateObj = this.list[dateStr]
+      // 判断是否有课程 并且 时间在当前时间之后
+      if (dateObj && dayjs().isBefore(dayjs(date.date))) {
+        if (
+          dateObj &&
+          (dateObj.fullCourse ||
+            !dateObj?.courseTime ||
+            dateObj?.courseTime?.length <= 0)
+        ) {
+          date.bottomInfo = '满'
+          date.className = 'full'
+          date.type = 'disabled'
+        }
+      } else {
+        date.type = 'disabled'
+      }
+      if (dateObj && this.isSkipHolidays && dateObj.holiday) {
+        // date.bottomInfo = '节假日'
+        date.type = 'disabled'
+      }
+
+      date.type = date.type === 'selected' ? '' : date.type
+      return date
+    },
+    onPrevMonth() {
+      // 上一月
+      if (this.arrowStatus) return
+      const tempDate = dayjs(this.currentDate).subtract(1, 'month')
+      this._monthChange(tempDate)
+      this.prevMonth && this.prevMonth(this.minDate)
+    },
+    onNextMonth() {
+      // 下一月
+      const tempDate = dayjs(this.currentDate).add(1, 'month')
+      this._monthChange(tempDate)
+      this.nextMonth && this.nextMonth(this.minDate)
+    },
+    _monthChange(date: any) {
+      // 月份改变
+      // 需要判断是否是当月,需要单独处理最小时间
+      const currentMinDate = dayjs().add(1, 'day').toDate()
+      const monthMinDate = date.startOf('month').toDate()
+      this.minDate = dayjs(currentMinDate).isAfter(monthMinDate)
+        ? currentMinDate
+        : monthMinDate
+      // this.minDate = date.startOf('month').toDate()
+      this.maxDate = date.endOf('month').toDate()
+      this.currentDate = date.toDate()
+      this.$emit('update:calendarDate', date.toDate())
+      this.subtitle = date.format('YYYY年MM月')
+    },
+    onSelectDay(item: any) {
+      // 选择某个时间段
+      const index = this.selectDays.findIndex((days: any) => {
+        return days.startTime === item.startTime
+      })
+      if (this.selectDays.length < this.maxDays || index !== -1) {
+        const index = this.selectDays.findIndex(
+          (days: any) => days.startTime === item.startTime
+        )
+        item.checked = !item.checked
+        if (index === -1) {
+          this.selectDays.push({ ...item })
+        } else {
+          this.selectDays.splice(index, 1)
+        }
+      } else {
+        ElMessage.info('最多选择' + this.maxDays + '个时间段')
+      }
+    },
+    onPrevDay() {
+      // 获取上一天的数据
+      const tempDate = dayjs(this.currentDate).subtract(1, 'day')
+      this._dayChange(tempDate.toDate())
+    },
+    onNextDay() {
+      // 获取下一天的数据
+      const tempDate = dayjs(this.currentDate).add(1, 'day')
+      this._dayChange(tempDate.toDate())
+    },
+    onDateSelect(date: any) {
+      // 选择日历上某一个日期
+      this.selectDays = [...this.selectList] // 初始化用户选中的值
+      this._dayChange(date)
+      this.onSelect && this.onSelect(date)
+    },
+    _dayChange(date: Date) {
+      const dateStr = dayjs(date).format('YYYY-MM-DD')
+      let dataList = (this.list[dateStr] && this.list[dateStr].courseTime) || []
+      dataList.forEach((item: any) => {
+        item.start = dayjs(item.startTime).format('HH:mm')
+        item.end = dayjs(item.endTime).format('HH:mm')
+        const isExist = this.selectDays?.some(
+          (course: any) => course.startTime === item.startTime
+        )
+        item.checked = isExist
+      })
+      this.dayList = dataList
+      this.currentDate = date // 更新当前日期
+      this.$emit('update:calendarDate', date)
+      this.show = true
+    }
+  },
+  render() {
+    return (
+      <>
+        <Calendar
+          class={styles.calendar}
+          showTitle={false}
+          poppable={false}
+          showConfirm={false}
+          showMark={false}
+          firstDayOfWeek={1}
+          rowHeight={56}
+          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={22}
+                  class={this.arrowStatus && styles.disabled}
+                  onClick={this.onPrevMonth}
+                />
+                <span>{this.subtitle}</span>
+                <Icon
+                  name={IconArrow}
+                  size={22}
+                  class={styles.right}
+                  onClick={this.onNextMonth}
+                />
+              </div>
+            )
+            // 'bottom-info': (date: any) => <span>{date.type}</span>
+          }}
+        />
+
+        <Popup show={this.show} class={styles.calenderPopup}>
+          <div class={styles.popup}>
+            <div class={styles.title}>
+              {/* <Button
+                type="primary"
+                plain
+                style={{ border: 0 }}
+                size="small"
+                disabled={this.isPrevDay}
+                onClick={this.onPrevDay}
+              >
+                上一日
+              </Button> */}
+              <span>{this.selectDayTitle}</span>
+              {/* <Button
+                type="primary"
+                plain
+                style={{ border: 0 }}
+                size="small"
+                disabled={this.isNextDay}
+                onClick={this.onNextDay}
+              >
+                下一日
+              </Button> */}
+            </div>
+
+            <div class={styles.container}>
+              {this.dayList.map((item: any) => (
+                <div>
+                  <Tag
+                    round
+                    class={[styles.tag, item.checked ? styles.active : '']}
+                    size="large"
+                    plain
+                    onClick={() => this.onSelectDay(item)}
+                  >
+                    {item.start}~{item.end}
+                  </Tag>
+                </div>
+              ))}
+              {this.dayList.length <= 0 && (
+                <div class={styles.noDay}>
+                  <Image src={IconClock} class={styles.clock} fit="cover" />
+                  <span>今日已约满</span>
+                </div>
+              )}
+            </div>
+
+            <div class={styles.dayBtn}>
+              <Button
+                round
+                plain
+                style={{ width: '33.33%', marginRight: '10px' }}
+                onClick={() => {
+                  this.show = false
+                  this.selectDays = []
+                }}
+              >
+                取消
+              </Button>
+              <Button
+                type="primary"
+                block
+                round
+                disabled={!(this.selectDays.length > 0)}
+                onClick={() => {
+                  this.selectDay && this.selectDay(this.selectDays)
+                  this.show = false
+                }}
+              >
+                确认
+              </Button>
+            </div>
+          </div>
+        </Popup>
+      </>
+    )
+  }
+})

+ 5 - 5
src/components/col-steps/index.tsx

@@ -28,16 +28,16 @@ export default defineComponent({
             iconActive: getAssetsHomeFile('6.png')
           },
           {
-            title: '课程安排',
-            icon: getAssetsHomeFile('4.png'),
-            iconActive: getAssetsHomeFile('4_active.png')
-          },
-          {
             title: '教学计划',
             icon: getAssetsHomeFile('2.png'),
             iconActive: getAssetsHomeFile('2_active.png')
           },
           {
+            title: '课程安排',
+            icon: getAssetsHomeFile('4.png'),
+            iconActive: getAssetsHomeFile('4_active.png')
+          },
+          {
             title: '开课条件',
             icon: getAssetsHomeFile('3.png'),
             iconActive: getAssetsHomeFile('3_active.png')

+ 2 - 0
src/views/main.ts

@@ -5,6 +5,8 @@ import '../style/index.css'
 import 'normalize.css'
 import 'element-plus/dist/index.css'
 import * as ElementPlusIconsVue from '@element-plus/icons-vue'
+// 引入模块后自动生效
+import '@vant/touch-emulator' // 处理移动端点击事件 vant 支持桌面端
 
 // import { state } from '@/state'
 

+ 70 - 0
src/views/user-info/live-operation/course-content/index.module.less

@@ -0,0 +1,70 @@
+.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: 17px;
+      background: linear-gradient(180deg, #59e5d5 0%, #2dc7aa 100%);
+      border-radius: 3px;
+    }
+  }
+  .rTag {
+    padding: 10px 0;
+    .tag {
+      background: #e9fff8;
+      margin-bottom: 8px;
+    }
+  }
+
+  .selectPopup {
+    width: 312px;
+    background: #ffffff;
+    border-radius: 8px;
+    .selectContainer {
+      padding: 18px 14px;
+    }
+    .rTitle {
+      font-size: 18px;
+    }
+    .selectPopupContent {
+      padding: 20px 0;
+    }
+    .desc,
+    .times {
+      font-size: 14px;
+      color: #666666;
+      line-height: 20px;
+    }
+
+    .times {
+      padding-top: 15px;
+      span {
+        display: block;
+      }
+    }
+
+    .selectBtn {
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      .btn {
+        width: 48%;
+      }
+    }
+  }
+}

+ 331 - 9
src/views/user-info/live-operation/course-content/index.tsx

@@ -1,17 +1,339 @@
-import { ElForm, ElFormItem, ElInput } from 'element-plus'
+import request from '@/helpers/request'
+import { Button, Popup, Tag, Toast } from 'vant'
 import { defineComponent } from 'vue'
+import styles from './index.module.less'
+import dayjs from 'dayjs'
+import { createState } from '../createState'
+import { state } from '@/state'
+import { getWeekCh } from '@/helpers/utils'
+import ColCalendar from '@/components/col-calendar'
+import { ElButton, ElMessageBox } from 'element-plus'
 
 export default defineComponent({
-  name: 'course-content',
+  name: 'arrange',
+  data() {
+    return {
+      selectStatus: false,
+      calendarList: {},
+      calendarDate: new Date() as Date // 日历当前时间
+    }
+  },
+  computed: {
+    showSelectList() {
+      let list = [...createState.selectCourseList]
+      list.forEach((item: any) => {
+        item.title =
+          dayjs(item.startTime).format('YYYY-MM-DD') +
+          ' ' +
+          getWeekCh(dayjs(item.startTime).day()) +
+          ' ' +
+          item.start +
+          '~' +
+          item.end
+      })
+      return list
+    },
+    selectType() {
+      // 循环次数是否足够
+      return createState.selectCourseList.length < createState.live.courseNum
+        ? 'noEnough'
+        : 'enough'
+    }
+  },
+  async mounted() {
+    const initDate = dayjs().add(1, 'day').toDate()
+    await this.getList(initDate)
+
+    if (createState.coursePlanStatus) {
+      this.selectStatus = true
+    }
+  },
+  methods: {
+    async getList(date?: Date) {
+      let params = {
+        day: dayjs(date || new Date()).format('DD'),
+        month: dayjs(date || new Date()).format('MM'),
+        year: dayjs(date || new Date()).format('YYYY')
+      }
+      try {
+        let res = await request.post(
+          '/api-teacher/courseSchedule/createLiveCourseCalendar',
+          {
+            data: {
+              ...params,
+              singleCourseMinutes: createState.live.singleMins,
+              freeCourseMinutes: createState.live.freeMinutes,
+              teacherId: state.user.data?.userId
+            }
+          }
+        )
+        const result = res.data || []
+        let tempObj = {}
+        result.forEach((item: any) => {
+          tempObj[item.date] = item
+        })
+        this.calendarList = tempObj
+      } catch {}
+    },
+    onSelectDay(obj: any) {
+      const result = obj || []
+      let list = [...createState.selectCourseList]
+      console.log(obj, list)
+      result.forEach((item: any) => {
+        const isExist = list.some(
+          (course: any) => course.startTime === item.startTime
+        )
+        !isExist && list.push({ ...item })
+      })
+      // 去掉不在
+      let tempList: any[] = []
+      list.forEach((item: any) => {
+        const isExist = result.some(
+          (course: any) => course.startTime === item.startTime
+        )
+        // const index = result.findIndex(
+        //   (course: any) => course.startTime === item.startTime
+        // )
+        // !isExist && list.splice(index, 1)
+        isExist && tempList.push(item)
+      })
+      // 对数组进行排序
+      tempList.sort((first: any, second: any) => {
+        if (first.startTime > second.startTime) return 1
+        if (first.startTime < second.startTime) return -1
+        return 0
+      })
+      createState.selectCourseList = [...tempList]
+    },
+    onCloseTag(item: any) {
+      ElMessageBox.confirm('确定删除该课程吗?', '提示', {
+        type: 'warning'
+      }).then(() => {
+        const index = createState.selectCourseList.findIndex(
+          (course: any) => course.startTime === item.startTime
+        )
+        createState.selectCourseList.splice(index, 1)
+      })
+    },
+    async onSubmit() {
+      if (createState.selectCourseList.length <= 0) {
+        Toast('请选择课程时间')
+        return
+      }
+
+      if (createState.selectCourseList.length < createState.live.courseNum) {
+        this.selectStatus = true
+        return
+      }
+
+      await this._lookCourse()
+    },
+    async _lookCourse(callBack?: Function) {
+      try {
+        let times = [] as any
+        createState.selectCourseList.forEach((item: any) => {
+          times.push({
+            startTime: item.startTime,
+            endTime: item.endTime
+          })
+        })
+        const res = await request.post(
+          '/api-teacher/courseGroup/lockCourseToCache',
+          {
+            data: {
+              courseNum: createState.live.courseNum,
+              courseType: 'LIVE',
+              loop: this.selectType === 'noEnough' ? 1 : 0,
+              teacherId: state.user.data?.userId,
+              timeList: [...times]
+            }
+          }
+        )
+        const result = res.data || []
+        result.forEach((item: any, index: number) => {
+          createState.live.coursePlanList[index] = {
+            ...createState.live.coursePlanList[index],
+            startTime: item.startTime,
+            endTime: item.endTime,
+            classNum: index + 1
+          }
+        })
+        createState.coursePlanStatus = true
+        this.selectStatus = true
+        callBack && callBack()
+      } catch (e: any) {
+        // 报错时需要重置日历表的数据
+        const message = e.message
+        ElMessageBox.confirm(message, '提示', {
+          type: 'warning'
+        }).then(() => {
+          this.getList(this.calendarDate || new Date())
+          createState.selectCourseList = []
+          this.selectStatus = false
+        })
+      }
+    },
+    async _unLookCourse() {
+      try {
+        await request.get('/api-teacher/courseGroup/unlockCourseToCache', {
+          params: {
+            teacherId: state.user.data?.userId
+          }
+        })
+        this.selectStatus = false
+        setTimeout(() => {
+          createState.live.coursePlanList.forEach((item: any) => {
+            item.startTime = ''
+            item.endTime = ''
+          })
+        }, 500)
+      } catch {}
+    },
+    async onReset() {
+      // 是否有锁课状态 或 是锁课类型的
+      if (createState.coursePlanStatus || this.selectType === 'enough') {
+        await this._unLookCourse()
+      } else if (this.selectType === 'noEnough') {
+        this.selectStatus = false
+      }
+      createState.live.coursePlanList = []
+      setTimeout(() => {
+        createState.coursePlanStatus = false
+      }, 500)
+    },
+    async onSure() {
+      // 判断是否有锁课状态 或 是锁课类型的 并且已经有课的
+      if (
+        this.selectType === 'enough' ||
+        createState.live.coursePlanList.length > 0
+      ) {
+        this.selectStatus = false
+        createState.active = 4
+        return
+      }
+      const status = createState.coursePlanStatus
+      await this._lookCourse(() => {
+        if (status) {
+          this.selectStatus = false
+          createState.active = 4
+        }
+      })
+    }
+  },
   render() {
     return (
-      <>
-        <ElForm>
-          <ElFormItem label="第一课">
-            <ElInput type="textarea" rows="3" />
-          </ElFormItem>
-        </ElForm>
-      </>
+      <div class={[styles.arrange]}>
+        <div class="px-[235px] pt-7">
+          <div class="border-dashed border-[#EDEDED] border-2 rounded-lg px-8 pt-4 pb-6">
+            <ColCalendar
+              selectList={createState.selectCourseList}
+              list={this.calendarList}
+              maxDays={createState.live.courseNum || 0}
+              nextMonth={(date: Date) => this.getList(date)}
+              prevMonth={(date: Date) => this.getList(date)}
+              selectDay={this.onSelectDay}
+              v-model:calendarDate={this.calendarDate}
+            />
+          </div>
+
+          <div class={[styles.arrangeCell, 'mb12']}>
+            <div class={styles.rTitle}>
+              <span>已选择课程时间</span>
+            </div>
+
+            <div class={styles.rTag}>
+              {this.showSelectList.map((item: any) => (
+                <>
+                  <Tag
+                    plain
+                    round
+                    closeable
+                    size="large"
+                    type="primary"
+                    class={styles.tag}
+                    onClose={() => this.onCloseTag(item)}
+                  >
+                    {item.title}
+                  </Tag>
+                  <br />
+                </>
+              ))}
+            </div>
+          </div>
+        </div>
+
+        <div class="border-t border-t-[#E5E5E5] text-center pt-6 pb-7">
+          <ElButton
+            class="!w-40 !h-[38px]"
+            onClick={() => {
+              createState.active = 2
+              // 重置选择的课次
+              createState.selectCourseList = []
+            }}
+          >
+            上一步
+          </ElButton>
+          <ElButton
+            type="primary"
+            class="!w-40 !h-[38px]"
+            onClick={this.onSubmit}
+          >
+            下一步
+          </ElButton>
+        </div>
+
+        <Popup show={this.selectStatus} class={styles.selectPopup}>
+          <div class={styles.selectContainer}>
+            <div class={styles.rTitle}>
+              <span>提示</span>
+            </div>
+            <div class={styles.selectPopupContent}>
+              <p class={styles.desc}>
+                {this.selectType === 'noEnough' && !createState.coursePlanStatus
+                  ? '您所选择的上课时间未达到您输入的课时数,系统根据已选时间将自动按周顺延排课。'
+                  : '您已选择以下上课时间段,时间段会暂时锁定,锁定期间学员不可购买该时间段课程。'}
+              </p>
+              {createState.live.coursePlanList &&
+                createState.live.coursePlanList.length > 0 &&
+                createState.coursePlanStatus && (
+                  <p class={styles.times}>
+                    {createState.live.coursePlanList.map((item: any) => (
+                      <span>
+                        {dayjs(item.startTime || new Date()).format(
+                          'YYYY-MM-DD'
+                        )}{' '}
+                        {dayjs(item.startTime || new Date()).format('HH:mm')}~
+                        {dayjs(item.endTime || new Date()).format('HH:mm')}
+                      </span>
+                    ))}
+                  </p>
+                )}
+            </div>
+
+            <div class={styles.selectBtn}>
+              <Button
+                class={styles.btn}
+                type="primary"
+                round
+                block
+                plain
+                onClick={this.onReset}
+              >
+                {this.selectType === 'noEnough' ? '继续选择' : '重新选择'}
+              </Button>
+              <Button
+                class={styles.btn}
+                type="primary"
+                round
+                block
+                onClick={this.onSure}
+              >
+                确认
+              </Button>
+            </div>
+          </div>
+        </Popup>
+      </div>
     )
   }
 })

+ 2 - 2
src/views/user-info/live-operation/index.tsx

@@ -29,8 +29,8 @@ export default defineComponent({
           />
 
           {createState.active === 0 && <CourseInfo />}
-          {createState.active === 1 && <CourseContent />}
-          {createState.active === 2 && <CoursePlan />}
+          {createState.active === 1 && <CoursePlan />}
+          {createState.active === 2 && <CourseContent />}
           {/* {createState.active === 1 && <CourseContent />} */}
         </div>
       </>

+ 28 - 2
yarn.lock

@@ -1074,7 +1074,7 @@
     "@nodelib/fs.scandir" "2.1.5"
     "fastq" "^1.6.0"
 
-"@popperjs/core@npm:@sxzz/popperjs-es@^2.11.7":
+"@popperjs/core@^2.9.2", "@popperjs/core@npm:@sxzz/popperjs-es@^2.11.7":
   "integrity" "sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ=="
   "resolved" "https://registry.npmjs.org/@sxzz/popperjs-es/-/popperjs-es-2.11.7.tgz"
   "version" "2.11.7"
@@ -1222,7 +1222,24 @@
     "@typescript-eslint/types" "5.28.0"
     "eslint-visitor-keys" "^3.3.0"
 
-"@vant/use@^1.3.6":
+"@vant/icons@^1.8.0":
+  "integrity" "sha512-sKfEUo2/CkQFuERxvkuF6mGQZDKu3IQdj5rV9Fm0weJXtchDSSQ+zt8qPCNUEhh9Y8shy5PzxbvAfOOkCwlCXg=="
+  "resolved" "https://registry.npmmirror.com/@vant/icons/-/icons-1.8.0.tgz"
+  "version" "1.8.0"
+
+"@vant/popperjs@^1.2.1":
+  "integrity" "sha512-qzQlrPE4aOsBzfrktDVwzQy/QICCTKifmjrruhY58+Q2fobUYp/T9QINluIafzsD3VJwgP8+HFVLBsyDmy3VZQ=="
+  "resolved" "https://registry.npmmirror.com/@vant/popperjs/-/popperjs-1.2.1.tgz"
+  "version" "1.2.1"
+  dependencies:
+    "@popperjs/core" "^2.9.2"
+
+"@vant/touch-emulator@^1.3.2":
+  "integrity" "sha512-Om6e8kCAnmk/q8byngKreff7Hyn6XxwOGr8yedP3y3LEVoE+iyj8/+Mn+AYvGEQ00GK0MlgAfyaV4emXAYj1Hw=="
+  "resolved" "https://registry.npmmirror.com/@vant/touch-emulator/-/touch-emulator-1.3.2.tgz"
+  "version" "1.3.2"
+
+"@vant/use@^1.3.6", "@vant/use@^1.4.1":
   "integrity" "sha512-YonNN0SuJLEJuqdoMcVAJm2JUZWkHNrW81QzeF6FLyG5HFUGlmTM5Sby7gdS3Z/8UDMlkWRQpJxBWbmVzmUWxQ=="
   "resolved" "https://registry.npmjs.org/@vant/use/-/use-1.4.1.tgz"
   "version" "1.4.1"
@@ -5063,6 +5080,15 @@
   "resolved" "https://registry.npmjs.org/v8flags/-/v8flags-4.0.0.tgz"
   "version" "4.0.0"
 
+"vant@^3.5.2":
+  "integrity" "sha512-RR2SEE/ZHH30Z7a9iLBxskvSzs8rwaU2vDhR7HtBjgXreADzGjU75XxV2ogwrtaj2YU1IaBYC8xXjn3VXRxREw=="
+  "resolved" "https://registry.npmmirror.com/vant/-/vant-3.5.2.tgz"
+  "version" "3.5.2"
+  dependencies:
+    "@vant/icons" "^1.8.0"
+    "@vant/popperjs" "^1.2.1"
+    "@vant/use" "^1.4.1"
+
 "vconsole@^3.14.6":
   "integrity" "sha512-8Ffk2SfNe6EzKqZ0aNnNjpAVBVT7zgJo81lYEJdKySYLVYBeSawdSkWi9fSjDg3WsQhgS1vNPmRqJDTuwdVbnQ=="
   "resolved" "https://registry.npmjs.org/vconsole/-/vconsole-3.14.6.tgz"