skyblued 3 gadi atpakaļ
vecāks
revīzija
fe830c6cfa

+ 3 - 1
src/main.js

@@ -52,6 +52,7 @@ import {
   Empty,
   Empty,
   Uploader,
   Uploader,
   Stepper,
   Stepper,
+  SubmitBar
 } from "vant";
 } from "vant";
 Vue.use(Button)
 Vue.use(Button)
   .use(Icon)
   .use(Icon)
@@ -99,7 +100,8 @@ Vue.use(Button)
   .use(Calendar)
   .use(Calendar)
   .use(Empty)
   .use(Empty)
   .use(Uploader)
   .use(Uploader)
-  .use(Stepper);
+  .use(Stepper)
+  .use(SubmitBar);
 
 
 Vue.config.productionTip = false;
 Vue.config.productionTip = false;
 
 

+ 36 - 0
src/router/teacherRouter.js

@@ -344,6 +344,42 @@ let teacherRouter = [
     },
     },
   },
   },
   {
   {
+    path: "/activeCourseArrange",
+    name: "activeCourseArrange",
+    component: () =>
+      import(
+        /* webpackChunkName:'activeCourseArrange'*/ "@/views/activeCourseArrange/index.vue"
+      ),
+    meta: {
+      descrition: "活动排课",
+      weight: 3, // 页面权重
+    },
+  },
+  {
+    path: "/selectStudent",
+    name: "selectStudent",
+    component: () =>
+      import(
+        /* webpackChunkName:'selectStudent'*/ "@/views/activeCourseArrange/selectStudent.vue"
+      ),
+    meta: {
+      descrition: "选择学员",
+      weight: 3, // 页面权重
+    },
+  },
+  {
+    path: "/classSchedule",
+    name: "classSchedule",
+    component: () =>
+      import(
+        /* webpackChunkName:'classSchedule'*/ "@/views/activeCourseArrange/classSchedule.vue"
+      ),
+    meta: {
+      descrition: "排课详情",
+      weight: 3, // 页面权重
+    },
+  },
+  {
     path: "/activeDetail",
     path: "/activeDetail",
     name: "activeDetail",
     name: "activeDetail",
     component: () =>
     component: () =>

+ 8 - 2
src/store.js

@@ -5,10 +5,16 @@ Vue.use(Vuex)
 
 
 export default new Vuex.Store({
 export default new Vuex.Store({
   state: {
   state: {
-
+    activeStudents:[], // 活动排课学生
+    activeCourse:{}, //活动排课
   },
   },
   mutations: {
   mutations: {
-
+    commit_set_active_students:(state, students) => {
+      state.activeStudents = students
+    },
+    commit_set_active_course: (state, course) => {
+      state.activeCourse = course
+    }
   },
   },
   actions: {
   actions: {
 
 

+ 80 - 0
src/views/activeCourseArrange/api.js

@@ -0,0 +1,80 @@
+import request from '@/helpers/request'
+
+// 排课
+export const createVipCourse = async (data) => {
+    return request({
+        url: '/activityUserMapper/createVipGroup',
+        method: 'post',
+        data
+    })
+}
+// 排课 - 网管课
+export const createPracticeGroup = async (data) => {
+    return request({
+        url: '/activityUserMapper/createPracticeGroup',
+        method: 'post',
+        data
+    })
+}
+
+//获取活动排课详情
+export const getActiveCourseInfo = async (data) => {
+    return request({
+        url: '/activity/getActivityWaitCourseStudentNum?activityId=' + data,
+        method: 'get'
+    })
+}
+
+// 获取单课时长
+export const getActivityCourseTimeList = async (data) => {
+    return request({
+        url: '/activityUserMapper/queryActivityCourseTimeList',
+        method: 'post',
+        data
+    })
+}
+
+//获取声部列表
+export const getStuSubject = (data) => {
+    return request({
+        url: '/student/queryStuSubjectId',
+        method: 'get'
+    })
+}
+
+//学员列表筛选
+export const studentsFilter = async (data) => {
+    try {
+        const result = await request({
+            url: '/activityUserMapper/queryActivityStudentList',
+            method: 'post',
+            data
+        })
+        return result
+    } catch (error) {
+
+    }
+    return ''
+}
+
+// 获取所有活动类型
+export const getActivityUserMapper = async () => {
+    try {
+        const result = await request({
+            url: '/activityUserMapper/queryWaitCourseCategory',
+            method: 'post'
+        })
+        return result
+    } catch (error) {
+
+    }
+    return ''
+}
+
+//获取所有分部
+export const getAllOrganization = async () => {
+    return request({
+        url: '/organization/queryEmployeeOrgan',
+        method: 'get'
+    })
+}

+ 566 - 0
src/views/activeCourseArrange/classSchedule.vue

@@ -0,0 +1,566 @@
+<template>
+  <div class="courseArrangeClassSchedule">
+    <m-header :isBack="true" />
+    <van-cell-group title="学生">
+      <van-cell
+        v-for="stu in students"
+        :key="stu.userId"
+        :title="stu.username"
+        :value="stu.phone"
+      ></van-cell>
+    </van-cell-group>
+
+    <van-cell-group title="课程信息">
+      <!-- <van-cell
+        title="分部"
+        :value="formData.fenbu ? formData.fenbu.name : '请选择'"
+        is-link
+        @click="show.fenbu = true"
+      ></van-cell> -->
+      <van-cell
+        title="课程类型"
+        :value="activeCourse.name"
+      ></van-cell>
+      <!-- <van-cell
+        title="课程形式"
+        :value="activeCourse.name"
+      ></van-cell> -->
+      <van-cell title="排课声部" :value="activeCourse.subjectName"></van-cell>
+      <van-cell
+        title="乐团主管"
+        :value="educational.userId ? educational.realName : '请选择'"
+        is-link
+        @click="onShowEducational"
+      ></van-cell>
+    </van-cell-group>
+
+    <van-cell-group title="课程安排">
+      <van-cell
+        title="单课时时长"
+        :value="activeCourse.courseTime + '分钟'"
+      ></van-cell>
+      <van-cell title="可排课时" :value="courseNum"></van-cell>
+      <!-- <van-cell
+        v-if="activeCourse.coursesStartTime"
+        title="最早排课时间"
+        :value="activeCourse.coursesStartTime"
+      ></van-cell>
+      <van-cell
+        v-if="activeCourse.coursesEndTime"
+        title="最晚排课时间"
+        :value="activeCourse.coursesEndTime"
+      ></van-cell> -->
+      <van-field
+        label="线上课次数"
+        input-align="right"
+        placeholder="请输入排课次数"
+        type="digit"
+        v-model="frequency.online"
+        :formatter="formatterOnline"
+      ></van-field>
+      <van-field
+        v-if="this.activityId != 0"
+        label="线下课次数"
+        input-align="right"
+        placeholder="请输入排课次数"
+        type="digit"
+        v-model="frequency.offline"
+        :formatter="formatterOffline"
+      ></van-field>
+      <van-cell
+        v-if="frequency.offline > 0"
+        title="线下课地址"
+        :value="adddress.id ? adddress.name : '请选择'"
+        is-link
+        @click="onShowAddress"
+      ></van-cell>
+      <van-cell
+        title="排课开始时间"
+        :value="formatterDate(formData.time)"
+        is-link
+        @click="show.date = true"
+      ></van-cell>
+    </van-cell-group>
+
+    <van-cell-group style="margin: 12px 16px;">
+      <van-cell v-for="(item, index) in scheduleList" :key="index">
+        <template #title>
+          <div class="course-item">
+            <span>{{ item.type }}</span>
+            <span style="margin: 0 16px;">{{ item.weekStr }}</span>
+            <span>{{ item.startTime }} ~ {{ item.endTime }}</span>
+            <van-icon
+              class="clear-icon"
+              name="cross"
+              size="18"
+              @click="onDeleteSchedule(index)"
+            />
+          </div>
+        </template>
+      </van-cell>
+      <van-cell>
+        <template #title>
+          <div class="course-item" style="justify-content: center;">
+            <van-button size="small" icon="plus" @click="show.course = true"
+              >添加课时安排</van-button
+            >
+          </div>
+        </template>
+      </van-cell>
+    </van-cell-group>
+
+    <van-cell
+      title="排课列表"
+      value="查看"
+      is-link
+      @click="onShowCourseList"
+    ></van-cell>
+
+    <div style="padding: 10px 30px">
+      <van-button round block type="info" @click="onSubmit">确认</van-button>
+    </div>
+
+    <van-popup v-model="show.teacher" position="bottom">
+      <!-- <TeacherModal
+        @close="show.teacher = false"
+        @onSelectTeacher="onSelectTeacher"
+      /> -->
+    </van-popup>
+
+    <!-- 排课开始时间 -->
+    <van-popup v-model="show.date" position="bottom">
+      <van-datetime-picker
+        v-model="formData.time"
+        type="date"
+        @cancel="show.date = false"
+        @confirm="show.date = false"
+      />
+    </van-popup>
+
+    <!-- 课时安排 -->
+    <van-popup v-model="show.course" position="bottom">
+      <CourseModal
+        :scheduleList="scheduleList"
+        :singleClassMinutes="activeCourse.courseTime"
+        @close="show.course = false"
+      />
+    </van-popup>
+
+    <!-- 课表展示 -->
+    <van-popup v-model="show.timeTable" position="bottom">
+      <van-row>
+        <van-col span="12">上课类型</van-col>
+        <van-col span="12">上课时间</van-col>
+      </van-row>
+      <div class="tableContainer">
+        <van-row v-for="(item, index) in timeTable" :key="index">
+          <van-col span="12">
+            {{ item.teachMode == "ONLINE" ? "线上" : "线下" }}
+          </van-col>
+          <van-col span="12">
+            {{ item.classDate }} {{ item.startClassTimeStr }}
+          </van-col>
+        </van-row>
+      </div>
+    </van-popup>
+
+    <!-- 乐团主管 -->
+    <van-popup v-model="show.fenbu" position="bottom">
+      <van-picker
+        title="乐团主管"
+        :loading="educationalLoading"
+        :columns="educationalList"
+        value-key="userName"
+        show-toolbar
+        @cancel="show.fenbu = false"
+        @confirm="onSetEducational"
+      />
+    </van-popup>
+
+    <!-- 选择线下课地址 -->
+    <van-popup
+      v-model="show.address"
+      :lock-scroll="true"
+      position="bottom"
+      :style="{ height: '5.16rem', overflow: 'hidden' }"
+      class="studentChose"
+    >
+      <AddressList
+        @close="show.address = false"
+        :teacherId="teacher.id"
+        @submit="onSelectAddress"
+      />
+    </van-popup>
+  </div>
+</template>
+
+<script>
+import AddressList from "./modal/addressList.vue";
+// import TeacherModal from "../applyActive/modal/teacher-modal";
+import CourseModal from "./modal/course";
+import MHeader from "../../components/MHeader.vue";
+import { findSubSubjects, findEducationUsers } from "@/api/teacher";
+import {
+  createPracticeGroup,
+  createVipCourse,
+  getActiveCourseInfo,
+} from "./api.js";
+import dayjs from "dayjs";
+export default {
+  name: "classSchedule",
+  components: { CourseModal, AddressList, MHeader},
+  data() {
+    return {
+      activityId: this.$route.query.activityId,
+      show: {
+        fenbu: false,
+        teacher: false,
+        date: false,
+        course: false,
+        timeTable: false,
+        address: false,
+      },
+      students: [],
+      activeCourse: {},
+
+      frequency: {
+        // 线上,线下排课
+        online: "",
+        offline: "",
+      },
+
+      formData: {
+        time: new Date(),
+        fenbu: "",
+        fullOrganId: "",
+      },
+      teacher: {},
+      adddress: {},
+      educational: {},
+
+      scheduleList: [], // 课时安排
+      timeTable: [], // 生成的课表
+      allBranch: [], // 分部
+      educationalList: [], //乐团主管
+      educationalLoading: false
+    };
+  },
+  computed: {
+    courseNum() {
+      const mins = this.students.map((n) => parseInt(n.subCourseNum));
+      return Math.min(...mins);
+    },
+  },
+  async mounted() {
+    this.students = this.$store.state.activeStudents;
+    this.activeCourse = this.$store.state.activeCourse;
+    // await this.$store.dispatch("setAllBranch");
+    // this.allBranch = this.$store.state.allBranch;
+    // if (this.allBranch.length) {
+    //   this.formData.fenbu = this.allBranch[0];
+    // }
+  },
+  methods: {
+    onSelectTeacher(val) {
+      this.teacher = {
+        id: val.id,
+        realName: val.realName,
+      };
+    },
+
+    onSetBranch(val) {
+      // 设置分部
+      console.log(val);
+      this.formData.fenbu = val;
+      this.show.fenbu = false;
+    },
+
+    formatterOnline(val) {
+      // 线上排课次数
+      const n = this.courseNum - this.frequency.offline || 0;
+      if (val > n) {
+        return n;
+      }
+      return val;
+    },
+    formatterOffline(val) {
+      // 线下排课次数
+      const n = this.courseNum - this.frequency.online || 0;
+      if (val > n) {
+        return n;
+      }
+      return val;
+    },
+    formatterDate(date) {
+      return dayjs(date).format("YYYY-MM-DD");
+    },
+
+    onDeleteSchedule(index) {
+      // 删除课时安排
+      this.scheduleList.splice(index, 1);
+    },
+
+    async onShowEducational(){ // 显示乐团主管
+      this.show.fenbu = true
+      if (this.educationalList.length) return 
+      this.educationalLoading = true
+      try {
+        const {data} = await findEducationUsers()
+        // console.log(data)
+        if (data.code == 200 && data.data && data.data.EDUCATION) {
+          const EDUCATION = data.data.EDUCATION || [];
+            this.educationalList = EDUCATION;
+        } else {
+            this.$toast("暂无乐团主管");
+        }
+      } catch (error) {
+        
+      }
+      this.educationalLoading = false
+    },
+
+    onSetEducational(val){
+      this.educational = val
+      this.show.fenbu = false;
+    },
+
+    onShowAddress() {
+      this.show.address = true;
+    },
+
+    onSelectAddress(val) {
+      // 设置线下课地址
+      this.adddress = val;
+      this.show.address = false;
+    },
+
+    onShowCourseList(isShowPopup = true) {
+      // 展示排课列表
+      if (!this.scheduleList.length) {
+        return this.$toast("缺少课时安排");
+      }
+      const online = parseInt(this.frequency.online || 0);
+      const offline = parseInt(this.frequency.offline || 0);
+
+      if (!online && !offline) {
+        return this.$toast("缺少线上或线下上课次数");
+      }
+      if (online + offline > this.courseNum) {
+        return this.$toast("线上 + 线下 总课时不能大于可排课次数");
+      }
+      if (online && !this.scheduleList.filter((n) => n.type == "线上").length) {
+        return this.$toast("课时安排缺少线上课类型");
+      }
+      if (
+        offline &&
+        !this.scheduleList.filter((n) => n.type == "线下").length
+      ) {
+        return this.$toast("课时安排缺少线下课类型");
+      }
+
+      if (offline && !this.adddress.id) {
+        this.$toast("请选择线下教学地址");
+        return;
+      }
+
+      if (!this.formData.time) {
+        return this.$toast("缺少排课开始时间");
+      }
+
+      this.setTimeTable();
+      isShowPopup && (this.show.timeTable = true);
+    },
+
+    setTimeTable() {
+      // 重置排课列表
+      let timeTable = [];
+      let online = parseInt(this.frequency.online);
+      let offline = parseInt(this.frequency.offline);
+      let scheduleList = this.scheduleList;
+      let totalCount = parseInt(online || 0) + parseInt(offline || 0);
+      let dateOperation = new Date(this.formData.time);
+      // console.log(dateOperation.toLocaleDateString());
+      let forMark = 0;
+      // console.log('totalCount',totalCount)
+      while (totalCount && totalCount > 0) {
+        for (let i = 0; i < scheduleList.length; i++) {
+          if (online == 0 && offline == 0) break;
+          let num = scheduleList[i].weekIndex - dateOperation.getDay();
+          // 如果是同一天一个周期会出现排课都排到一天
+          if (forMark > 0 && num == 0 && i == 0) {
+            num = num + 7;
+          }
+          if (num < 0) {
+            // 如果为负数则为下周
+            num = num + 7;
+          }
+          let dataStr = this.getThinkDate(dateOperation, num);
+          // 判断是否大于当前时间
+          let nowGetTime = new Date().getTime();
+          let courseTime = new Date(
+            dataStr.replace(/-/gi, "/") +
+              " " +
+              scheduleList[i].startTime +
+              ":00"
+          ).getTime();
+          // console.log(nowGetTime < courseTime)
+          if (nowGetTime < courseTime) {
+            let tempArr = {
+              classDate: dataStr,
+              startClassTimeStr: scheduleList[i].startTime,
+              endClassTimeStr: scheduleList[i].endTime,
+            };
+            if (scheduleList[i].type == "线上" && online > 0) {
+              tempArr.teachMode = "ONLINE";
+              timeTable.push(tempArr);
+              online--;
+
+              totalCount--;
+            } else if (scheduleList[i].type == "线下" && offline > 0) {
+              tempArr.teachMode = "OFFLINE";
+              timeTable.push(tempArr);
+              offline--;
+
+              totalCount--;
+            }
+          }
+        }
+        // 加一周
+        if (scheduleList.length == 1) {
+          dateOperation.setDate(dateOperation.getDate() + 7);
+        } else if (
+          scheduleList.every((item) => item.weekStr === scheduleList[0].weekStr)
+        ) {
+          // 标记循环次数(标记判断课程安排是不是同一天)
+          forMark++;
+        }
+      }
+
+      timeTable.sort((a, b) => {
+        let aStr = dayjs(
+          dayjs(a.classDate).format("YYYY-MM-DD") +
+            " " +
+            a.startClassTimeStr +
+            ":00"
+        ).valueOf();
+        let bStr = dayjs(
+          dayjs(b.classDate).format("YYYY-MM-DD") +
+            " " +
+            b.startClassTimeStr +
+            ":00"
+        ).valueOf();
+        return aStr - bStr;
+      });
+      this.timeTable = timeTable;
+    },
+
+    getThinkDate(date, num) {
+      let Stamp = date;
+      Stamp.setDate(date.getDate() + num); // 获取当前月数的第几天
+      return dayjs(Stamp).format("YYYY-MM-DD");
+    },
+
+    async onSubmit() {
+      if (!this.educational.userId) {
+        this.$toast("选择乐团主管");
+        return;
+      }
+      // 排课
+      this.onShowCourseList(false);
+      if (!this.timeTable.length) return;
+      const online = parseInt(this.frequency.online || 0)
+      const offline = parseInt(this.frequency.offline || 0)
+      const total = online + offline
+      let params = {
+        courseSchedules: this.timeTable,
+        vipGroupApplyBaseInfo: {
+          vipGroupCategoryId: this.activityId,
+          subjectId: this.activeCourse.subjectId,
+          subjectIdList: this.activeCourse.subjectId,
+          singleClassMinutes: this.activeCourse.courseTime,
+          totalClassTime: total,
+          allCourseNum: total,
+          studentNum: this.students.length,
+          coursesStart: dayjs(this.formData.time).format("YYYY-MM-DD"),
+          organId: this.formData.fenbu.id,
+          studentIdList: this.students.map((n) => n.userId).join(","),
+          studentId:this.students.map((n) => n.userId).join(","),
+          onlineClassesNum: online,
+          offlineClassesNum: offline,
+          userId: this.teacher.id,
+        },
+      };
+      if (params.vipGroupApplyBaseInfo.offlineClassesNum) {
+        // 教学点
+        params.vipGroupApplyBaseInfo.teacherSchoolId = this.adddress.id;
+      }
+      // console.log(params)
+      if (this.activityId == 0) {
+        params.practiceGroupApplyBaseInfoDto = params.vipGroupApplyBaseInfo
+        delete params.vipGroupApplyBaseInfo
+        let res = await createPracticeGroup(params);
+        if (res.code == 200) {
+          this.$toast("排课成功");
+          setTimeout(() => {
+            history.go(-2);
+          }, 1000);
+        }
+      } else {
+        let res = await createVipCourse(params);
+        if (res.code == 200) {
+          this.$toast("排课成功");
+          setTimeout(() => {
+            history.go(-2);
+          }, 1000);
+        }
+      }
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.course-item {
+  display: flex;
+  align-content: center;
+  .clear-icon {
+    display: flex;
+    align-items: center;
+    margin-left: auto;
+  }
+}
+</style>
+
+<style lang="less">
+.courseArrangeClassSchedule {
+  .van-cell__value,
+  .van-field__label {
+    color: #323233;
+  }
+}
+.van-row {
+  line-height: 0.4rem;
+  border-top: 1px solid #edeef0;
+  text-align: center;
+  font-size: 0.14rem;
+
+  &:first-child {
+    border-top: 0;
+    background: #edeef0;
+    color: #444;
+    font-size: 0.15rem;
+  }
+}
+.tableContainer {
+  max-height: 2.44rem;
+  overflow: auto;
+  .van-row {
+    color: #444;
+    &:first-child {
+      border-top: 0;
+      background: #fff;
+      font-size: 0.14rem;
+    }
+  }
+}
+</style>

+ 131 - 0
src/views/activeCourseArrange/components/drop-select.vue

@@ -0,0 +1,131 @@
+<template>
+  <div>
+    <div class="checkboxs">
+      <van-cell
+        clickable
+        v-for="o in list"
+        :key="o.value"
+        :title="o.text"
+        @click="o.checked = !o.checked"
+      >
+        <template #right-icon>
+          <van-checkbox :value="o.checked" :name="o.value" />
+        </template>
+      </van-cell>
+    </div>
+    <van-cell class="selectStudent-action-cell" center>
+      <template #title>
+        <van-checkbox
+          style="margin-right: 15px"
+          :value="checkAll"
+          @click="setCheckAll"
+          >全选<span v-if="getSelectedLength">(已选 {{getSelectedLength}})</span>
+        </van-checkbox>
+      </template>
+      <van-button size="small" round block style="margin-right: 10px;" @click="onreSet"
+        >重置</van-button
+      >
+      <van-button size="small" round block type="info" @click="onFilter"
+        >确定</van-button
+      >
+    </van-cell>
+    <!-- <van-goods-action>
+      <van-checkbox
+        style="margin-right: 15px"
+        :value="checkAll"
+        @click="setCheckAll"
+        >全选</van-checkbox
+      >
+
+      <van-goods-action-button type="warning" text="加入购物车" />
+      <van-goods-action-button
+        type="danger"
+        text="立即购买"
+        @click="onFilter"
+      />
+    </van-goods-action> -->
+  </div>
+</template>
+
+<script>
+export default {
+  name: "dropSelect",
+  props: {
+    time: {
+      type: Number,
+      default: 0,
+    },
+    options: {
+      type: Array,
+      default: () => [],
+    },
+  },
+  watch: {
+    time() {
+      console.log(1);
+    },
+  },
+  data() {
+    return {
+      list: [],
+    };
+  },
+  computed: {
+    checkAll() {
+      if (this.list) {
+        const total = this.list.filter((n) => n.checked).length;
+        if (total === 0) return false;
+        return total === this.list.length;
+      }
+      return false;
+    },
+
+    getSelectedLength(){
+      return this.list.filter((n) => n.checked).length
+    }
+  },
+  mounted() {
+    this.list = this.options.map((n) => {
+      return {
+        ...n,
+      };
+    });
+  },
+  methods: {
+    setCheckAll() {
+      const checkAll = this.checkAll;
+      this.list.forEach((n) => (n.checked = !checkAll));
+    },
+
+    setList() {
+      this.list = this.options.map((n) => {
+        return {
+          ...n,
+        };
+      });
+    },
+
+    onFilter() {
+      this.$emit("onFilter", this.list);
+    },
+
+    onreSet(){
+      this.list.forEach((n) => (n.checked = false));
+    }
+  },
+};
+</script>
+
+<style lang="less">
+.selectStudent-action-cell {
+  box-shadow: 0 -2px 12px rgba(100, 101, 102, 0.12);
+  .van-cell__title {
+    flex: inherit;
+    width: 1.3rem;
+  }
+  .van-cell__value {
+    margin: 0 auto;
+    display: flex;
+  }
+}
+</style>

+ 64 - 0
src/views/activeCourseArrange/index.vue

@@ -0,0 +1,64 @@
+<template>
+  <div class="activeCourseArrangIndex">
+    <m-header :isBack="true" />
+    <div>
+      <van-cell
+        :title="n.name"
+        is-link
+        center
+        v-for="n in list"
+        :key="n.id"
+        @click="onTo(n)"
+      >
+        <van-tag slot="icon" type="primary" style="margin-right: 10px;">{{n.name}}</van-tag>
+        <span slot="label"
+          >未排课学生<span style="color: red"> {{ n.num }} </span>名</span
+        >
+      </van-cell>
+    </div>
+  </div>
+</template>
+
+<script>
+import MHeader from "../../components/MHeader.vue";
+import { getActivityUserMapper } from "./api";
+export default {
+  components: { MHeader },
+  name: "activeCourseArrangIndex",
+  data() {
+    return {
+      list: [],
+    };
+  },
+  mounted() {
+    let params = this.$route.query;
+    if (params.Authorization) {
+        localStorage.setItem("Authorization", decodeURI(params.Authorization));
+        localStorage.setItem("userInfo", decodeURI(params.Authorization));
+    }
+    this.init();
+    this.$store.commit("commit_set_active_course", {});
+    this.$store.commit("commit_set_active_students", []);
+  },
+  methods: {
+    async init() {
+      const result = await getActivityUserMapper();
+      if (result && result.data) {
+        this.list = result.data;
+      }
+    },
+    onTo(n) {
+      const category = {
+        ...this.$store.state.activeCourse,
+        ...n,
+      };
+      this.$store.commit("commit_set_active_course", category);
+      this.$router.push(
+        `/selectStudent?categoryId=${n.id}&studentNum=${n.studentNum || 2}`
+      );
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 227 - 0
src/views/activeCourseArrange/modal/addressList.vue

@@ -0,0 +1,227 @@
+<template>
+  <div class="containers">
+    <search @onSearch="onSearch" placeholder="请输入地址名称" />
+    <div class="paddingB80">
+      <div
+        class="studentContainer"
+        v-if="dataShow"
+      >
+        <!-- <div class="studentContainer" v-if="dataShow" key="data"> -->
+        <van-radio-group v-model="radioSelect">
+          <van-cell-group :border="false">
+            <van-cell
+              v-for="(item, index) in dataList"
+              :key="index"
+              @click="onRadioSelect(item)"
+              class="input-cell"
+              value-class="value-times"
+              :center="true"
+            >
+              <template slot="title">
+                {{ item.text }}
+              </template>
+              <template slot="extra">
+                <van-radio :name="item.value">
+                  <template #icon="props">
+                    <img
+                      v-if="!item.radioDisabled"
+                      class="img-icon"
+                      :src="
+                        props.checked ? activeButtonIcon : inactiveButtonIcon
+                      "
+                    />
+                  </template>
+                </van-radio>
+              </template>
+            </van-cell>
+          </van-cell-group>
+        </van-radio-group>
+        <!-- </div> -->
+      </div>
+      <m-empty class="empty" msg="暂无地址" v-else key="data" />
+    </div>
+    <div class="button-group-popup">
+      <span class="btn" @click="onPopupCancel">取消</span>
+      <span class="btn primary" @click="onPopupSubmit">确定</span>
+    </div>
+    <div class="wall"></div>
+  </div>
+</template>
+
+<script>
+import MEmpty from "@/components/MEmpty";
+import search from "@/components/Search";
+import { findVipSchoolByTeacher2 } from "@/api/teacher";
+export default {
+  components: {
+    MEmpty,
+    search,
+  },
+  props: [
+    "teacherId"
+  ],
+  data() {
+    return {
+      params: {
+        search: null,
+      },
+      dataShow: true, // 是否有数据
+      dataList: [],
+      radioSelect: null,
+      container: null,
+    };
+  },
+  watch: {
+    teacherId(newVal, oldVal) {
+      if(newVal != oldVal) {
+        this.getAddress();
+      }
+    }
+  },
+  mounted() {
+    this.getAddress();
+  },
+  methods: {
+    // 搜索
+    onSearch(val) {
+      this.params.search = val
+      this.getAddress()
+    },
+    async getAddress() {
+      let params = this.params;
+      params.userId = this.teacherId
+      // 教师教学点
+      this.$toast.loading({
+          duration: 0, // 持续展示 toast
+          forbidClick: true,
+          message: '加载中...',
+      })
+      await findVipSchoolByTeacher2(params).then((res) => {
+          this.$toast.clear()
+          let result = res.data;
+          if (result.code == 200 && result.data.length > 0) {
+              let tempArr = [];
+              result.data.forEach((item) => {
+                  item.value = item.id;
+                  item.text = item.name;
+                  tempArr.push(item);
+              });
+              this.dataList = tempArr
+              this.dataShow = tempArr.length > 0 ? true : false
+          } else {
+            this.dataShow = false
+          }
+      });
+      this.$toast.clear()
+    },
+    onRadioSelect(value) {
+      this.radioSelect = value.value
+    },
+    onPopupCancel() {
+      this.$listeners.close();
+    },
+    onPopupSubmit() {
+      let dataList = this.dataList || []
+      let selectItem = {}
+      dataList.forEach(item => {
+        if(item.id == this.radioSelect) {
+          selectItem = item
+        }
+      })
+      this.$emit("submit", selectItem);
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+@import url("../../../assets/commonLess/variable.less");
+.containers {
+  overflow: hidden;
+}
+.studentContainer {
+  min-height: 3.78rem;
+  height: 3.78rem;
+  padding-top: 0.1rem;
+  background-color: #fff;
+  overflow-y: scroll;
+  /deep/.van-cell__title {
+    font-size: 0.16rem;
+    color: @mFontColor;
+    // flex: 1 auto;
+  }
+
+  .logo {
+    width: 0.35rem;
+    height: 0.35rem;
+    margin-right: 0.12rem;
+    border-radius: 100%;
+  }
+
+  .input-cell {
+    padding: 0.12rem 0.16rem 0.2rem;
+
+    .van-checkbox {
+      justify-content: flex-end;
+    }
+  }
+
+  /deep/.van-cell__value {
+    height: 0.2rem;
+  }
+
+  /deep/.van-checkbox__icon .van-icon {
+    border-color: @sFontColor;
+  }
+  /deep/.van-checkbox__icon--checked .van-icon {
+    border-color: #01c1b5;
+  }
+
+  .van-tag {
+    margin-left: 0.08rem;
+  }
+}
+.value-times {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+.paddingB80 {
+  padding-bottom: 0.8rem;
+  overflow: hidden;
+}
+.wall {
+  content: "";
+  height: 0.62rem;
+  position: relative;
+}
+.button-group-popup {
+  position: fixed;
+  bottom: 0;
+  padding: 0.1rem 0;
+  width: 100%;
+  text-align: center;
+  background-color: #ffffff;
+  .btn {
+    padding: 0 0.45rem;
+    line-height: 0.4rem;
+    display: inline-block;
+    border: 1px solid @mColor;
+    border-radius: 1rem;
+    color: @mColor;
+    background: #fff;
+    font-size: 0.18rem;
+    &.primary {
+      color: #fff;
+      background: @mColor;
+    }
+  }
+  .btn + .btn {
+    margin-left: 0.1rem;
+  }
+}
+
+.van-search--show-action {
+  padding-right: 12px;
+}
+</style>

+ 160 - 0
src/views/activeCourseArrange/modal/course.vue

@@ -0,0 +1,160 @@
+<template>
+    <van-picker
+        :columns="columns"
+        show-toolbar
+        @cancel="$listeners.close()"
+        @confirm="onTeachingConfirm"
+    />
+</template>
+
+<script>
+let minutes = []; // 分钟数
+for (let i = 0; i < 60; i++) {
+    let mi = i < 10 ? "0" + i : i;
+    minutes.push(mi + "分");
+}
+export default {
+    props: ['scheduleList', 'singleClassMinutes'],
+    data() {
+        return {
+            // 排课弹窗
+            columns: [{
+                // 课程选项
+                values: ["线上", "线下"],
+                className: "type",
+            }, {
+                values: ["周一", "周二", "周三", "周四", "周五", "周六", "周日"],
+                className: "week",
+            }, {
+                values: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23],
+                className: "hours",
+                defaultIndex: 7,
+            }, {
+                values: minutes,
+                className: "minutes",
+            }],
+        }
+    },
+    watch: {
+        'singleClassMinutes'() {
+            this.scheduleList = []
+        }
+    },
+    methods: {
+        onTeachingConfirm(value) {
+            // 添加课程
+            let scheduleList = this.scheduleList;
+            let startTime =
+                (value[2] >= 10 ? value[2] : "0" + value[2]) +
+                ":" +
+                value[3].split("分")[0];
+            let endTime = this.MinutesTest(
+                value[2],
+                value[3],
+                this.singleClassMinutes
+            );
+
+            let isAdd = true;
+            scheduleList.forEach((item) => {
+                let isStartTime = this.timeIsRange(startTime, endTime, item.startTime);
+                let isEndTime = this.timeIsRange(startTime, endTime, item.endTime);
+                if (isAdd) {
+                    if (value[1] == item.weekStr) {
+                        if (isStartTime || isEndTime) {
+                            isAdd = false;
+                        } else {
+                            isAdd = true;
+                        }
+                    } else if (value[1] != item.weekStr) {
+                        isAdd = true;
+                    }
+                }
+            });
+
+            // 判断结束时间
+            const endTime1 = endTime ? Number(endTime.split(":")[0]) : 0
+            const endTime2 = endTime ? Number(endTime.split(":")[1]) : 0
+            if(endTime1 >= 21 && endTime2 > 0) {
+                this.$toast("课程安排结束时间不可超过21:00");
+                return;
+            }
+
+            if (isAdd) {
+                // 判断时间范围是否有重复
+                scheduleList.push({
+                    type: value[0], // 线上还是线下
+                    weekStr: value[1],
+                    weekIndex: this.getWeek(value[1]),
+                    startTime: startTime,
+                    endTime: endTime,
+                    id: Date.now(),
+                });
+
+                // this.courseForm.teachingStatus = false;
+                this.$listeners.close()
+
+                // this.setTimeTable();
+            } else {
+                this.$toast("该时间段已排课请重选时间");
+                return;
+            }
+        },
+        // 分钟小时相加减
+        MinutesTest(hour, mins, interval) {
+            let min = mins.split("分")[0];
+            let sDate1 = new Date(1900, 1, 1, hour, min);
+            sDate1.setMinutes(sDate1.getMinutes() + parseInt(interval));
+            let H = sDate1.getHours();
+            let M = sDate1.getMinutes();
+            if (H < 10) H = "0" + H;
+            if (M < 10) M = "0" + M;
+            return H + ":" + M;
+        },
+        // 判断时间是否在时间段内
+        timeIsRange(beginTime, endTime, nowTime) {
+            var strB = beginTime.split(":");
+            if (strB.length != 2) {
+                return false;
+            }
+            var strE = endTime.split(":");
+            if (strE.length != 2) {
+                return false;
+            }
+            var strN = nowTime.split(":");
+            if (strE.length != 2) {
+                return false;
+            }
+            var b = new Date();
+            var e = new Date();
+            var n = new Date();
+            b.setHours(strB[0]);
+            b.setMinutes(strB[1]);
+            e.setHours(strE[0]);
+            e.setMinutes(strE[1]);
+            n.setHours(strN[0]);
+            n.setMinutes(strN[1]);
+
+            if (n.getTime() - b.getTime() >= 0 && n.getTime() - e.getTime() <= 0) {
+                // 在时间范围内
+                return true;
+            } else {
+                // 不在时间范围内
+                return false;
+            }
+        },
+        getWeek(str) {
+            // 获取周几索引值
+            let template = {
+                '周一': 1,
+                '周二': 2,
+                '周三': 3,
+                '周四': 4,
+                '周五': 5,
+                '周六': 6,
+                '周日': 0,
+            };
+            return template[str];
+        },
+    }
+}
+</script>

+ 399 - 0
src/views/activeCourseArrange/selectStudent.vue

@@ -0,0 +1,399 @@
+<template>
+  <div class="selectStudent">
+    
+    <div class="selectStudentWrap">
+      <m-header :isBack="true" />
+      <van-search
+        class="searchWrap"
+        shape="round"
+        show-action
+        v-model="search"
+        @search="onSearch()"
+      >
+        <template #action>
+          <div @click="onSearch()">筛选</div>
+        </template>
+      </van-search>
+      <van-dropdown-menu class="selectStudent-dropdown" active-color="#01C1B5">
+        <!-- <van-dropdown-item
+          title="分部"
+          ref="fenbu"
+          @open="onOpenDropdown('fenbuDrop')"
+        >
+          <template slot="default">
+            <DropSelect
+              :options="option1"
+              @onFilter="onFilter1"
+              ref="fenbuDrop"
+            />
+          </template>
+        </van-dropdown-item> -->
+        <van-dropdown-item
+          title="声部"
+          ref="dropdown"
+          @close="onOpenDropdown('dropdownDrop')"
+        >
+          <template slot="default">
+            <DropSelect
+              :options="option2"
+              @onFilter="onFilter2"
+              ref="dropdownDrop"
+            />
+          </template>
+        </van-dropdown-item>
+        <van-dropdown-item
+          v-if="options3.length"
+          v-model="value3"
+          :options="options3"
+          @change="getStudents"
+        ></van-dropdown-item>
+      </van-dropdown-menu>
+    </div>
+
+    <div class="students">
+      <van-cell
+        v-for="(stu, index) in studentsData"
+        :key="stu.value"
+        :title="stu.text"
+        :label="stu.subjectName"
+        center
+        clickable
+        @click="onSelectStudent(index)"
+      >
+        <img
+          v-if="stu.avatar"
+          slot="icon"
+          class="stuAvatar"
+          :src="stu.avatar"
+        />
+        <img
+           v-else 
+           slot="icon"
+          class="stuAvatar"
+        />
+        <div class="students-right">
+          <span style="margin-left: auto;margin-right: 20px;"
+            >{{ stu.subCourseNum }}课时</span
+          >
+          <van-checkbox :value="stu.checked"></van-checkbox>
+        </div>
+      </van-cell>
+      <MEmpty v-if="!loading && !studentsData.length" :msg="'暂无学员'" />
+    </div>
+
+    <div style="height: 50px;"></div>
+
+    <van-submit-bar
+      button-text="下一步"
+      button-type="info"
+      class="selectStudent-submit-bar"
+      @submit="onSubmit"
+      :disabled="!selectStudents.length"
+    >
+      <van-dropdown-menu active-color="#01C1B5" direction="up">
+        <van-dropdown-item>
+          <template #title>
+            已选学生
+            <span style="color: red">{{ selectStudents.length }}</span> 名
+          </template>
+          <template slot="default">
+            <div class="submit-bar-popup-title">已选学生</div>
+            <van-cell
+              v-for="student in selectStudents"
+              :key="student.value"
+              :title="student.text"
+              :label="student.subjectName"
+              center
+            >
+              <img
+                v-if="student.avatar"
+                slot="icon"
+                class="stuAvatar"
+                :src="student.avatar"
+              />
+              <van-icon v-else slot="icon" name="manager" />
+              <van-icon name="clear" size="25" @click="onDelete(student)" />
+            </van-cell>
+          </template>
+        </van-dropdown-item>
+      </van-dropdown-menu>
+    </van-submit-bar>
+  </div>
+</template>
+
+<script>
+import {
+  getAllOrganization,
+  studentsFilter,
+  getStuSubject,
+  getActivityCourseTimeList,
+} from "./api";
+import DropSelect from "./components/drop-select.vue";
+import MEmpty from "../../components/MEmpty.vue";
+import MHeader from "../../components/MHeader.vue";
+export default {
+  name: "selectStudent",
+  components: { DropSelect, MEmpty,MHeader },
+  data() {
+    return {
+      activityId: this.$route.query.categoryId,
+      search: "",
+      option1: [],
+      option2: [],
+      studentsData: [],
+      selectStudents: [],
+      value3: 0,
+      options3: [],
+      loading: true,
+    };
+  },
+  mounted() {
+    this.selectStudents = this.$store.state.activeStudents;
+    this.init();
+  },
+  methods: {
+    async init() {
+      Promise.all([
+        getAllOrganization(),
+        getStuSubject(),
+        getActivityCourseTimeList({
+          categoryId: this.$route.query.categoryId,
+        }),
+      ]).then((values) => {
+        
+        const res = values[0];
+        const subjects = values[1];
+        const courseList = values[2];
+        if (res && res.data && Array.isArray(res.data)) {
+          this.option1 = res.data.map((n) => {
+            return {
+              text: n.name,
+              value: n.id,
+              checked: true,
+            };
+          });
+        }
+
+        if (subjects && subjects.data && Array.isArray(subjects.data)) {
+          this.option2 = subjects.data.filter(n => n).map((n) => {
+            return {
+              text: n.value,
+              value: n.key,
+              checked: true,
+            };
+          });
+        }
+
+        if (courseList && courseList.data && Array.isArray(courseList.data)) {
+          this.options3 = courseList.data.map((n, index) => {
+            if (index === 0) {
+              this.value3 = n;
+            }
+            return {
+              text: `单课时长${n}分钟`,
+              value: n,
+            };
+          });
+        }
+        this.getStudents();
+        console.log(values)
+      });
+    },
+    async getStudents() {
+      this.loading = true;
+      const subjectIds = this.option2
+        .map((n) => (n.checked ? n.value : undefined))
+        .filter((n) => n);
+      const organIds = this.option1
+        .map((n) => (n.checked ? n.value : undefined))
+        .filter((n) => n);
+      // 获取学员列表
+      const res = await studentsFilter({
+        categoryId: this.$route.query.categoryId,
+        search: this.search,
+        singleCourseTime: this.value3, // 单课时长
+        subjectIds: subjectIds.join(",") || undefined, // 声部
+        organIds: organIds.join(",") || undefined, // 分部
+      });
+      if (res && res.data && Array.isArray(res.data)) {
+        this.studentsData = res.data.map((n) => {
+          n.checked =
+            this.selectStudents.filter((s) => s.value == n.userId).length > 0
+              ? true
+              : false;
+          n.text = n.username;
+          n.value = n.userId;
+          return {
+            ...n,
+          };
+        });
+      }
+      this.loading = false;
+    },
+    onSearch() {
+      // 搜索
+      this.getStudents();
+    },
+    onFilter1(list) {
+      this.option1 = list.map((n) => {
+        return { ...n };
+      });
+      this.getStudents();
+      this.$refs.fenbu.toggle(false);
+    },
+    onFilter2(list) {
+      this.option2 = list.map((n) => {
+        return { ...n };
+      });
+      this.getStudents();
+      this.$refs.dropdown.toggle(false);
+    },
+    onOpenDropdown(key) {
+      // 筛选弹框
+      this.$nextTick(() => {
+        this.$refs[key].setList();
+      });
+    },
+    //选择学生
+    onSelectStudent(index) {
+      const i = this.selectStudents.findIndex(
+        (n) => n.value == this.studentsData[index].value
+      );
+
+      if (i > -1) {
+        if (this.studentsData[index].checked) {
+          this.selectStudents.splice(i, 1);
+          this.studentsData[index].checked = false;
+        }
+      } else {
+        const studentNum = parseInt(this.$route.query.studentNum);
+        if (studentNum && this.selectStudents.length == studentNum) {
+          this.$toast("已到达该类型课程学生人数上限");
+          return;
+        }
+        this.studentsData[index].checked = true;
+        this.selectStudents.push({
+          ...this.studentsData[index],
+        });
+      }
+    },
+    // 删除已选学生
+    onDelete(student) {
+      const index = this.selectStudents.findIndex(
+        (n) => n.value == student.value
+      );
+      const stu = this.selectStudents.find((n) => n.value == student.value);
+      this.selectStudents.splice(index, 1);
+      if (stu) {
+        const studentIndex = this.studentsData.findIndex(
+          (n) => n.value == student.value
+        );
+        if (studentIndex > -1) {
+          this.studentsData[studentIndex].checked = false;
+        }
+      }
+    },
+
+    onSubmit() {
+      // 下一步
+      const studentNum = parseInt(this.$route.query.studentNum);
+      if (this.$store.state.activeCourse.name == "乐理课") {
+        if(this.selectStudents.length > studentNum){
+          this.$toast("已到达该类型课程学生人数上限");
+          return;
+        }
+      } else {
+        if (studentNum != this.selectStudents.length) {
+          this.$toast("所选学生人数不足");
+          return;
+        }
+      }
+      
+      let subject = {
+        id: null,
+        name: null,
+      };
+      for (let i = 0; i < this.selectStudents.length; i++) {
+        if (!subject.id) {
+          subject.id = this.selectStudents[i].subjectId;
+          subject.name = this.selectStudents[i].subjectName;
+          continue;
+        }
+        if (subject.id != this.selectStudents[i].subjectId) {
+          this.$toast("所选学生声部不一致");
+          return;
+        }
+      }
+
+      const category = {
+        ...this.$store.state.activeCourse,
+        subjectId: subject.id,
+        subjectName: subject.name,
+        courseTime: this.value3,
+      };
+      this.$store.commit("commit_set_active_course", category);
+      this.$store.commit("commit_set_active_students", this.selectStudents);
+      this.$router.push(`/classSchedule?activityId=${this.activityId}`);
+    },
+  },
+};
+</script>
+
+<style>
+body{
+  background: #f3f4f8;
+}
+</style>
+
+<style lang="less" scoped>
+
+.searchWrap {
+  position: relative;
+  z-index: 100;
+}
+.selectStudentWrap {
+  position: sticky;
+  top: 0;
+  z-index: 1000;
+}
+.students {
+  padding: 12px 0;
+}
+.students-right {
+  display: flex;
+  align-content: center;
+}
+.submit-bar-popup-title {
+  text-align: center;
+  padding: 10px 0;
+  font-weight: bold;
+}
+</style>
+<style lang="less">
+.selectStudent-dropdown {
+  .van-dropdown-menu__bar {
+    // justify-content: space-around;
+  }
+  .van-dropdown-menu__item {
+    // flex: inherit;
+  }
+  .checkboxs {
+    max-height: calc(60vh - 50px);
+    overflow-y: auto;
+  }
+}
+.selectStudent-submit-bar {
+  .van-submit-bar__bar {
+    justify-content: space-between;
+  }
+  .van-dropdown-menu__bar {
+    box-shadow: none;
+  }
+}
+.stuAvatar {
+  width: 0.5rem;
+  height: 0.5rem;
+  margin-right: 10px;
+  border-radius: 50%;
+}
+</style>