Browse Source

云琴房排课

skyblued 2 years ago
parent
commit
a72a63638d

+ 54 - 0
src/teacher/piano-room/class-arrangement/course-schedule/index.module.less

@@ -0,0 +1,54 @@
+.box {
+  font-size: 14px;
+  line-height: 20px;
+}
+.wrap {
+  max-height: 50vh;
+  overflow-y: auto;
+}
+.title {
+  position: relative;
+  display: flex;
+  align-items: center;
+  padding: 8px 0;
+  font-size: 18px;
+  font-weight: 500;
+  color: #333333;
+  line-height: 30px;
+}
+.leftIcon {
+  width: 4px;
+  height: 18px;
+  background: linear-gradient(180deg, #59e5d5 0%, #2dc7aa 100%);
+  border-radius: 3px;
+  margin-right: 6px;
+}
+.stu {
+  color: #333333;
+  font-weight: 400;
+}
+
+.timeBox {
+  height: 134px;
+  overflow-y: auto;
+  background-color: #f7f8f9;
+  border-radius: 8px;
+  margin: 10px 0;
+  padding: 7px 8px;
+}
+.timeTitle {
+  color: #333;
+  font-weight: 500;
+}
+.timeItem {
+  color: #666666;
+  line-height: 24px;
+}
+.footer {
+  display: flex;
+  justify-content: space-evenly;
+  padding: 10px 0;
+  button {
+    width: 136px;
+  }
+}

+ 82 - 0
src/teacher/piano-room/class-arrangement/course-schedule/index.tsx

@@ -0,0 +1,82 @@
+import { Button } from 'vant'
+import { computed, defineComponent, PropType } from 'vue'
+import { IStudent } from '../select-students'
+import styles from './index.module.less'
+
+export default defineComponent({
+  name: 'CourseSchedule',
+  props: {
+    item: {
+      type: Object,
+      default: {}
+    },
+    students: {
+      type: Array as PropType<IStudent[]>,
+      default: []
+    },
+    curriculum: {
+      type: Array as PropType<string[]>,
+      default: []
+    },
+    onClose: {
+      type: Function,
+      default: () => {}
+    },
+    onComfirm: {
+      type: Function,
+      default: () => {}
+    }
+  },
+  setup(props) {
+    const students = computed(() => {
+      let list = props.students.map((n: IStudent) => n.realName)
+      return list.join('、')
+    })
+    return () => (
+      <div class={styles.box}>
+        <div class={styles.title}>
+          <div class={styles.leftIcon}></div>
+          课程预览
+        </div>
+        <div class={styles.wrap}>
+          <div class={styles.stu}>
+            <div>
+              您将为学员:
+              <span style={{ color: 'var(--van-primary)' }}>{students.value}</span>
+            </div>
+            <div>
+              排
+              <span style={{ color: '#FF4E19', margin: '0 10px' }}>
+                {props.item.classNum}节 {props.item.singleClssTime}分钟
+              </span>
+              课程
+            </div>
+          </div>
+          <div class={styles.timeBox}>
+            <div class={styles.timeTitle}>上课时间:</div>
+            {props.curriculum.map((item: string) => (
+              <div class={styles.timeItem}>{item}</div>
+            ))}
+          </div>
+
+          <div style={{ color: '#999999' }}>
+            以上课程预计将消耗琴房时长{' '}
+            {Math.ceil(props.item.classNum * props.item.singleClssTime)} 分钟{' '}
+            <br />
+            确认排课后时长冻结 <br />
+            实际消耗时长以扣减结果为准 <br />
+          </div>
+        </div>
+
+        <div class={styles.footer}>
+          <Button block round onClick={() => props.onClose()}>
+            重新选择
+          </Button>
+          <Button block round type="primary" onClick={() => props.onComfirm()}>
+            确认排课
+          </Button>
+        </div>
+      </div>
+    )
+  }
+})

+ 116 - 0
src/teacher/piano-room/class-arrangement/index.module.less

@@ -29,3 +29,119 @@
   color: #696969;
   line-height: 21px;
 }
+.week {
+  display: flex;
+  justify-content: space-between;
+  :global {
+    .van-radio__icon {
+      display: none;
+    }
+    .van-radio__label {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      width: 39px;
+      height: 39px;
+      background: #f5f8fb;
+      border-radius: 6px;
+      font-size: 12px;
+      font-weight: 500;
+      color: var(--van-primary);
+      margin: 0;
+    }
+    .van-radio[aria-checked='true'] {
+      .van-radio__label {
+        background-color: var(--van-primary);
+        color: #fff;
+      }
+    }
+    .van-radio.van-radio--disabled {
+      .van-radio__label {
+        color: #cdced0;
+      }
+    }
+  }
+}
+.holdays {
+  display: flex;
+  justify-content: flex-end;
+  :global {
+    .van-radio__icon {
+      display: none;
+    }
+    .van-radio__label {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      width: 38px;
+      height: 22px;
+      background: #f5f8fb;
+      border-radius: 3px;
+      border: 1px solid #999999;
+      font-size: 12px;
+      font-weight: 500;
+      color: #999999;
+      margin: 0;
+    }
+    .van-radio[aria-checked='true'] {
+      .van-radio__label {
+        background-color: var(--van-primary);
+        border-color: var(--van-primary);
+        color: #fff;
+      }
+    }
+  }
+}
+
+.coursePopup {
+  min-height: 411px;
+  border-radius: 8px;
+  padding: 18px 14px;
+  box-sizing: border-box;
+}
+
+.tags {
+  display: flex;
+  flex-wrap: wrap;
+  padding: 12px;
+  :global {
+    .van-tag {
+      padding: 2px 5px;
+      border-radius: 3px;
+      border: 1px solid var(--van-primary);
+      background-color: #e0f7f3;
+      color: var(--van-primary);
+      margin-right: 8px;
+      margin-bottom: 8px;
+      font-size: 12px;
+      line-height: 17px;
+      font-weight: 500;
+    }
+  }
+}
+// .picker {
+//   display: flex;
+//   align-items: center;
+//   :global {
+//     .van-picker__toolbar {
+//       display: none;
+//     }
+//     .van-picker {
+//       flex: 1;
+//     }
+//   }
+// }
+.pickerTitle {
+  justify-content: center;
+  font-size: 18px;
+  color: #000000;
+  font-weight: 500;
+  text-align: center;
+}
+.singleClssTime {
+  :global {
+    .van-field__control {
+      text-align: center;
+    }
+  }
+}

+ 428 - 77
src/teacher/piano-room/class-arrangement/index.tsx

@@ -1,9 +1,26 @@
-import { Button, Cell, DatetimePicker, Field, Popup, Stepper } from 'vant'
-import { defineComponent, ref } from 'vue'
+import {
+  Button,
+  Cell,
+  DatetimePicker,
+  Field,
+  Popup,
+  Radio,
+  RadioGroup,
+  Stepper,
+  Tag,
+  Toast
+} from 'vant'
+import { computed, defineComponent, onMounted, reactive, ref } from 'vue'
 import styles from './index.module.less'
 import iconTips from '../images/icon_tips.png'
 import { formatterDate } from '@/helpers/utils'
 import request from '@/helpers/request'
+import OrganSearch from '@/student/practice-class/model/organ-search'
+import dayjs from 'dayjs'
+import ColHeader from '@/components/col-header'
+import ColPopup from '@/components/col-popup'
+import SelectStudents, { IStudent } from './select-students'
+import CourseSchedule from './course-schedule'
 const fieldProps = {
   'is-link': true,
   readonly: true,
@@ -12,106 +29,440 @@ const fieldProps = {
 export default defineComponent({
   name: 'ClassArrangement',
   setup() {
-    const dateShow = ref<boolean>(false)
-    const setDate = (time: Date) => {
-      console.log(time)
-      dateShow.value = false
+    const dateShow = ref(false)
+    const timeShow = ref(false)
+    const voiceShow = ref(false)
+    const selectStudentShow = ref(false)
+    const confirmShow = ref(false)
+
+    // 参数
+    const params = reactive({
+      courseName: '', // 课程名称
+      classNum: 1, // 课时数
+      singleClssTime: 45, //单课时长
+      studentIds: [], //学员id集合
+      timeList: [] as any, // 上课时间
+      date: '',
+      time: '',
+      subjectId: 0,
+      subjectName: '',
+      week: '', // 周几
+      isSkipHolidays: true
+    })
+    const startTime = ref('')
+    const endTime = ref('')
+
+    // 上课时间
+    const timeScope = computed(() => {
+      //   if (startTime.value && endTime.value) {
+      //     let start = startTime.value.split(':')
+      //     let end = endTime.value.split(':')
+      //     let min = parseInt(start[0]) * 60 + parseInt(start[1])
+      //     let max = parseInt(end[0]) * 60 + parseInt(end[1])
+      //     // params.singleClssTime = max - min
+      //     return startTime.value + ' ~ ' + endTime.value
+      //   }
+      //   params.singleClssTime = 0
+      return startTime.value
+    })
+
+    // 学员
+    const students = ref<IStudent[]>([])
+    // 设置学员
+    const onSetStudents = (result: IStudent[]) => {
+      students.value = result
+      selectStudentShow.value = false
     }
+    const onDeleteStudent = index => {
+      const n = students.value.splice(index, 1)[0]
+      studentRef?.value.onDelete(n)
+    }
+    const studentRef = ref('') as any
+
     // 训练声部
-    // const subjectList = ref<>()
+    const subjectList = ref<[]>([]) // 声部分类
     const getSubjectSelect = async () => {
       try {
         const res = await request.get('/api-teacher/subject/subjectSelect')
-        // this.subjectList = res.data || []
+        subjectList.value = res.data || []
       } catch {}
     }
+    onMounted(() => {
+      getSubjectSelect()
+    })
+
+    //开始时间
+    const onSetTime = async (time: string) => {
+      console.log(time)
+      params.time = time
+      timeShow.value = false
+    }
+
+    const holidays = ref('') // 节假日
+    const getHolidays = async (year: string) => {
+      try {
+        let result = await request.get(
+          '/api-teacher/courseSchedule/selectHoliday',
+          { params: { year } }
+        )
+        holidays.value = result?.data?.holidaysFestivalsJson || ''
+      } catch (error) {}
+    }
+
+    // 计算日期
+    const calcDate = () => {
+      const timeList = [] as any
+      const curriculumList = [] as any
+      let total = 0
+      let index = 0
+      const selectWeekIndex = week[params.week]
+      const endTime = dayjs()
+        .set('hour', Number(startTime.value.split(':')[0]))
+        .set('minute', Number(startTime.value.split(':')[1]))
+        .add(params.singleClssTime, 'minute')
+        .format('HH:mm')
+      while (total < params.classNum) {
+        const time = dayjs(params.date).add(index, 'day')
+        index++
+        const weekIndex = time.get('day')
+        if (weekIndex !== selectWeekIndex) {
+          continue
+        }
+        const year_month_date = time.format('YYYY-MM-DD')
+        if (params.isSkipHolidays) {
+          if (
+            ![6, 0].includes(time.get('day')) &&
+            !holidays.value.includes(year_month_date)
+          ) {
+            total++
+            timeList.push({
+              startTime: year_month_date + ` ${startTime.value}`,
+              endTime: year_month_date + ` ${endTime}`
+            })
+            curriculumList.push(
+              year_month_date + ` ${startTime.value} ~ ${endTime}`
+            )
+          }
+        } else {
+          total++
+          timeList.push({
+            startTime: year_month_date + ` ${startTime.value}`,
+            endTime: year_month_date + ` ${endTime}`
+          })
+          curriculumList.push(
+            year_month_date + ` ${startTime.value} ~ ${endTime}`
+          )
+        }
+      }
+      return { timeList, curriculumList }
+    }
+
+    const curriculum = ref<string[]>([])
+    //  设置排课数据
+    const setParmas = () => {
+      if (!params.courseName) {
+        Toast('请填写课程名称')
+        return
+      }
+      if (!params.subjectId) {
+        Toast('请选择训练声部')
+        return
+      }
+      if (!students.value.length) {
+        Toast('请选择上课学员')
+        return
+      }
+      if (!params.singleClssTime) {
+        Toast('请填写单课时时长')
+        return
+      }
+      if (!params.date) {
+        Toast('请选择开始日期')
+        return
+      }
+
+      if (!params.week) {
+        Toast('请选择循环周次')
+        return
+      }
+
+      if (!startTime.value) {
+        Toast('请选择上课时间')
+        return
+      }
+      const { timeList, curriculumList } = calcDate()
+      params.timeList = timeList
+      curriculum.value = curriculumList
+      console.log(curriculumList)
+      confirmShow.value = true
+    }
+
+    // 排课
+    const onCourseSchedule = async () => {
+      try {
+        let { code, data } = await request.post(
+          '/api-teacher/courseSchedule/arrangeCourse',
+          {
+            data: {
+              classNum: params.classNum, //课时数
+              consumeTime: Math.ceil(params.classNum * params.singleClssTime), //消耗时长
+              courseName: params.courseName, //课程名称
+              singleClssTime: params.singleClssTime, //单课时长
+              studentIds: students.value.map(n => n.userId), //学员id集合
+              subjectId: params.subjectId, //声部id
+              timeList: params.timeList
+            }
+          }
+        )
+        if(code === 200){
+            confirmShow.value = false
+        }
+      } catch (error) {}
+      
+    }
+    const week = {
+      周一: 1,
+      周二: 2,
+      周三: 3,
+      周四: 4,
+      周五: 5,
+      周六: 6,
+      周日: 0
+    }
+    // console.log(Object.values(week))
     return () => (
-      <div class={styles.container}>
-        <Field label="课程名称" placeholder="请输入课程名称" />
-        <Field label="训练声部" placeholder="请选择训练声部" {...fieldProps} />
-        <Field label="上课学员" placeholder="请选择上课学员" {...fieldProps} />
-        <Field
-          label="课时数"
-          placeholder="请输入课时数"
-          v-slots={{
-            input: () => <Stepper></Stepper>
-          }}
-        />
-        {/* <Field label="单课时时长" placeholder="请输入课程时长" /> */}
-        <Field
-          label="开始日期"
-          placeholder="请选择开始日期"
-          {...fieldProps}
-          onClick={() => (dateShow.value = true)}
-        />
-        <Cell title="循环周次" />
-        <Field label="开始时间" placeholder="请选择开始时间" {...fieldProps} />
-        <Cell title="是否跳过节假日" />
-        <Cell
-          v-slots={{
-            title: () => (
-              <div class={styles.tips}>
-                <img class={styles.icon} src={iconTips} />
-                <span>温馨提醒</span>
-              </div>
-            ),
-            label: () => (
-              <div class={styles.tipsContent}>
-                1、云酷琴房时长按课程人数扣减(含老师),以45分钟1对1课程师生2人为例,课程结束后将消耗时长:2人*45分钟=90分钟;
-                <br />
-                <br />
-                2、每节线上课平台赠送10分钟免费时长,分别为课前5分钟及课后5分钟,赠送时长不计算费用;
-                <br />
-                <br />
-                3、课程消耗时长按排课人数计算,无论实际到课人数是否为排课人数,都会按照排课人数扣费;
-                <br />
-                <br />
-                4、课程结束后费用立即结算;
-                <br />
-                <br />
-                5、琴房时长不足时,您将无法排课,请确保琴房剩余时长充足。
+      <>
+        <ColHeader />
+        <div class={styles.container}>
+          <Field
+            label="课程名称"
+            placeholder="请输入课程名称"
+            v-model={params.courseName}
+          />
+          <Field
+            label="训练声部"
+            placeholder="请选择训练声部"
+            {...fieldProps}
+            modelValue={params.subjectName}
+            onClick={() => (voiceShow.value = true)}
+          />
+          <Cell style={{ padding: 0 }}>
+            <Field
+              style={{ margin: 0 }}
+              border={false}
+              label="上课学员"
+              placeholder="请选择上课学员"
+              {...fieldProps}
+              onClick={() => (selectStudentShow.value = true)}
+            />
+            {students.value.length ? (
+              <div class={styles.tags}>
+                {students.value.map((n: IStudent, index: number) => (
+                  <Tag closeable onClose={() => onDeleteStudent(index)}>
+                    {n.userName}
+                  </Tag>
+                ))}
               </div>
-            )
-          }}
-        />
+            ) : null}
+          </Cell>
+          <Field
+            label="课时数"
+            placeholder="请输入课时数"
+            v-slots={{
+              input: () => <Stepper v-model={params.classNum}></Stepper>
+            }}
+          />
+          <Field
+            class={styles.singleClssTime}
+            type="number"
+            label="单课时时长"
+            placeholder="请输入课程时长"
+            modelValue={params.singleClssTime}
+            onUpdate:modelValue={(t) => {
+                if (Math.abs(t) > 60){
+                    Toast('时长不能大于60分钟')
+                    return
+                }
+                params.singleClssTime = Math.abs(t)
+            }}
+            // v-model={params.singleClssTime}
+            v-slots={{
+              'right-icon': () => <div>分钟</div>
+            }}
+          />
+          <Field
+            label="开始日期"
+            placeholder="请选择开始日期"
+            {...fieldProps}
+            modelValue={params.date}
+            onClick={() => (dateShow.value = true)}
+          />
+          <Cell
+            title="循环周次"
+            v-slots={{
+              label: () => (
+                <RadioGroup class={styles.week} v-model={params.week}>
+                  {Object.keys(week).map((n: string) => {
+                    return (
+                      <Radio
+                        disabled={
+                          params.isSkipHolidays &&
+                          (n === '周六' || n === '周日')
+                        }
+                        name={n}
+                      >
+                        {n}
+                      </Radio>
+                    )
+                  })}
+                </RadioGroup>
+              )
+            }}
+          />
+          <Field
+            label="上课时间"
+            placeholder="请选择上课时间"
+            {...fieldProps}
+            modelValue={timeScope.value}
+            onClick={() => (timeShow.value = true)}
+          />
+          <Cell
+            title="是否跳过节假日"
+            v-slots={{
+              value: () => (
+                <RadioGroup
+                  class={styles.holdays}
+                  v-model={params.isSkipHolidays}
+                  onChange={() => {
+                    if (
+                      params.isSkipHolidays &&
+                      (params.week === '周六' || params.week === '周日')
+                    ) {
+                      params.week = ''
+                    }
+                  }}
+                >
+                  <Radio name={true} style={{ marginRight: '10px' }}>
+                    是
+                  </Radio>
+                  <Radio name={false}>否</Radio>
+                </RadioGroup>
+              )
+            }}
+          />
+          <Cell
+            v-slots={{
+              title: () => (
+                <div class={styles.tips}>
+                  <img class={styles.icon} src={iconTips} />
+                  <span>温馨提醒</span>
+                </div>
+              ),
+              label: () => (
+                <div class={styles.tipsContent}>
+                  1、云酷琴房时长按课程人数扣减(含老师),以45分钟1对1课程师生2人为例,课程结束后将消耗时长:2人*45分钟=90分钟;
+                  <br />
+                  <br />
+                  2、每节线上课平台赠送10分钟免费时长,分别为课前5分钟及课后5分钟,赠送时长不计算费用;
+                  <br />
+                  <br />
+                  3、课程消耗时长按排课人数计算,无论实际到课人数是否为排课人数,都会按照排课人数扣费;
+                  <br />
+                  <br />
+                  4、课程结束后费用立即结算;
+                  <br />
+                  <br />
+                  5、琴房时长不足时,您将无法排课,请确保琴房剩余时长充足。
+                </div>
+              )
+            }}
+          />
 
-        <Button
-          block
-          type="primary"
-          round
-          style={{ margin: '0 auto', width: '90%', marginTop: '20px' }}
-        >
-          下一步
-        </Button>
+          <Button
+            block
+            type="primary"
+            round
+            style={{ margin: '0 auto', width: '90%', marginTop: '20px' }}
+            onClick={() => setParmas()}
+          >
+            下一步
+          </Button>
+        </div>
 
         <Popup position="bottom" v-model:show={dateShow.value}>
           <DatetimePicker
             type="date"
-            // minDate={new Date()}
+            minDate={dayjs().year(2022).toDate()}
             formatter={formatterDate}
-            onConfirm={setDate}
+            onConfirm={(time: Date) => {
+              params.date = dayjs(time).format('YYYY-MM-DD')
+              dateShow.value = false
+              getHolidays(dayjs(time).format('YYYY'))
+            }}
             onCancel={() => (dateShow.value = false)}
           />
         </Popup>
 
-        {/* <Popup
-          show={this.searchStatus}
+        <Popup
+          v-model:show={voiceShow.value}
           position="bottom"
           round
           closeable
           safe-area-inset-bottom
         >
-          {this.openStatus && (
-            <OrganSearch
-              subjectList={this.subjectList}
-              onSort={this.onSort}
-              isReset
-              v-model={this.params.subjectId}
-              v-model:subjectName={this.params.subjectName}
+          <OrganSearch
+            subjectList={subjectList.value}
+            v-model={params.subjectId}
+            v-model:subjectName={params.subjectName}
+            onSort={() => (voiceShow.value = false)}
+          />
+        </Popup>
+
+        <Popup position="bottom" v-model:show={timeShow.value} round>
+          <div class={styles.picker}>
+            <DatetimePicker
+              v-model={startTime.value}
+              type="time"
+              maxHour="22"
+              onConfirm={() => {
+                timeShow.value = false
+                console.log(startTime.value)
+              }}
+              onCancel={() => (timeShow.value = false)}
             />
-          )}
-        </Popup> */}
-      </div>
+          </div>
+        </Popup>
+
+        {/* 选择学员 */}
+        <ColPopup v-model={selectStudentShow.value}>
+          <SelectStudents
+            ref={studentRef}
+            subjectList={subjectList.value}
+            onSetStudents={onSetStudents}
+          />
+        </ColPopup>
+
+        {/* 确认排课 */}
+        <Popup
+          position="bottom"
+          class={styles.coursePopup}
+          v-model:show={confirmShow.value}
+          closeable
+          round
+        >
+          <CourseSchedule
+            item={params}
+            students={students.value}
+            curriculum={curriculum.value}
+            onClose={() => {
+              confirmShow.value = false
+            }}
+            onComfirm={() => {
+              onCourseSchedule()
+            }}
+          />
+        </Popup>
+      </>
     )
   }
 })

+ 37 - 0
src/teacher/piano-room/class-arrangement/select-students/index.module.less

@@ -0,0 +1,37 @@
+.container {
+    background-color: #f7f8f9;
+    min-height: 100vh;
+    padding-bottom: 70px;
+    box-sizing: border-box;
+  }
+  .label {
+    margin-right: 8px;
+    font-size: 14px;
+    :global {
+      .van-list__loading,
+      .van-list__finished-text,
+      .van-list__error-text {
+        width: 100%;
+      }
+      .iconfont-down {
+        margin-left: 4px;
+      }
+    }
+  }
+  .btnGroup {
+    position: fixed;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    padding: 10px 28px;
+    background-color: #fff;
+    button{
+      height: 50px;
+      line-height: 50px;
+    }
+  }
+  .check {
+    display: flex;
+    justify-content: flex-end;
+  }
+  

+ 165 - 0
src/teacher/piano-room/class-arrangement/select-students/index.tsx

@@ -0,0 +1,165 @@
+import ColHeader from '@/components/col-header'
+import ColSearch from '@/components/col-search'
+import { Button, Checkbox, CheckboxGroup, Icon, Popup, Sticky } from 'vant'
+import {
+  defineComponent,
+  onMounted,
+  PropType,
+  reactive,
+  ref,
+  toRef,
+  toRefs,
+  watch
+} from 'vue'
+import styles from './index.module.less'
+import Student from '../../components/student'
+import request from '@/helpers/request'
+import OrganSearch from '@/student/practice-class/model/organ-search'
+import cleanDeep from 'clean-deep'
+
+export type IStudent = {
+  avatar: string
+  phone: number
+  realName: string
+  subjectName: string
+  userId: string
+  userName: string
+  checked: boolean
+}
+
+export default defineComponent({
+  name: 'SelectStudents',
+  props: {
+    subjectList: {
+      type: Array,
+      default: []
+    },
+    onSetStudents: {
+      type: Function,
+      default: (n: any) => {}
+    }
+  },
+  setup(props, { expose }) {
+    const show = ref(false)
+    const subjectName = ref('全部声部')
+
+    const params = reactive({
+      courseId: undefined, //课程id
+      subjectId: undefined, // 声部ID
+      userName: undefined as any // 学员姓名
+    })
+    const list = ref<IStudent[]>([])
+    const getList = async () => {
+      try {
+        const { code, data } = await request.post(
+          '/api-teacher/courseSchedule/selectStudent',
+          {
+            data: {
+              ...params
+            }
+          }
+        )
+        if (code === 200 && data.rows.length) {
+          data.rows.forEach((n: IStudent) => (n.checked = false))
+        
+          list.value = data.rows
+          console.log(list.value)
+        }
+      } catch (error) {}
+    }
+    onMounted(() => {
+      getList()
+    })
+    const onSearch = (val: string) => {
+      if (!val) return
+      params.userName = val
+      getList()
+    }
+    const toggle = (n: IStudent) => {
+      n.checked = !n.checked
+    }
+    const onDelete = (n: IStudent) => {
+      list.value.forEach((item: IStudent) => {
+        if (item.userId === n.userId) item.checked = false
+      })
+    }
+    expose({
+      onDelete
+    })
+    return () => (
+      <div class={styles.container}>
+        <Sticky offsetTop={0}>
+          <ColHeader isBack title="选择学员" />
+          <ColSearch
+            placeholder="请输入学员名称"
+            onSearch={onSearch}
+            v-slots={{
+              left: () => (
+                <div
+                  class={styles.label}
+                  onClick={() => {
+                    show.value = true
+                  }}
+                >
+                  {subjectName.value}
+                  <Icon
+                    classPrefix="iconfont"
+                    name="down"
+                    size={12}
+                    color="#333"
+                  />
+                </div>
+              )
+            }}
+          />
+        </Sticky>
+        {list.value.map(n => (
+          <div
+            onClick={() => {
+              toggle(n)
+            }}
+          >
+            <Student item={n}>
+              <div class={styles.check}>
+                <Checkbox v-model={n.checked} name={n.userId}></Checkbox>
+              </div>
+            </Student>
+          </div>
+        ))}
+        <div class={styles.btnGroup}>
+          <Button
+            block
+            round
+            class={styles.confirmBtn}
+            type="primary"
+            onClick={() => {
+              let stus = cleanDeep(list.value.filter(n => n.checked)) 
+              // console.log(stus)
+              props.onSetStudents(stus)
+            }}
+          >
+            确认
+          </Button>
+        </div>
+        <Popup
+          v-model:show={show.value}
+          position="bottom"
+          round
+          closeable
+          safe-area-inset-bottom
+        >
+          <OrganSearch
+            isReset
+            subjectList={props.subjectList}
+            v-model={params.subjectId}
+            v-model:subjectName={subjectName.value}
+            onSort={() => {
+              show.value = false
+              getList()
+            }}
+          />
+        </Popup>
+      </div>
+    )
+  }
+})

+ 9 - 3
src/teacher/piano-room/components/student/index.tsx

@@ -6,18 +6,24 @@ import iconStudent from '@common/images/icon_student.png'
 
 export default defineComponent({
   name: 'student',
+  props: {
+    item: {
+      type: Object,
+      default: {}
+    }
+  },
   render() {
     return (
       <Cell
         center
         class={styles.student}
         v-slots={{
-          icon: () => <img src={iconStudent} class={styles.studentImg} />,
+          icon: () => <img src={this.item.avatar || iconStudent} class={styles.studentImg} />,
           title: () => (
             <div class={styles.info}>
-              <p class={styles.studentName}>学生</p>
+              <p class={styles.studentName}>{this.item.userName}</p>
               <p>
-                <span class={styles.subject}>长笛</span>
+                <span class={styles.subject}>{this.item.subjectName}</span>
               </p>
             </div>
           )