Sfoglia il codice sorgente

feat: migrate to event mode

Casper Dai 3 anni fa
parent
commit
100ee7dcf4
28 ha cambiato i file con 864 aggiunte e 1934 eliminazioni
  1. 1 2
      .eslintrc.js
  2. 6 10
      src/api/calendar.js
  3. 12 36
      src/api/publish.js
  4. 12 1
      src/components/ConfirmDialog/index.vue
  5. 0 0
      src/components/Schedule/ScheduleCalendar/EventItem.vue
  6. 0 0
      src/components/Schedule/ScheduleCalendar/EventItemWeek.vue
  7. 0 62
      src/components/Schedule/ScheduleCalendar/PopDetail.vue
  8. 18 25
      src/components/Schedule/ScheduleCalendar/PopList.vue
  9. 0 78
      src/components/Schedule/ScheduleCalendar/ProgramItem.vue
  10. 39 35
      src/components/Schedule/ScheduleCalendar/ScheduleCalendarMonth.vue
  11. 0 0
      src/components/Schedule/ScheduleCalendar/ScheduleCalendarWeek.vue
  12. 0 0
      src/components/Schedule/ScheduleCalendar/ScheduleComplexEdit.vue
  13. 0 174
      src/components/Schedule/ScheduleCalendar/ScheduleEdit.vue
  14. 103 513
      src/components/Schedule/ScheduleCalendar/index.vue
  15. 0 51
      src/components/Schedule/ScheduleComplex/PopList.vue
  16. 0 181
      src/components/Schedule/ScheduleComplex/ScheduleCalendarMonth.vue
  17. 0 733
      src/components/Schedule/ScheduleComplex/index.vue
  18. 0 0
      src/components/Schedule/ScheduleSwiper/ProgramChoose.vue
  19. 5 5
      src/components/Schedule/ScheduleSwiper/index.vue
  20. 2 4
      src/components/Schedule/index.vue
  21. 325 0
      src/components/Schedule/mixins/calendar.js
  22. 204 0
      src/components/Schedule/mixins/event.js
  23. 5 8
      src/components/Schedule/mixins/schedule.js
  24. 1 1
      src/constant.js
  25. 8 3
      src/router/index.js
  26. 1 1
      src/utils/event.js
  27. 121 8
      src/views/platform/transfer/deploy/index.vue
  28. 1 3
      vue.config.js

+ 1 - 2
.eslintrc.js

@@ -15,8 +15,7 @@ module.exports = {
     __DEV__: true,
     __PLACEHOLDER__: true,
     __SENSOR__: true,
-    __CAMERA__: true,
-    __SUPPORT_COMPLEX__: true
+    __CAMERA__: true
   },
   // add your custom rules here
   //it is base on https://github.com/vuejs/eslint-config-vue

+ 6 - 10
src/api/calendar.js

@@ -25,12 +25,12 @@ export function getSchedules (query) {
 
 export function getSchedule (id, options) {
   return request({
-    url: `/content/${__SUPPORT_COMPLEX__ ? 'programCalendar' : 'calendar'}/${id}`,
+    url: `/content/programCalendar/${id}`,
     method: 'GET',
     ...options
   }).then(({ data }) => {
     const { id, type, status, name, resolutionRatio, eventDetail } = data
-    return { id, type, status, name, resolutionRatio, events: __SUPPORT_COMPLEX__ ? JSON.parse(eventDetail) : eventDetail }
+    return { id, type, status, name, resolutionRatio, events: JSON.parse(eventDetail) }
   })
 }
 
@@ -49,14 +49,10 @@ export function deleteSchedule ({ id, name }) {
   }, name)
 }
 
-const httpEnum = {
-  [ScheduleType.CALENDAR]: 'singleEventList',
-  [ScheduleType.RECUR]: 'recurEventList'
-}
 export function saveScheduleEvents (schedule, events) {
-  const { id, type } = schedule
+  const { id } = schedule
   return messageSend({
-    url: `/content/calendar/${id}/${__SUPPORT_COMPLEX__ ? 'eventList' : httpEnum[type]}`,
+    url: `/content/calendar/${id}/eventList`,
     method: 'POST',
     data: events
   }, '保存')
@@ -106,10 +102,10 @@ export function submitSchedule ({ id, type, name }, events) {
       resolve(events)
     } else {
       send({
-        url: `/content/${__SUPPORT_COMPLEX__ ? 'programCalendar' : 'calendar'}/${id}`,
+        url: `/content/programCalendar/${id}`,
         method: 'GET'
       }).then(({ data }) => {
-        resolve(__SUPPORT_COMPLEX__ ? JSON.parse(data.eventDetail) : data.eventDetail)
+        resolve(JSON.parse(data.eventDetail))
       }, reject)
     }
   }).then(events => {

+ 12 - 36
src/api/publish.js

@@ -1,29 +1,10 @@
 import request from '@/utils/request'
 import {
-  confirm,
   messageSend,
   resolve,
   reject
 } from './base'
 import { toEvent } from '@/utils/event'
-import { ScheduleType } from '@/constant'
-
-export function publishSchedule ({ id, type, name }, devices, options) {
-  return confirm(`确定对设备 ${devices.map(device => device.name)} 发布排期 ${name}?`).then(() => {
-    const data = {
-      programCalendarId: id,
-      deviceIds: devices.map(device => device.id)
-    }
-    if (type === ScheduleType.RECUR) {
-      Object.assign(data, options)
-    }
-    return messageSend({
-      url: '/orchestration/calendarRelease',
-      method: 'POST',
-      data
-    }, '发布')
-  })
-}
 
 export function publish (deviceIds, target, options) {
   return messageSend({
@@ -37,12 +18,10 @@ export function publish (deviceIds, target, options) {
   }, '发布')
 }
 
-const api = __SUPPORT_COMPLEX__ ? 'calendarReleaseSchedu' : 'calendarRelease'
-
 export function getPublishes (query) {
   const { pageNum: pageIndex, pageSize, ...params } = query
   return request({
-    url: `/orchestration/${api}/page`,
+    url: '/orchestration/calendarReleaseSchedu/page',
     method: 'GET',
     params: {
       pageIndex, pageSize,
@@ -53,7 +32,7 @@ export function getPublishes (query) {
 
 export function resolvePublish ({ id, name }) {
   return resolve({
-    url: `/orchestration/${api}/${id}/approval`,
+    url: `/orchestration/calendarReleaseSchedu/${id}/approval`,
     method: 'POST',
     data: { remark: '' }
   }, name)
@@ -61,25 +40,22 @@ export function resolvePublish ({ id, name }) {
 
 export function rejectPublish ({ id, name }, remark) {
   return reject({
-    url: `/orchestration/${api}/${id}/reject`,
+    url: `/orchestration/calendarReleaseSchedu/${id}/reject`,
     method: 'POST',
     data: { remark }
   }, name)
 }
 
 export function getPublishHistory (query) {
-  if (__SUPPORT_COMPLEX__) {
-    const { pageNum: pageIndex, pageSize, ...params } = query
-    return request({
-      url: '/orchestration/calendarReleaseHis/page',
-      method: 'GET',
-      params: {
-        pageIndex, pageSize,
-        ...params
-      }
-    })
-  }
-  return getPublishes(query)
+  const { pageNum: pageIndex, pageSize, ...params } = query
+  return request({
+    url: '/orchestration/calendarReleaseHis/page',
+    method: 'GET',
+    params: {
+      pageIndex, pageSize,
+      ...params
+    }
+  })
 }
 
 export function getTimeline (deviceId, options) {

+ 12 - 1
src/components/ConfirmDialog/index.vue

@@ -1,7 +1,7 @@
 <template>
   <el-dialog
     :visible.sync="dialogVisible"
-    custom-class="c-dialog mini"
+    :custom-class="className"
     :close-on-click-modal="false"
     :before-close="onCancel"
     v-bind="$attrs"
@@ -27,11 +27,22 @@
 <script>
 export default {
   name: 'ConfirmDialog',
+  props: {
+    size: {
+      type: String,
+      default: 'mini'
+    }
+  },
   data () {
     return {
       dialogVisible: false
     }
   },
+  computed: {
+    className () {
+      return `c-dialog ${this.size}`
+    }
+  },
   methods: {
     show () {
       this.dialogVisible = true

+ 0 - 0
src/components/Schedule/ScheduleComplex/EventItem.vue → src/components/Schedule/ScheduleCalendar/EventItem.vue


+ 0 - 0
src/components/Schedule/ScheduleComplex/EventItemWeek.vue → src/components/Schedule/ScheduleCalendar/EventItemWeek.vue


+ 0 - 62
src/components/Schedule/ScheduleCalendar/PopDetail.vue

@@ -1,62 +0,0 @@
-<template>
-  <el-popover
-    trigger="hover"
-    placement="right"
-  >
-    <div class="c-pop-detail">
-      <div class="u-ellipsis">{{ name }}</div>
-      <div class="c-pop-detail__time">{{ time }}</div>
-      <div class="c-pop-detail__desc">{{ desc }}</div>
-    </div>
-    <template #reference>
-      <slot />
-    </template>
-  </el-popover>
-</template>
-
-<script>
-export default {
-  name: 'PopDetail',
-  props: {
-    program: {
-      type: Object,
-      default: null
-    }
-  },
-  computed: {
-    name () {
-      return this.program.name
-    },
-    time () {
-      return `${this.program.startDateTime.split(' ')[0]} - ${this.program.endDateTime.split(' ')[0]}`
-    },
-    isSingle () {
-      return this.program.repeatType === 1
-    },
-    desc () {
-      return `${this.program.startDateTime.split(' ')[1]} - ${this.program.endDateTime.split(' ')[1]}`
-    }
-  }
-}
-</script>
-
-<style lang="scss" scoped>
-.c-pop-detail {
-  display: inline-block;
-  width: 256px;
-  font-size: 18px;
-  font-weight: bold;
-  text-align: center;
-  color: $black;
-
-  &__time {
-    margin-top: 10px;
-  }
-
-  &__desc {
-    margin-top: 6px;
-    color: $gray;
-    font-size: 12px;
-  }
-}
-</style>

+ 18 - 25
src/components/Schedule/ScheduleCalendar/PopList.vue

@@ -1,18 +1,18 @@
 <template>
-  <el-popover trigger="hover">
-    <div class="c-pop-list">
-      <pop-detail
-        v-for="(program, index) in list"
-        :key="index"
-        :program="program"
-      >
-        <program-item
-          class="c-pop-list__item"
-          :program="program"
-          v-bind="$attrs"
-          v-on="$listeners"
-        />
-      </pop-detail>
+  <el-popover
+    trigger="hover"
+    :close-delay="0"
+  >
+    <div class="l-flex--col c-pop-list">
+      <event-item
+        v-for="event in list"
+        :key="event.key"
+        class="c-pop-list__item"
+        :event="event"
+        fill
+        v-bind="$attrs"
+        v-on="$listeners"
+      />
     </div>
     <template #reference>
       <slot />
@@ -21,24 +21,22 @@
 </template>
 
 <script>
-import ProgramItem from './ProgramItem'
-import PopDetail from './PopDetail'
+import EventItem from './EventItem'
 
 export default {
   name: 'PopList',
   components: {
-    ProgramItem,
-    PopDetail
+    EventItem
   },
   props: {
-    programs: {
+    events: {
       type: Array,
       default: null
     }
   },
   computed: {
     list () {
-      return this.programs ? this.programs.slice(1) : []
+      return this.events ? this.events.slice(1) : []
     }
   }
 }
@@ -49,10 +47,5 @@ export default {
   width: 200px;
   max-height: 200px;
   overflow-y: auto;
-
-  &__item {
-    padding: 6px 30px;
-    width: 100%;
-  }
 }
 </style>

+ 0 - 78
src/components/Schedule/ScheduleCalendar/ProgramItem.vue

@@ -1,78 +0,0 @@
-<template>
-  <div
-    class="c-program-item u-ellipsis u-pointer"
-    :class="{ active: editable }"
-    @click="onClick"
-  >
-    {{ program.name }}
-    <i
-      class="c-program-item__icon el-icon-close"
-      @click.stop="remove"
-    />
-  </div>
-</template>
-
-<script>
-export default {
-  name: 'ProgramItem',
-  props: {
-    program: {
-      type: Object,
-      default: null
-    },
-    editable: {
-      type: [Boolean, String],
-      default: false
-    }
-  },
-  methods: {
-    onClick () {
-      if (this.editable) {
-        this.$emit('edit', this.program)
-      } else {
-        window.open(this.$router.resolve({
-          name: 'view',
-          params: { id: this.program.id }
-        }).href, '_blank')
-      }
-    },
-    remove () {
-      this.$emit('remove', this.program)
-    }
-  }
-}
-</script>
-
-<style lang="scss" scoped>
-.c-program-item {
-  display: inline-block;
-  position: relative;
-  padding: 6px 8px;
-  color: #fff;
-  font-size: 14px;
-  line-height: 1;
-  text-align: center;
-  border-radius: 4px;
-  background-color: $blue;
-
-  &__icon {
-    display: none;
-    position: absolute;
-    justify-content: center;
-    align-items: center;
-    top: 50%;
-    right: 0;
-    width: 30px;
-    height: 100%;
-    transform: translateY(-50%);
-  }
-
-  &.active:hover {
-    padding-right: 30px;
-
-    i {
-      display: inline-flex;
-    }
-  }
-}
-</style>

+ 39 - 35
src/components/Schedule/ScheduleCalendar/ScheduleCalendarMonth.vue

@@ -1,6 +1,6 @@
 <template>
-  <div class="l-flex--col">
-    <div class="l-flex__none l-flex c-schedule-calendar__header">
+  <div class="l-flex--col c-month-calendar">
+    <div class="l-flex__none l-flex c-month-calendar__header">
       <div class="l-flex__auto u-readonly">周日</div>
       <div class="l-flex__auto u-readonly">周一</div>
       <div class="l-flex__auto u-readonly">周二</div>
@@ -9,44 +9,40 @@
       <div class="l-flex__auto u-readonly">周五</div>
       <div class="l-flex__auto u-readonly">周六</div>
     </div>
-    <div class="c-schedule-calendar__content">
+    <div class="c-month-calendar__content">
       <div
         v-for="(week, weekIndex) in weeks"
         :key="weekIndex"
-        class="l-flex c-schedule-calendar__row"
+        class="l-flex c-month-calendar__row"
       >
         <div
           v-for="(day, dayIndex) in week"
           :key="dayIndex"
-          class="c-schedule-calendar__col day c-day-card"
-          :class="{ 'enabled': day.editable, 'disabled': day.disabled, 'invalid': day.invalid }"
-          @dblclick="addProgramByDay(day)"
+          class="c-month-calendar__col day c-day-card"
+          :class="day.status"
+          @click="switchTo(day)"
+          @dblclick="onAdd(day)"
         >
           <span class="c-day-card__day">{{ day.value }}</span>
           <div
-            v-if="day.events.length > 0"
+            v-if="day.events.length"
             class="l-flex--row"
           >
-            <pop-detail
-              class="l-flex__auto"
-              :program="day.events[0]"
-            >
-              <program-item
-                class="c-day-card__program"
-                :program="day.events[0]"
-                :editable="editable"
-                @edit="editProgram"
-                @remove="removeProgram"
-              />
-            </pop-detail>
+            <event-item
+              class="c-day-card__program"
+              :event="day.events[0]"
+              :editable="editable"
+              @edit="$listeners.edit"
+              @remove="$listeners.remove"
+            />
           </div>
           <pop-list
             v-if="day.events.length > 1"
             class="l-flex__auto"
-            :programs="day.events"
+            :events="day.events"
             :editable="editable"
-            @edit="editProgram"
-            @remove="removeProgram"
+            @edit="$listeners.edit"
+            @remove="$listeners.remove"
           >
             <div class="c-day-card__count u-ellipsis u-pointer">
               +{{ day.events.length - 1 }}
@@ -59,15 +55,15 @@
 </template>
 
 <script>
-import ProgramItem from './ProgramItem'
-import PopDetail from './PopDetail'
+import { EventFreq } from '@/constant'
+import { toDateStr } from '@/utils/event'
+import EventItem from './EventItem'
 import PopList from './PopList'
 
 export default {
   name: 'ScheduleCalendarMonth',
   components: {
-    ProgramItem,
-    PopDetail,
+    EventItem,
     PopList
   },
   props: {
@@ -81,21 +77,29 @@ export default {
     }
   },
   methods: {
-    addProgramByDay (day) {
-      this.$emit('add', day)
-    },
-    editProgram (item) {
-      this.$emit('edit', item)
+    onAdd (day) {
+      if (day.status === 'enabled') {
+        this.$emit('add', {
+          freq: EventFreq.ONCE,
+          start: `${toDateStr(day.date)} 00:00:00`,
+          until: `${toDateStr(day.date, 1)} 00:00:00`
+        })
+      }
     },
-    removeProgram (item) {
-      this.$emit('remove', item)
+    switchTo (day) {
+      if (day.status === 'disabled') {
+        this.$emit('switch', day.date)
+      }
     }
   }
 }
 </script>
 
 <style lang="scss" scoped>
-.c-schedule-calendar {
+.c-month-calendar {
+  color: $black;
+  font-size: 16px;
+
   &__header {
     padding: 10px 0;
     font-weight: bold;

+ 0 - 0
src/components/Schedule/ScheduleComplex/ScheduleCalendarWeek.vue → src/components/Schedule/ScheduleCalendar/ScheduleCalendarWeek.vue


+ 0 - 0
src/components/Schedule/ScheduleComplex/ScheduleComplexEdit.vue → src/components/Schedule/ScheduleCalendar/ScheduleComplexEdit.vue


+ 0 - 174
src/components/Schedule/ScheduleCalendar/ScheduleEdit.vue

@@ -1,174 +0,0 @@
-<template>
-  <div class="c-form">
-    <div class="c-form__section">
-      <span class="c-form__label">播放模式:</span>
-      <el-select
-        v-model="repeatType"
-        class="c-form__item"
-        placeholder="请选择"
-      >
-        <el-option
-          v-for="option in repeatTypeOptions"
-          :key="option.value"
-          :label="option.label"
-          :value="option.value"
-        />
-      </el-select>
-    </div>
-    <div class="c-form__section">
-      <el-date-picker
-        v-model="range"
-        type="datetimerange"
-        start-placeholder="开始时间"
-        range-separator="至"
-        end-placeholder="结束时间"
-        value-format="yyyy-MM-dd HH:mm:ss"
-        :default-time="['00:00:00', '23:59:59']"
-        :picker-options="pickerOptions"
-        :editable="false"
-        @change="onChange"
-      />
-    </div>
-    <div class="c-form__section">
-      <div
-        class="o-program-name u-ellipsis u-pointer"
-        @click="toBind"
-      >
-        {{ programName }}
-      </div>
-    </div>
-    <el-dialog
-      :visible.sync="choosing"
-      title="选择节目"
-      custom-class="c-dialog hidden-footer"
-      append-to-body
-    >
-      <program-choose
-        v-if="choosing"
-        v-bind="$attrs"
-        @choose="toChoose"
-      />
-    </el-dialog>
-  </div>
-</template>
-
-<script>
-import { parseTime } from '@/utils'
-import ProgramChoose from '../components/ProgramChoose'
-
-export default {
-  name: 'ScheduleEdit',
-  components: {
-    ProgramChoose
-  },
-  props: {
-    program: {
-      type: Object,
-      default: null
-    },
-    minDate: {
-      type: Object,
-      default: null
-    },
-    maxDate: {
-      type: Object,
-      default: null
-    }
-  },
-  data () {
-    return {
-      choosing: false,
-      repeatTypeOptions: [
-        { value: 1, label: '单次' }
-      ],
-      repeatType: 1,
-      range: [],
-      bindTo: null
-    }
-  },
-  computed: {
-    isSingle () {
-      return this.repeatType === 1
-    },
-    isWeekly () {
-      return this.repeatType === 2
-    },
-    pickerOptions () {
-      return {
-        disabledDate: this.isDisableDate
-      }
-    },
-    programName () {
-      return this.bindTo ? this.bindTo.name : '点击选择节目'
-    },
-    minDateRange () {
-      const { year, month, day } = this.minDate
-      return new Date(year, month, day)
-    },
-    maxDateRange () {
-      if (!this.maxDate) {
-        return null
-      }
-      const { year, month, day } = this.maxDate
-      return new Date(year, month, day)
-    }
-  },
-  watch: {
-    program: {
-      handler () {
-        this.init()
-      },
-      immediate: true
-    }
-  },
-  methods: {
-    onChange () {
-      if (this.range) {
-        const now = Date.now()
-        const nowDate = parseTime(now, '{y}-{m}-{d} {h}:{i}:{s}')
-        if (new Date(this.range[0]) < now) {
-          this.range[0] = nowDate
-        }
-        if (new Date(this.range[1]) < now) {
-          this.range[1] = nowDate
-        }
-      }
-    },
-    init () {
-      const { id, name, startDateTime, endDateTime } = this.program
-      this.range = startDateTime ? [startDateTime, endDateTime] : []
-      this.bindTo = id ? { id, name } : null
-    },
-    isDisableDate (date) {
-      return date < this.minDateRange || this.maxDateRange && date > this.maxDateRange
-    },
-    toBind () {
-      this.choosing = true
-    },
-    toChoose ({ id, name }) {
-      this.choosing = false
-      this.bindTo = { id, name }
-    },
-    getValue () {
-      return {
-        ...this.bindTo,
-        startDateTime: this.range?.[0],
-        endDateTime: this.range?.[1]
-      }
-    }
-  }
-}
-</script>
-
-<style lang="scss" scoped>
-.o-program-name {
-  width: 300px;
-  padding: $spacing;
-  color: $blue;
-  font-size: 16px;
-  line-height: 1;
-  text-align: center;
-  border-radius: 4px;
-  border: 1px solid $gray;
-}
-</style>

+ 103 - 513
src/components/Schedule/ScheduleCalendar/index.vue

@@ -5,7 +5,7 @@
     :hide-header="hideHeader"
     :editable="editable"
     :dirty="dirty"
-    @add="addProgram"
+    @add="onAdd"
     @submit="submit"
     @save="onSave"
   >
@@ -13,561 +13,137 @@
       <button
         class="l-flex__none o-button c-schedule-calendar__toggle"
         :class="{ show: canToToday }"
-        @click="today"
+        @click="onToday"
       >
         今日
       </button>
       <div class="l-flex__fill l-flex--row center">
         <i
           class="c-schedule-calendar__toggle c-schedule-calendar__btn el-icon-arrow-left c-sibling-item u-pointer"
-          :class="{ show: canToPresent }"
-          @click="toPresent"
+          :class="{ show: canToPrevious }"
+          @click="onPrevious"
         />
         <span class="c-schedule-calendar__time u-readonly">{{ date }}</span>
         <i
           class="c-schedule-calendar__toggle c-schedule-calendar__btn el-icon-arrow-right u-pointer"
           :class="{ show: canToNext }"
-          @click="toNext"
+          @click="onNext"
         />
       </div>
+      <div class="l-flex__none l-flex--row c-schedule-calendar__mode u-pointer">
+        <div
+          class="c-schedule-calendar__type"
+          :class="{ active: mode === 0 }"
+          @click="onChangeToWeek"
+        >
+          周
+        </div>
+        <div
+          class="c-schedule-calendar__type"
+          :class="{ active: mode === 1 }"
+          @click="onChangeToMonth"
+        >
+          月
+        </div>
+      </div>
     </div>
-    <schedule-calendar-month
-      class="l-flex__auto c-schedule-calendar__main"
-      :weeks="weeks"
+    <component
+      :is="activeComponent"
+      class="c-schedule-calendar__main"
       :editable="editable"
-      @add="addProgramByDay"
-      @edit="editProgram"
-      @remove="removeProgram"
+      :weeks="weeks"
+      :cursor="weekIndex"
+      @add="onAdd"
+      @edit="onEditProxy"
+      @remove="onRemove"
+      @switch="toDay"
     />
-    <el-dialog
-      title="排期设置"
-      :visible.sync="editing"
-      custom-class="c-dialog"
-      :close-on-click-modal="false"
-      :before-close="onCloseEditDialog"
+    <confirm-dialog
+      ref="editDialog"
+      title="事件设置"
+      @confirm="onSaveEvent"
     >
       <schedule-edit
-        v-if="program"
+        v-if="event"
         ref="editor"
-        :program="program"
+        :event="event"
         :ratio="ratio"
-        :min-date="min"
-        :max-date="max"
       />
-      <template #footer>
-        <button
-          class="o-button"
-          @click="saveProgram"
-        >
-          确定
-        </button>
-        <button
-          class="o-button cancel"
-          @click="onCloseEditDialog"
-        >
-          取消
-        </button>
-      </template>
-    </el-dialog>
-    <el-dialog
+    </confirm-dialog>
+    <confirm-dialog
+      ref="conflictDialog"
       title="冲突提醒"
-      :visible.sync="conflicting"
-      custom-class="c-dialog"
-      :close-on-click-modal="false"
       append-to-body
+      @confirm="onCover"
     >
-      <div class="has-bottom-padding u-bold">
-        <div>局部覆盖:被冲突的节目的时间段将被切割</div>
-        <div>覆盖:被冲突的节目将被移除</div>
-      </div>
       <div
         v-for="conflict in conflicts"
         :key="conflict.key"
+        class="o-conflict"
       >
-        与 {{ conflict.target.name }} 于 {{ conflict.start }} ~ {{ conflict.end }} 冲突
+        {{ conflict.info }}
+        <div class="o-conflict__desc">{{ conflict.desc }}</div>
       </div>
-      <template #footer>
-        <button
-          class="o-button"
-          @click="_coverConflicts"
-        >
-          局部覆盖
-        </button>
-        <button
-          class="o-button"
-          @click="_coverConflictsFull"
-        >
-          覆盖
-        </button>
-        <button
-          class="o-button cancel"
-          @click="conflicting = false"
-        >
-          取消
-        </button>
-      </template>
+    </confirm-dialog>
+    <el-dialog
+      :visible.sync="isPreviewing"
+      custom-class="c-preview schedule"
+      :before-close="onClosePreviewDialog"
+      append-to-body
+    >
+      <schedule
+        v-if="scheduleId"
+        class="l-flex__auto has-padding"
+        :schedule="scheduleId"
+      />
     </el-dialog>
   </schedule-wrapper>
 </template>
 
 <script>
-import { parseTime } from '@/utils'
-import scheduleMixin from '../mixins/schedule'
+import { ScheduleType } from '@/constant'
+import {
+  toEvent,
+  toDate
+} from '@/utils/event'
+import calendarMixin from '../mixins/calendar'
 import ScheduleWrapper from '../components/ScheduleWrapper'
+import ScheduleCalendarWeek from './ScheduleCalendarWeek'
 import ScheduleCalendarMonth from './ScheduleCalendarMonth'
-import ScheduleEdit from './ScheduleEdit'
+import ScheduleEdit from './ScheduleComplexEdit'
 
 export default {
-  name: 'ScheduleCalendar',
+  name: 'ScheduleComplex',
   components: {
     ScheduleWrapper,
+    ScheduleCalendarWeek,
     ScheduleCalendarMonth,
     ScheduleEdit
   },
-  mixins: [scheduleMixin],
-  data () {
-    return {
-      weeks: [],
-      min: null,
-      max: null,
-      current: null,
-      editing: false,
-      program: null,
-      dirty: false,
-      conflicting: false,
-      conflicts: []
-    }
-  },
-  computed: {
-    date () {
-      return this.current ? `${this.current.year}.${this.current.month + 1}` : ''
-    },
-    canToPresent () {
-      return this.current ? this._compare(this.current, this.min, false) > 0 : false
-    },
-    canToNext () {
-      return this.current && this.max ? this._compare(this.current, this.max, false) < 0 : !!this.current
-    },
-    canToToday () {
-      if (!this.current) {
-        return false
-      }
-      if (this.editable) {
-        return this._compare(this.current, this._transform(Date.now()), false) !== 0
-      }
-      if (this.min && this.max) {
-        const today = this._transform(Date.now())
-        return this._compare(this.min, today, false) <= 0 && this._compare(this.max, today, false) >= 0
-      }
-      return false
-    }
-  },
+  mixins: [calendarMixin],
   methods: {
-    init () {
-      const now = this._transform(Date.now())
-      let current = {
-        year: now.year,
-        month: now.month
-      }
-      if (this.editable) {
-        this._setMinDate()
-        if (this.events.length) {
-          const minDate = this._transform(this.events[0].startDateTime)
-          if (this._compare(now, minDate, false) < 0) {
-            current = {
-              year: minDate.year,
-              month: minDate.month
-            }
-          }
-        }
-      } else {
-        if (this.events.length) {
-          this.min = this._transform(this.events[0].startDateTime)
-          this.max = this.events.reduce((curr, { endDateTime }) => {
-            const date = this._transform(endDateTime)
-            if (curr) {
-              return this._compare(curr, date) < 0 ? date : curr
-            }
-            return date
-          }, null)
-        } else {
-          this.max = this.min = now
-        }
-        if (this._compare(now, this.max, false) > 0) {
-          current = {
-            year: this.min.year,
-            month: this.min.month
-          }
-        }
-      }
-      this.current = current
-      this._calculate()
-    },
-    fix () {
-      let endTime = Date.now() + 60000
-      if (this.events.some(({ endDateTime }) => new Date(endDateTime).getTime() <= endTime)) {
-        return this.$confirm(
-          '存在过期或将要过期(<=60s)节目,是否移除?',
-          {
-            type: 'warning',
-            distinguishCancelAndClose: true,
-            confirmButtonText: '移除',
-            cancelButtonText: '保留'
-          }
-        ).then(
-          () => {
-            endTime = Date.now() + 60000
-            this.scheduleOptions.events = this.events.filter(({ endDateTime }) => new Date(endDateTime).getTime() > endTime)
-            if (this._compare(this.current, this._transform(endTime), false) <= 0) {
-              this._calculate()
+    transformEvents (events, type) {
+      switch (type) {
+        case ScheduleType.CALENDAR:
+          return events.map(event => this.createEventProxy(toEvent(event))).sort((a, b) => toDate(a.origin.start) - toDate(b.origin.start))
+        default:
+          return events.sort((a, b) => toDate(a.start) - toDate(b.start)).map(this.createEventProxy)
+      }
+    },
+    getEvents () {
+      switch (this.scheduleOptions.type) {
+        case ScheduleType.CALENDAR:
+          return this.events.map(({ origin: event }) => {
+            const { start, until, target: { id, name } } = event
+            return {
+              programId: id,
+              programName: name,
+              startDateTime: start,
+              endDateTime: until
             }
-          },
-          action => {
-            if (action === 'cancel') {
-              return Promise.reject()
-            }
-          }
-        )
-      }
-      return Promise.resolve()
-    },
-    onSave () {
-      this.fix().then(this.save).then(() => { this.dirty = false })
-    },
-    _transformEvents (events) {
-      return events.sort(this._sortEvents)
-    },
-    _sortEvents (a, b) {
-      return new Date(a.startDateTime).getTime() - new Date(b.startDateTime).getTime()
-    },
-    _transformEvent ({ programId, programName, startDateTime, endDateTime }) {
-      // 服务器保存的时间采用左闭右开原则
-      return this._createEvent(programId, programName, startDateTime, this._offsetDateTime(endDateTime, -1))
-    },
-    _getEvents () {
-      return this.events.map(({ id, name, startDateTime, endDateTime }) => {
-        endDateTime = this._offsetDateTime(endDateTime, 1)
-        return {
-          programId: id,
-          programName: name,
-          startDateTime, endDateTime
-        }
-      })
-    },
-    _createEvent (id, name, startDateTime, endDateTime) {
-      return {
-        id, name, startDateTime, endDateTime,
-        key: Math.random().toString(16).slice(2),
-        days: this._getDays(startDateTime, endDateTime)
-      }
-    },
-    _offsetDateTime (dateTime, val) {
-      return parseTime(new Date(dateTime).getTime() + val * 1000, '{y}-{m}-{d} {h}:{i}:{s}')
-    },
-    _getDays (startDateTime, endDateTime) {
-      const days = {}
-      const { year: endYear, month: endMonth, day: endDay } = this._transform(endDateTime)
-      const end = `${endYear}-${endMonth + 1}-${endDay}`
-      let { year, month, day } = this._transform(startDateTime)
-      let start = `${year}-${month + 1}-${day}`
-      while (start !== end) {
-        days[start] = 1
-        ;({ year, month, day } = this._transform(new Date(year, month, day + 1)))
-        start = `${year}-${month + 1}-${day}`
-      }
-      days[start] = 1
-      return days
-    },
-    removeProgram ({ key, name }) {
-      this.$confirm(
-        `确定移除节目 ${name}`,
-        { type: 'warning' }
-      ).then(() => {
-        this.dirty = true
-        this._removeProgram(key)
-        this._calculate()
-      })
-    },
-    _removeProgram (key) {
-      if (!key) {
-        return
-      }
-      const index = this.events.findIndex(event => event.key === key)
-      this.events.splice(index, 1)
-      if (index === 0) {
-        this._setMinDate()
-        if (this._compare(this.current, this.min, false) < 0) {
-          this.current = {
-            year: this.min.year,
-            month: this.min.month
-          }
-        }
-      }
-    },
-    _setMinDate () {
-      let minDate = this._transform(Date.now())
-      if (this.events.length) {
-        const minProgramDate = this._transform(this.events[0].startDateTime)
-        if (this._compare(minDate, minProgramDate, false) > 0) {
-          minDate = minProgramDate
-        }
-      }
-      if (!this.min || this._compare(this.min, minDate) < 0) {
-        this.min = minDate
-      }
-    },
-    addProgram () {
-      this.editProgram({})
-    },
-    addProgramByDay (day) {
-      if (day.editable) {
-        const { year, month } = this.current
-        const date = day.value
-        this.editProgram({
-          startDateTime: `${year}-${(month + 1).toString().padStart(2, '0')}-${(date).toString().padStart(2, '0')} ${this._compare({ year, month, day: date }, this._transform(Date.now())) === 0 ? parseTime(new Date(), '{h}:{i}:{s}') : '00:00:00'}`,
-          endDateTime: `${year}-${(month + 1).toString().padStart(2, '0')}-${(date).toString().padStart(2, '0')} 23:59:59`
-        })
-      }
-    },
-    editProgram (program) {
-      if (this.editable) {
-        this.program = program
-        this.editing = true
-      }
-    },
-    saveProgram () {
-      const { id, name, startDateTime, endDateTime } = this.$refs.editor.getValue()
-      if (!startDateTime || !endDateTime) {
-        this.$message({
-          type: 'warning',
-          message: '请选择生效时间'
-        })
-        return
-      }
-      if (!id) {
-        this.$message({
-          type: 'warning',
-          message: '请选择节目'
-        })
-        return
-      }
-      if (this.program.id &&
-        id === this.program.id &&
-        startDateTime === this.program.startDateTime &&
-        endDateTime === this.program.endDateTime
-      ) {
-        this.onCloseEditDialog()
-        return
-      }
-      this._program = { id, name, startDateTime, endDateTime }
-      if (this._checkConflict(this._program, this.program.key)) {
-        this._saveProgram()
-      }
-    },
-    _saveProgram () {
-      this._removeProgram(this.program.key)
-      this._mergeOrAdd()
-      this.dirty = true
-      this._calculate()
-      this.onCloseEditDialog()
-    },
-    _mergeOrAdd () {
-      let program = this._program
-      const timestamp = new Date(program.startDateTime).getTime()
-      const insertIndex = this.events.findIndex(item => new Date(item.startDateTime).getTime() > timestamp)
-      let pre = null
-      let next = null
-      let add = true
-      if (~insertIndex) {
-        pre = this.events[insertIndex - 1]
-        next = this.events[insertIndex]
-      } else {
-        pre = this.events[this.events.length - 1]
-      }
-      if (next && program.id === next.id && new Date(program.endDateTime).getTime() + 1000 === new Date(next.startDateTime).getTime()) {
-        next.startDateTime = program.startDateTime
-        program = next
-        add = false
-      }
-      if (pre && program.id === pre.id && new Date(pre.endDateTime).getTime() + 1000 === new Date(program.startDateTime).getTime()) {
-        pre.endDateTime = program.endDateTime
-        this._removeProgram(program.key)
-        program = pre
-        add = false
-      }
-      program.key = Math.random().toString(16).slice(2)
-      program.days = this._getDays(program.startDateTime, program.endDateTime)
-      if (add) {
-        if (next) {
-          this.events.splice(insertIndex, 0, program)
-        } else {
-          this.events.push(program)
-        }
-      }
-    },
-    _checkConflict (program, key) {
-      if (this.events.length) {
-        const cstartDateTime = new Date(program.startDateTime)
-        const cendDateTime = new Date(program.endDateTime)
-        this.conflicts = this.events.filter(item => {
-          const startDateTime = new Date(item.startDateTime)
-          const endDateTime = new Date(item.endDateTime)
-          return item.key !== key && !(cstartDateTime > endDateTime || cendDateTime < startDateTime)
-        }).map(item => {
-          const startDateTime = new Date(item.startDateTime)
-          const endDateTime = new Date(item.endDateTime)
-          return {
-            target: item,
-            key: item.key,
-            start: cstartDateTime > startDateTime ? program.startDateTime : item.startDateTime,
-            end: cendDateTime > endDateTime ? item.endDateTime : program.endDateTime
-          }
-        })
-        if (this.conflicts.length) {
-          this.conflicting = true
-          return false
-        }
-      }
-      return true
-    },
-    _coverConflicts () {
-      const now = Date.now()
-      this.conflicts.forEach(({ target, key, start, end }) => {
-        const { startDateTime, endDateTime } = target
-        if (new Date(endDateTime).getTime() <= now || startDateTime === start && endDateTime === end) {
-          this._removeProgram(key)
-          return
-        }
-        if (startDateTime === start) {
-          target.startDateTime = this._offsetDateTime(end, 1)
-        } else {
-          if (endDateTime !== end) {
-            const index = this.events.findIndex(({ key }) => key === target.key)
-            this.events.splice(index + 1, 0, this._createEvent(target.id, target.name, this._offsetDateTime(end, 1), target.endDateTime))
-          }
-          target.endDateTime = this._offsetDateTime(start, -1)
-        }
-        target.days = this._getDays(target.startDateTime, target.endDateTime)
-      })
-      this.conflicting = false
-      this._saveProgram()
-    },
-    _coverConflictsFull () {
-      this.conflicts.forEach(({ key }) => this._removeProgram(key))
-      this.conflicting = false
-      this._saveProgram()
-    },
-    onCloseEditDialog () {
-      this.program = null
-      this.editing = false
-    },
-    today () {
-      const { year, month } = this._transform(Date.now())
-      this.current = { year, month }
-      this._calculate()
-    },
-    _transform (timestamp) {
-      const date = new Date(timestamp)
-      return {
-        year: date.getFullYear(),
-        month: date.getMonth(),
-        day: date.getDate()
-      }
-    },
-    _compare (a, b, full = true) {
-      if (a.year === b.year) {
-        if (full && a.month === b.month) {
-          return a.day - b.day
-        }
-        return a.month - b.month
-      }
-      return a.year - b.year
-    },
-    _createItem (date, disabled) {
-      const dateObj = this._transform(date)
-      const invalid = this._compare(dateObj, this._transform(Date.now())) < 0
-      const day = `${dateObj.year}-${dateObj.month + 1}-${dateObj.day}`
-      return {
-        value: dateObj.day,
-        editable: this.editable && !disabled && !invalid,
-        invalid,
-        disabled,
-        events: this.events.length ? this.events.filter(program => {
-          return program.days[day]
-        }) : []
-      }
-    },
-    _calculate () {
-      const { year, month } = this.current
-      const monthDays = this._getPresentMonthDays(year, month).concat(this._currentMonthDays(year, month))
-      const diff = 42 - monthDays.length
-      const dates = diff === 0 ? monthDays : monthDays.concat(this._getNextMonthDays(year, month, diff))
-      const weeks = []
-      let row
-      for (let i = 0; i < dates.length; i++) {
-        if (i % 7 === 0) {
-          row = weeks.length
-          weeks[row] = []
-        }
-        weeks[row][i % 7] = dates[i]
-      }
-      this.weeks = weeks
-    },
-    _currentMonthDays (year, month) {
-      const dateArr = []
-      const lastDay = new Date(year, month + 1, 0).getDate()
-
-      for (let i = 1; i <= lastDay; i++) {
-        dateArr.push(this._createItem(new Date(year, month, i)))
-      }
-      return dateArr
-    },
-    _getPresentMonthDays (year, month) {
-      const dateArr = []
-      const week = new Date(year, month, 1).getDay()
-      const fill = week === 0 ? 7 : week
-
-      for (let i = fill; i > 0; i--) {
-        dateArr.push(this._createItem(new Date(year, month, 1 - i), true))
-      }
-
-      return dateArr
-    },
-    _getNextMonthDays (year, month, count) {
-      const dateArr = []
-
-      for (let i = 0; i < count; i++) {
-        dateArr.push(this._createItem(new Date(year, month + 1, 1 + i), true))
-      }
-
-      return dateArr
-    },
-    _offsetMonth (offset) {
-      if (offset < 0) {
-        if (this.current.month === 0) {
-          this.current.year -= 1
-          this.current.month = 11
-        } else {
-          this.current.month -= 1
-        }
-      } else if (offset > 0) {
-        if (this.current.month === 11) {
-          this.current.year += 1
-          this.current.month = 0
-        } else {
-          this.current.month += 1
-        }
-      }
-      this._calculate()
-    },
-    toPresent () {
-      if (this.canToPresent) {
-        this._offsetMonth(-1)
-      }
-    },
-    toNext () {
-      if (this.canToNext) {
-        this._offsetMonth(1)
+          })
+        default:
+          return this.events.map(({ origin }) => origin)
       }
     }
   }
@@ -604,8 +180,14 @@ export default {
   }
 
   &__main {
-    color: $black;
-    font-size: 16px;
+    flex: 0 1 auto;
+    min-height: 0;
+  }
+
+  &__mode {
+    border-radius: 4px;
+    background-color: #f4f7f8;
+    overflow: hidden;
   }
 
   &__type {
@@ -618,4 +200,12 @@ export default {
     }
   }
 }
+
+.o-conflict {
+  &__desc {
+    padding: 4px 0;
+    color: $info;
+    font-size: 12px;
+  }
+}
 </style>

+ 0 - 51
src/components/Schedule/ScheduleComplex/PopList.vue

@@ -1,51 +0,0 @@
-<template>
-  <el-popover
-    trigger="hover"
-    :close-delay="0"
-  >
-    <div class="l-flex--col c-pop-list">
-      <event-item
-        v-for="event in list"
-        :key="event.key"
-        class="c-pop-list__item"
-        :event="event"
-        fill
-        v-bind="$attrs"
-        v-on="$listeners"
-      />
-    </div>
-    <template #reference>
-      <slot />
-    </template>
-  </el-popover>
-</template>
-
-<script>
-import EventItem from './EventItem'
-
-export default {
-  name: 'PopList',
-  components: {
-    EventItem
-  },
-  props: {
-    events: {
-      type: Array,
-      default: null
-    }
-  },
-  computed: {
-    list () {
-      return this.events ? this.events.slice(1) : []
-    }
-  }
-}
-</script>
-
-<style lang="scss" scoped>
-.c-pop-list {
-  width: 200px;
-  max-height: 200px;
-  overflow-y: auto;
-}
-</style>

+ 0 - 181
src/components/Schedule/ScheduleComplex/ScheduleCalendarMonth.vue

@@ -1,181 +0,0 @@
-<template>
-  <div class="l-flex--col c-month-calendar">
-    <div class="l-flex__none l-flex c-month-calendar__header">
-      <div class="l-flex__auto u-readonly">周日</div>
-      <div class="l-flex__auto u-readonly">周一</div>
-      <div class="l-flex__auto u-readonly">周二</div>
-      <div class="l-flex__auto u-readonly">周三</div>
-      <div class="l-flex__auto u-readonly">周四</div>
-      <div class="l-flex__auto u-readonly">周五</div>
-      <div class="l-flex__auto u-readonly">周六</div>
-    </div>
-    <div class="c-month-calendar__content">
-      <div
-        v-for="(week, weekIndex) in weeks"
-        :key="weekIndex"
-        class="l-flex c-month-calendar__row"
-      >
-        <div
-          v-for="(day, dayIndex) in week"
-          :key="dayIndex"
-          class="c-month-calendar__col day c-day-card"
-          :class="day.status"
-          @click="switchTo(day)"
-          @dblclick="onAdd(day)"
-        >
-          <span class="c-day-card__day">{{ day.value }}</span>
-          <div
-            v-if="day.events.length"
-            class="l-flex--row"
-          >
-            <event-item
-              class="c-day-card__program"
-              :event="day.events[0]"
-              :editable="editable"
-              @edit="$listeners.edit"
-              @remove="$listeners.remove"
-            />
-          </div>
-          <pop-list
-            v-if="day.events.length > 1"
-            class="l-flex__auto"
-            :events="day.events"
-            :editable="editable"
-            @edit="$listeners.edit"
-            @remove="$listeners.remove"
-          >
-            <div class="c-day-card__count u-ellipsis u-pointer">
-              +{{ day.events.length - 1 }}
-            </div>
-          </pop-list>
-        </div>
-      </div>
-    </div>
-  </div>
-</template>
-
-<script>
-import { EventFreq } from '@/constant'
-import { toDateStr } from '@/utils/event'
-import EventItem from './EventItem'
-import PopList from './PopList'
-
-export default {
-  name: 'ScheduleCalendarMonth',
-  components: {
-    EventItem,
-    PopList
-  },
-  props: {
-    editable: {
-      type: [Boolean, String],
-      default: false
-    },
-    weeks: {
-      type: Array,
-      default: null
-    }
-  },
-  methods: {
-    onAdd (day) {
-      if (day.status === 'enabled') {
-        this.$emit('add', {
-          freq: EventFreq.ONCE,
-          start: `${toDateStr(day.date)} 00:00:00`,
-          until: `${toDateStr(day.date, 1)} 00:00:00`
-        })
-      }
-    },
-    switchTo (day) {
-      if (day.status === 'disabled') {
-        this.$emit('switch', day.date)
-      }
-    }
-  }
-}
-</script>
-
-<style lang="scss" scoped>
-.c-month-calendar {
-  color: $black;
-  font-size: 16px;
-
-  &__header {
-    padding: 10px 0;
-    font-weight: bold;
-    text-align: center;
-    border: 1px solid $info;
-  }
-
-  &__content {
-    min-height: 0;
-    border: 1px solid $info;
-    border-top: none;
-    overflow-y: auto;
-  }
-
-  &__row + &__row {
-    border-top: 1px solid $info;
-  }
-
-  &__col {
-    flex: 1 1 0;
-    display: flex;
-    min-width: 0;
-    height: 108px;
-    user-select: none;
-
-    &.disabled {
-      color: $gray;
-    }
-
-    &.invalid {
-      background-color: lighten($gray--light, 4%);
-      cursor: not-allowed;
-    }
-
-    &.day.enabled {
-      cursor: pointer;
-
-      &:hover {
-        transition: background-color 0.4s;
-        background-color: #f2f8fe;
-      }
-    }
-
-    & + & {
-      border-left: 1px solid $info;
-    }
-  }
-}
-
-.c-day-card {
-  display: inline-flex;
-  flex-direction: column;
-  padding: 10px 10px 0;
-  font-size: 14px;
-  line-height: 1;
-  text-align: left;
-
-  &__day {
-    align-self: flex-end;
-    font-size: 16px;
-  }
-
-  &__program {
-    max-width: 100%;
-    margin-top: 8px;
-  }
-
-  &__count {
-    align-self: flex-start;
-    display: inline-block;
-    padding: 6px 10px;
-    margin-top: 10px;
-    color: #fff;
-    font-weight: bold;
-    border-radius: 4px;
-    background-color: #fb8885;
-  }
-}
-</style>

+ 0 - 733
src/components/Schedule/ScheduleComplex/index.vue

@@ -1,733 +0,0 @@
-<template>
-  <schedule-wrapper
-    class="c-schedule-calendar"
-    :name="name"
-    :hide-header="hideHeader"
-    :editable="editable"
-    :dirty="dirty"
-    @add="onAdd"
-    @submit="submit"
-    @save="onSave"
-  >
-    <div class="l-flex__none l-flex--row">
-      <button
-        class="l-flex__none o-button c-schedule-calendar__toggle"
-        :class="{ show: canToToday }"
-        @click="onToday"
-      >
-        今日
-      </button>
-      <div class="l-flex__fill l-flex--row center">
-        <i
-          class="c-schedule-calendar__toggle c-schedule-calendar__btn el-icon-arrow-left c-sibling-item u-pointer"
-          :class="{ show: canToPrevious }"
-          @click="onPrevious"
-        />
-        <span class="c-schedule-calendar__time u-readonly">{{ date }}</span>
-        <i
-          class="c-schedule-calendar__toggle c-schedule-calendar__btn el-icon-arrow-right u-pointer"
-          :class="{ show: canToNext }"
-          @click="onNext"
-        />
-      </div>
-      <div class="l-flex__none l-flex--row c-schedule-calendar__mode u-pointer">
-        <div
-          class="c-schedule-calendar__type"
-          :class="{ active: mode === 0 }"
-          @click="onChangeToWeek"
-        >
-          周
-        </div>
-        <div
-          class="c-schedule-calendar__type"
-          :class="{ active: mode === 1 }"
-          @click="onChangeToMonth"
-        >
-          月
-        </div>
-      </div>
-    </div>
-    <component
-      :is="activeComponent"
-      class="c-schedule-calendar__main"
-      :editable="editable"
-      :weeks="weeks"
-      :cursor="weekIndex"
-      @add="onAdd"
-      @edit="onEditProxy"
-      @remove="onRemove"
-      @switch="toDay"
-    />
-    <el-dialog
-      title="事件设置"
-      :visible.sync="editing"
-      custom-class="c-dialog"
-      :close-on-click-modal="false"
-      :before-close="onCloseEditDialog"
-    >
-      <schedule-edit
-        v-if="event"
-        ref="editor"
-        :event="event"
-        :ratio="ratio"
-      />
-      <template #footer>
-        <button
-          class="o-button"
-          @click="onSaveEvent"
-        >
-          确定
-        </button>
-        <button
-          class="o-button cancel"
-          @click="onCloseEditDialog"
-        >
-          取消
-        </button>
-      </template>
-    </el-dialog>
-    <el-dialog
-      title="冲突提醒"
-      :visible.sync="conflicting"
-      custom-class="c-dialog"
-      :close-on-click-modal="false"
-      append-to-body
-    >
-      <div
-        v-for="conflict in conflicts"
-        :key="conflict.key"
-        class="o-conflict"
-      >
-        {{ conflict.info }}
-        <div class="o-conflict__desc">{{ conflict.desc }}</div>
-      </div>
-      <template #footer>
-        <button
-          class="o-button"
-          @click="onCover"
-        >
-          覆盖
-        </button>
-        <button
-          class="o-button cancel"
-          @click="conflicting = false"
-        >
-          取消
-        </button>
-      </template>
-    </el-dialog>
-    <el-dialog
-      :visible.sync="isPreviewing"
-      custom-class="c-preview schedule"
-      :before-close="onClosePreviewDialog"
-      append-to-body
-    >
-      <schedule
-        v-if="scheduleId"
-        class="l-flex__auto has-padding"
-        :schedule="scheduleId"
-      />
-    </el-dialog>
-  </schedule-wrapper>
-</template>
-
-<script>
-import { EventFreq } from '@/constant'
-import {
-  ONE_DAY,
-  toDate,
-  toDateStr,
-  toTimeStr,
-  toZeroPoint,
-  pickMin,
-  pickMax,
-  isOverDay,
-  getConflict
-} from '@/utils/event'
-import scheduleMixin from '../mixins/schedule'
-import ScheduleWrapper from '../components/ScheduleWrapper'
-import ScheduleCalendarWeek from './ScheduleCalendarWeek'
-import ScheduleCalendarMonth from './ScheduleCalendarMonth'
-import ScheduleEdit from './ScheduleComplexEdit'
-
-export default {
-  name: 'ScheduleComplex',
-  components: {
-    ScheduleWrapper,
-    ScheduleCalendarWeek,
-    ScheduleCalendarMonth,
-    ScheduleEdit
-  },
-  mixins: [scheduleMixin],
-  data () {
-    return {
-      mode: 1,
-      weeks: [],
-      weekIndex: 0,
-      weekIndexStart: 0,
-      weekIndexEnd: 6,
-      minDate: null,
-      maxDate: null,
-      current: null,
-      editing: false,
-      event: null,
-      dirty: false,
-      conflicting: false,
-      conflicts: [],
-      isPreviewing: false,
-      scheduleId: null
-    }
-  },
-  computed: {
-    activeComponent () {
-      switch (this.mode) {
-        case 0:
-          return 'ScheduleCalendarWeek'
-        default:
-          return 'ScheduleCalendarMonth'
-      }
-    },
-    date () {
-      if (!this.current) {
-        return ''
-      }
-      if (this.mode === 0) {
-        const week = this.weeks[this.weekIndex]
-        const sun = week[0]
-        const sunYear = sun.date.getFullYear()
-        const sunMonth = sun.date.getMonth()
-        const sat = week[6]
-        const satYear = sat.date.getFullYear()
-        const satMonth = sat.date.getMonth()
-        if (sunMonth === satMonth) {
-          return `${sunYear}.${sunMonth + 1}.${sun.value}-${sat.value}`
-        }
-        if (sunYear === satYear) {
-          return `${sunYear}.${sunMonth + 1}.${sun.value}-${satMonth + 1}.${sat.value}`
-        }
-        return `${sunYear}.${sunMonth + 1}.${sun.value}-${satYear}.${satMonth + 1}.${sat.value}`
-      }
-      const year = this.current.getFullYear()
-      const month = this.current.getMonth()
-      return `${year}.${month + 1}`
-    },
-    minRange () {
-      const minDate = toZeroPoint(toDate(this.current))
-      minDate.setDate(1)
-      return minDate
-    },
-    maxRange () {
-      const maxDate = toZeroPoint(toDate(this.current))
-      maxDate.setDate(1)
-      maxDate.setMonth(maxDate.getMonth() + 1)
-      return maxDate
-    },
-    canToPrevious () {
-      if (!this.current) {
-        return false
-      }
-      if (this.mode === 0) {
-        return this.weeks[this.weekIndex][0].date > this.minDate
-      }
-      return this.minRange > this.minDate
-    },
-    canToNext () {
-      if (!this.current) {
-        return false
-      }
-      if (!this.maxDate) {
-        return true
-      }
-      if (this.mode === 0) {
-        return this.weeks[this.weekIndex][6].date < this.maxDate
-      }
-      return this.maxRange <= this.maxDate
-    },
-    canToToday () {
-      if (!this.current) {
-        return false
-      }
-      const today = toZeroPoint(new Date())
-      if (today < this.minDate || this.maxDate && today >= this.maxDate) {
-        return false
-      }
-      if (this.mode === 0) {
-        return !(this.weeks[this.weekIndex][0].date <= today && this.weeks[this.weekIndex][6].date >= today)
-      }
-      return today < this.minRange || today >= this.maxRange
-    }
-  },
-  methods: {
-    _transformEvents (events) {
-      return events.sort(this._sortEvents)
-    },
-    _sortEvents (a, b) {
-      return toDate(a.start) - toDate(b.start)
-    },
-    _transformEvent (event) {
-      return {
-        key: Math.random().toString(16).slice(2),
-        origin: event
-      }
-    },
-    _getEvents () {
-      return this.events.map(({ origin }) => origin)
-    },
-    init () {
-      const events = this.events
-      const length = events.length
-      const today = new Date()
-      let minDate = null
-      let maxDate = null
-      if (this.editable) {
-        minDate = length ? pickMin(toDate(events[0].origin.start), today) : today
-      } else {
-        if (length) {
-          minDate = toDate(events[0].origin.start)
-          for (let i = length - 1; i >= 0; i--) {
-            const until = events[length - 1].origin.until
-            if (!until) {
-              maxDate = null
-              break
-            }
-            if (!maxDate || maxDate < until) {
-              maxDate = until
-            }
-          }
-          maxDate = toDate(maxDate)
-        } else {
-          minDate = today
-          maxDate = today
-        }
-      }
-      this.minDate = toZeroPoint(minDate)
-      this.maxDate = maxDate
-      this.toDay(maxDate ? toZeroPoint(pickMax(minDate, pickMin(today, maxDate))) : toZeroPoint(pickMax(minDate, today)))
-    },
-    calculate () {
-      const date = toDate(this.current)
-      date.setDate(1)
-      const today = toZeroPoint(new Date())
-      const firstDayWeek = date.getDay()
-      const prevMonthDays = this.getPrevMonthDays(date, firstDayWeek === 0 ? 7 : firstDayWeek, today)
-      const monthDays = this.getMonthDays(date, today)
-      const diff = 42 - prevMonthDays.length - monthDays.length
-      const nextMonthDays = this.getNextMonthDays(date, diff, today)
-      const weeks = []
-      const days = [...prevMonthDays, ...monthDays, ...nextMonthDays]
-
-      let row
-      for (let i = 0; i < days.length; i++) {
-        if (i % 7 === 0) {
-          row = weeks.length
-          weeks[row] = []
-        }
-        weeks[row][i % 7] = days[i]
-      }
-      this.weeks = weeks
-
-      this.weekIndexStart = Math.floor(prevMonthDays.length / 7)
-      this.weekIndexEnd = 6 - Math.floor(diff / 7)
-    },
-    getMonthDays (date, reference) {
-      const year = date.getFullYear()
-      const month = date.getMonth()
-      const dateArr = []
-      const lastDay = new Date(year, month + 1, 0).getDate()
-
-      for (let i = 1; i <= lastDay; i++) {
-        const temp = new Date(year, month, i)
-        dateArr.push(this.createDayItem(temp, temp < reference))
-      }
-      return dateArr
-    },
-    getPrevMonthDays (date, amount, reference) {
-      const dateArr = []
-
-      if (amount) {
-        date = toDate(date)
-        date.setDate(0)
-        const year = date.getFullYear()
-        const month = date.getMonth()
-        const lastDay = date.getDate()
-        for (let i = amount; i > 0; i--) {
-          const temp = new Date(year, month, lastDay - i + 1)
-          dateArr.push(this.createDayItem(temp, temp < reference, true))
-        }
-      }
-
-      return dateArr
-    },
-    getNextMonthDays (date, amount, reference) {
-      const dateArr = []
-
-      if (amount) {
-        const year = date.getFullYear()
-        const month = date.getMonth()
-        for (let i = 0; i < amount; i++) {
-          const temp = new Date(year, month + 1, i + 1)
-          dateArr.push(this.createDayItem(temp, temp < reference, true))
-        }
-      }
-
-      return dateArr
-    },
-    createDayItem (date, invalid, disabled) {
-      return {
-        date,
-        value: date.getDate(),
-        status: this.getStatus(invalid, disabled, this.editable),
-        events: this.getEventsByDay(this.events, date)
-      }
-    },
-    getStatus (invalid, disabled, editable) {
-      if (invalid) {
-        return 'invalid'
-      }
-      if (disabled) {
-        return 'disabled'
-      }
-      return editable ? 'enabled' : 'normal'
-    },
-    getEventsByDay (events, date) {
-      const arr = []
-      if (events.length) {
-        const minDate = date
-        const maxDate = toDate(date.getTime() + ONE_DAY)
-        const week = minDate.getDay()
-        for (let i = 0; i < events.length; i++) {
-          const { freq, start, until } = events[i].origin
-          if (until && toDate(until) <= minDate) {
-            continue
-          }
-          if (toDate(start) >= maxDate) {
-            break
-          }
-          if (freq === EventFreq.WEEKLY) {
-            const event = events[i].origin
-            if (event.byDay.includes(week) || isOverDay(event) && event.byDay.includes(week === 0 ? 6 : week - 1)) {
-              arr.push(events[i])
-            }
-          } else {
-            arr.push(events[i])
-          }
-        }
-      }
-      return arr
-    },
-    toDay (date) {
-      date = toZeroPoint(toDate(date))
-      if (date >= this.minDate && (!this.maxDate || date < this.maxDate)) {
-        this.current = date
-        this.calculate()
-        let weekIndex = this.weekIndexStart
-        for (let i = this.weekIndexStart, j = this.weekIndexEnd; i <= j; i++) {
-          const start = this.weeks[i][0].date
-          const end = this.weeks[i][6].date
-          if (start <= date && date <= end) {
-            weekIndex = i
-            break
-          }
-        }
-        this.weekIndex = weekIndex
-      }
-    },
-    onToday () {
-      this.toDay(new Date())
-    },
-    onPrevious () {
-      if (this.canToPrevious) {
-        switch (this.mode) {
-          case 0:
-            this.offsetWeek(-1)
-            break
-          case 1:
-            this.offsetMonth(-1)
-            break
-          default:
-            break
-        }
-      }
-    },
-    onNext () {
-      if (this.canToNext) {
-        switch (this.mode) {
-          case 0:
-            this.offsetWeek(1)
-            break
-          case 1:
-            this.offsetMonth(1)
-            break
-          default:
-            break
-        }
-      }
-    },
-    offsetWeek (offset) {
-      const next = this.weekIndex + offset
-      if (next < this.weekIndexStart || next > this.weekIndexEnd) {
-        const firstDate = this.weeks[this.weekIndex][0].date
-        firstDate.setDate(firstDate.getDate() + 7 * offset)
-        this.toDay(firstDate)
-      } else {
-        this.weekIndex += offset
-      }
-    },
-    offsetMonth (offset) {
-      const next = toDate(this.current)
-      next.setDate(1)
-      next.setMonth(next.getMonth() + offset)
-      this.toDay(pickMax(this.minDate, next))
-    },
-    onChangeToMonth () {
-      this.mode = 1
-    },
-    onChangeToWeek () {
-      this.mode = 0
-    },
-    onAdd (event) {
-      if (this.editable) {
-        if (event && event.until && toDate(event.until) <= new Date()) {
-          this.calculate()
-          return
-        }
-        this.editEvent(event)
-      }
-    },
-    onEditProxy (eventProxy) {
-      const event = eventProxy.origin
-      if (this.editable) {
-        this.$eventProxy = eventProxy
-        this.editEvent(event)
-      } else {
-        switch (event.target.type) {
-          case 2:
-            this.scheduleId = event.target.id
-            this.isPreviewing = true
-            break
-          default:
-            window.open(this.$router.resolve({
-              name: 'view',
-              params: { id: event.target.id }
-            }).href, '_blank')
-            break
-        }
-      }
-    },
-    editEvent (event) {
-      if (this.editable) {
-        this.event = event || {}
-        this.editing = true
-      }
-    },
-    onCloseEditDialog () {
-      this.$eventProxy = null
-      this.event = null
-      this.editing = false
-    },
-    onClosePreviewDialog () {
-      this.scheduleId = null
-      this.isPreviewing = false
-    },
-    onRemove (eventProxy) {
-      this.$confirm(
-        `确定移除 ${eventProxy.origin.target.name}`,
-        { type: 'warning' }
-      ).then(() => {
-        this.removeEventProxy(eventProxy)
-        this.correct()
-      })
-    },
-    removeEventProxy ({ key }) {
-      const index = this.events.findIndex(event => event.key === key)
-      if (~index) {
-        this.events.splice(index, 1)
-        this.dirty = true
-      }
-      return index
-    },
-    onSaveEvent () {
-      const event = this.$refs.editor.getValue()
-      if (!event) {
-        return
-      }
-      console.log(event)
-      if (this.$eventProxy && this.getEventUnique(this.$eventProxy.origin) === this.getEventUnique(event)) {
-        this.dirty = true
-        this.$eventProxy.origin.target = event.target
-        this.onCloseEditDialog()
-        return
-      }
-      if (!this.checkConflict(event, this.$eventProxy?.key)) {
-        if (this.$eventProxy) {
-          this.removeEventProxy(this.$eventProxy)
-        }
-        this.saveEvent(event)
-        this.onCloseEditDialog()
-      }
-    },
-    saveEvent (event) {
-      this.dirty = true
-      const minDate = toDate(event.start)
-      const insertIndex = this.events.findIndex(eventProxy => toDate(eventProxy.origin.start) > minDate)
-      if (~insertIndex) {
-        this.events.splice(insertIndex, 0, this._transformEvent(event))
-      } else {
-        this.events.push(this._transformEvent(event))
-      }
-      this.toDay(minDate)
-    },
-    getEventUnique (event) {
-      const { freq, start, until, byDay, startTime, endTime } = event
-      switch (freq) {
-        case EventFreq.ONCE:
-          return `${freq};${start};${until}`
-        case EventFreq.WEEKLY:
-          return `${freq};${start};${until};${byDay};${startTime};${endTime}`
-        default:
-          return Math.random().toString(16).slice(2)
-      }
-    },
-    checkConflict (event, key) {
-      if (this.events.length) {
-        const conflicts = []
-        this.events.forEach(eventProxy => {
-          if (eventProxy.key !== key) {
-            const date = getConflict(event, eventProxy.origin)
-            if (date) {
-              conflicts.push({
-                key: eventProxy.key,
-                info: `与 ${eventProxy.origin.target.name} 于 ${toDateStr(date)} ${toTimeStr(date)} 有冲突`,
-                desc: this.getConflectMessage(eventProxy.origin)
-              })
-            }
-          }
-        })
-        this.conflicts = conflicts
-        if (this.conflicts.length) {
-          this.$conflictSource = event
-          this.conflicting = true
-          return true
-        }
-      }
-      return false
-    },
-    getConflectMessage (event) {
-      const { freq, start, until, byDay, startTime, endTime } = event
-      switch (freq) {
-        case EventFreq.WEEKLY:
-          return `自${start.split(' ')[0]}开始${until ? `至${until.split(' ')[0]}前` : ''} 每周${byDay.split(',').map(val => ['日', '一', '二', '三', '四', '五', '六'][val]).join('、')} ${startTime} - ${endTime}`
-        default:
-          return until ? `${start} - ${until}` : `自${start}开始`
-      }
-    },
-    onCover () {
-      this.conflicts.forEach(eventProxy => this.removeEventProxy(eventProxy))
-      this.saveEvent(this.$conflictSource)
-      this.$conflictSource = null
-      this.conflicting = false
-      this.onCloseEditDialog()
-    },
-    onSave () {
-      this.fix().then(this.save).then(() => { this.dirty = false })
-    },
-    fix () {
-      let maxDate = Date.now() + 60000
-      if (this.events.some(({ origin: { until } }) => until && toDate(until) <= maxDate)) {
-        return this.$confirm(
-          '存在过期或将要过期(<=60s)节目,是否移除?',
-          {
-            type: 'warning',
-            distinguishCancelAndClose: true,
-            confirmButtonText: '移除',
-            cancelButtonText: '保留'
-          }
-        ).then(() => {
-          maxDate = Date.now() + 60000
-          this.scheduleOptions.events = this.events.filter(({ origin: { until } }) => !until || toDate(until) > maxDate)
-          this.correct()
-          return this.events
-        }, action => {
-          if (action === 'cancel') {
-            return this.events
-          }
-          return Promise.reject()
-        })
-      }
-      return Promise.resolve(this.events)
-    },
-    correct () {
-      const today = new Date()
-      const minDate = toZeroPoint(this.events.length ? pickMin(toDate(this.events[0].origin.start), today) : today)
-      if (this.minDate < minDate) {
-        this.minDate = minDate
-        if (this.current < this.minDate) {
-          this.toDay(this.current)
-          return
-        }
-      }
-      this.calculate()
-    }
-  }
-}
-</script>
-
-<style lang="scss" scoped>
-.c-schedule-calendar {
-  min-height: 200px;
-
-  &__time {
-    box-sizing: content-box;
-    min-width: 100px;
-    padding: $spacing;
-    color: $black;
-    font-size: 24px;
-    font-weight: bold;
-    line-height: 1;
-    text-align: center;
-  }
-
-  &__toggle {
-    visibility: hidden;
-
-    &.show {
-      visibility: visible;
-    }
-  }
-
-  &__btn {
-    color: $blue;
-    font-size: 18px;
-    font-weight: bold;
-  }
-
-  &__main {
-    flex: 0 1 auto;
-    min-height: 0;
-  }
-
-  &__mode {
-    border-radius: 4px;
-    background-color: #f4f7f8;
-    overflow: hidden;
-  }
-
-  &__type {
-    padding: 8px 12px;
-    color: $info--dark;
-
-    &.active {
-      color: #fff;
-      background-color: $blue;
-    }
-  }
-}
-
-.o-conflict {
-  &__desc {
-    padding: 4px 0;
-    color: $info;
-    font-size: 12px;
-  }
-}
-</style>

+ 0 - 0
src/components/Schedule/components/ProgramChoose.vue → src/components/Schedule/ScheduleSwiper/ProgramChoose.vue


+ 5 - 5
src/components/Schedule/ScheduleSwiper/index.vue

@@ -87,7 +87,7 @@
 import scheduleMixin from '../mixins/schedule'
 import Draggable from 'vuedraggable'
 import ScheduleWrapper from '../components/ScheduleWrapper'
-import ProgramChoose from '../components/ProgramChoose'
+import ProgramChoose from './ProgramChoose'
 
 export default {
   name: 'ScheduleSwiper',
@@ -124,10 +124,10 @@ export default {
     }
   },
   methods: {
-    _transformEvents (events) {
-      return events.sort((a, b) => a.index - b.index)
+    transformEvents (events) {
+      return events.sort((a, b) => a.index - b.index).map(this.transformEvent)
     },
-    _transformEvent (event) {
+    transformEvent (event) {
       const { programId, programName, programUrl, spend } = event
       return {
         key: Math.random().toString(16).slice(2),
@@ -137,7 +137,7 @@ export default {
         duration: this.transformDuration(spend)
       }
     },
-    _getEvents () {
+    getEvents () {
       return this.events.map(({ id, name, url, duration }, index) => {
         return {
           index,

+ 2 - 4
src/components/Schedule/index.vue

@@ -2,14 +2,12 @@
 import { getSchedule } from '@/api/calendar'
 import ScheduleCalendar from './ScheduleCalendar'
 import ScheduleSwiper from './ScheduleSwiper'
-import ScheduleComplex from './ScheduleComplex'
 
 export default {
   name: 'ScheduleComponent',
   components: {
     ScheduleCalendar,
-    ScheduleSwiper,
-    ScheduleComplex
+    ScheduleSwiper
   },
   props: {
     schedule: {
@@ -75,7 +73,7 @@ export default {
     }
 
     const { detail } = this.options
-    return h(['ScheduleCalendar', 'ScheduleSwiper', 'ScheduleComplex'][detail.type - 1], {
+    return h(['ScheduleCalendar', 'ScheduleSwiper', 'ScheduleCalendar'][detail.type - 1], {
       props: {
         detail,
         ...this.$attrs

+ 325 - 0
src/components/Schedule/mixins/calendar.js

@@ -0,0 +1,325 @@
+import { EventFreq } from '@/constant'
+import {
+  ONE_DAY,
+  toDate,
+  toZeroPoint,
+  pickMin,
+  pickMax,
+  isOverDay,
+  isIn
+} from '@/utils/event'
+import scheduleMinxi from './schedule'
+import eventMinxi from './event'
+
+export default {
+  mixins: [scheduleMinxi, eventMinxi],
+  data () {
+    return {
+      mode: 1,
+      weeks: [],
+      weekIndex: 0,
+      minDate: null,
+      maxDate: null,
+      current: null
+    }
+  },
+  computed: {
+    activeComponent () {
+      switch (this.mode) {
+        case 0:
+          return 'ScheduleCalendarWeek'
+        default:
+          return 'ScheduleCalendarMonth'
+      }
+    },
+    date () {
+      if (!this.current) {
+        return ''
+      }
+      if (this.mode === 0) {
+        const week = this.weeks[this.weekIndex]
+        const sun = week[0]
+        const sunYear = sun.date.getFullYear()
+        const sunMonth = sun.date.getMonth()
+        const sat = week[6]
+        const satYear = sat.date.getFullYear()
+        const satMonth = sat.date.getMonth()
+        if (sunMonth === satMonth) {
+          return `${sunYear}.${sunMonth + 1}.${sun.value}-${sat.value}`
+        }
+        if (sunYear === satYear) {
+          return `${sunYear}.${sunMonth + 1}.${sun.value}-${satMonth + 1}.${sat.value}`
+        }
+        return `${sunYear}.${sunMonth + 1}.${sun.value}-${satYear}.${satMonth + 1}.${sat.value}`
+      }
+      const year = this.current.getFullYear()
+      const month = this.current.getMonth()
+      return `${year}.${month + 1}`
+    },
+    minRange () {
+      const minDate = toZeroPoint(toDate(this.current))
+      minDate.setDate(1)
+      return minDate
+    },
+    maxRange () {
+      const maxDate = toZeroPoint(toDate(this.current))
+      maxDate.setDate(1)
+      maxDate.setMonth(maxDate.getMonth() + 1)
+      return maxDate
+    },
+    canToPrevious () {
+      if (!this.current) {
+        return false
+      }
+      if (this.mode === 0) {
+        return this.weeks[this.weekIndex][0].date > this.minDate
+      }
+      return this.minRange > this.minDate
+    },
+    canToNext () {
+      if (!this.current) {
+        return false
+      }
+      if (!this.maxDate) {
+        return true
+      }
+      if (this.mode === 0) {
+        return this.weeks[this.weekIndex][6].date < this.maxDate
+      }
+      return this.maxRange <= this.maxDate
+    },
+    canToToday () {
+      if (!this.current) {
+        return false
+      }
+      const today = toZeroPoint(new Date())
+      if (today < this.minDate || this.maxDate && today >= this.maxDate) {
+        return false
+      }
+      if (this.mode === 0) {
+        return !(this.weeks[this.weekIndex][0].date <= today && this.weeks[this.weekIndex][6].date >= today)
+      }
+      return today < this.minRange || today >= this.maxRange
+    }
+  },
+  methods: {
+    createEventProxy (event) {
+      return {
+        key: Math.random().toString(16).slice(2),
+        origin: event
+      }
+    },
+    init () {
+      const events = this.events
+      const length = events.length
+      const today = new Date()
+      let minDate = null
+      let maxDate = null
+      if (this.editable) {
+        minDate = length ? pickMin(toDate(events[0].origin.start), today) : today
+      } else {
+        if (length) {
+          minDate = toDate(events[0].origin.start)
+          for (let i = length - 1; i >= 0; i--) {
+            const until = events[length - 1].origin.until
+            if (!until) {
+              maxDate = null
+              break
+            }
+            if (!maxDate || maxDate < until) {
+              maxDate = until
+            }
+          }
+          maxDate = toDate(maxDate)
+        } else {
+          minDate = today
+          maxDate = today
+        }
+      }
+      this.minDate = toZeroPoint(minDate)
+      this.maxDate = maxDate
+      this.toDay(maxDate ? isIn(today, minDate, maxDate) ? today : minDate : pickMax(minDate, today))
+    },
+    calculate () {
+      const date = toDate(this.current)
+      date.setDate(1)
+      const today = toZeroPoint(new Date())
+      const firstDayWeek = date.getDay()
+      const prevMonthDays = this.getPrevMonthDays(date, firstDayWeek === 0 ? 7 : firstDayWeek, today)
+      const monthDays = this.getMonthDays(date, today)
+      const diff = 42 - prevMonthDays.length - monthDays.length
+      const nextMonthDays = this.getNextMonthDays(date, diff, today)
+      const weeks = []
+      const days = [...prevMonthDays, ...monthDays, ...nextMonthDays]
+
+      let row
+      for (let i = 0; i < days.length; i++) {
+        if (i % 7 === 0) {
+          row = weeks.length
+          weeks[row] = []
+        }
+        weeks[row][i % 7] = days[i]
+      }
+      this.weeks = weeks
+
+      this.weekIndexStart = Math.floor(prevMonthDays.length / 7)
+      this.weekIndexEnd = 6 - Math.floor(diff / 7)
+    },
+    getMonthDays (date, reference) {
+      const year = date.getFullYear()
+      const month = date.getMonth()
+      const dateArr = []
+      const lastDay = new Date(year, month + 1, 0).getDate()
+
+      for (let i = 1; i <= lastDay; i++) {
+        const temp = new Date(year, month, i)
+        dateArr.push(this.createDayItem(temp, temp < reference))
+      }
+      return dateArr
+    },
+    getPrevMonthDays (date, amount, reference) {
+      const dateArr = []
+
+      if (amount) {
+        date = toDate(date)
+        date.setDate(0)
+        const year = date.getFullYear()
+        const month = date.getMonth()
+        const lastDay = date.getDate()
+        for (let i = amount; i > 0; i--) {
+          const temp = new Date(year, month, lastDay - i + 1)
+          dateArr.push(this.createDayItem(temp, temp < reference, true))
+        }
+      }
+
+      return dateArr
+    },
+    getNextMonthDays (date, amount, reference) {
+      const dateArr = []
+
+      if (amount) {
+        const year = date.getFullYear()
+        const month = date.getMonth()
+        for (let i = 0; i < amount; i++) {
+          const temp = new Date(year, month + 1, i + 1)
+          dateArr.push(this.createDayItem(temp, temp < reference, true))
+        }
+      }
+
+      return dateArr
+    },
+    createDayItem (date, invalid, disabled) {
+      return {
+        date,
+        value: date.getDate(),
+        status: this.getStatus(invalid, disabled, this.editable),
+        events: this.getEventsByDay(this.events, date)
+      }
+    },
+    getStatus (invalid, disabled, editable) {
+      if (invalid) {
+        return 'invalid'
+      }
+      if (disabled) {
+        return 'disabled'
+      }
+      return editable ? 'enabled' : 'normal'
+    },
+    getEventsByDay (events, date) {
+      const arr = []
+      if (events.length) {
+        const minDate = date
+        const maxDate = toDate(date.getTime() + ONE_DAY)
+        const week = minDate.getDay()
+        for (let i = 0; i < events.length; i++) {
+          const { freq, start, until } = events[i].origin
+          if (until && toDate(until) <= minDate) {
+            continue
+          }
+          if (toDate(start) >= maxDate) {
+            break
+          }
+          if (freq === EventFreq.WEEKLY) {
+            const event = events[i].origin
+            if (event.byDay.includes(week) || isOverDay(event) && event.byDay.includes(week === 0 ? 6 : week - 1)) {
+              arr.push(events[i])
+            }
+          } else {
+            arr.push(events[i])
+          }
+        }
+      }
+      return arr
+    },
+    toDay (date) {
+      date = toZeroPoint(toDate(date))
+      if (date >= this.minDate && (!this.maxDate || date < this.maxDate)) {
+        this.current = date
+        this.calculate()
+        let weekIndex = this.weekIndexStart
+        for (let i = this.weekIndexStart, j = this.weekIndexEnd; i <= j; i++) {
+          const start = this.weeks[i][0].date
+          const end = this.weeks[i][6].date
+          if (start <= date && date <= end) {
+            weekIndex = i
+            break
+          }
+        }
+        this.weekIndex = weekIndex
+      }
+    },
+    onToday () {
+      this.toDay(new Date())
+    },
+    onPrevious () {
+      if (this.canToPrevious) {
+        switch (this.mode) {
+          case 0:
+            this.offsetWeek(-1)
+            break
+          case 1:
+            this.offsetMonth(-1)
+            break
+          default:
+            break
+        }
+      }
+    },
+    onNext () {
+      if (this.canToNext) {
+        switch (this.mode) {
+          case 0:
+            this.offsetWeek(1)
+            break
+          case 1:
+            this.offsetMonth(1)
+            break
+          default:
+            break
+        }
+      }
+    },
+    offsetWeek (offset) {
+      const next = this.weekIndex + offset
+      if (next < this.weekIndexStart || next > this.weekIndexEnd) {
+        const firstDate = this.weeks[this.weekIndex][0].date
+        firstDate.setDate(firstDate.getDate() + 7 * offset)
+        this.toDay(firstDate)
+      } else {
+        this.weekIndex += offset
+      }
+    },
+    offsetMonth (offset) {
+      const next = toDate(this.current)
+      next.setDate(1)
+      next.setMonth(next.getMonth() + offset)
+      this.toDay(pickMax(this.minDate, next))
+    },
+    onChangeToMonth () {
+      this.mode = 1
+    },
+    onChangeToWeek () {
+      this.mode = 0
+    }
+  }
+}

+ 204 - 0
src/components/Schedule/mixins/event.js

@@ -0,0 +1,204 @@
+import {
+  toDate,
+  toDateStr,
+  toTimeStr,
+  toZeroPoint,
+  pickMin,
+  getConflict
+} from '@/utils/event'
+import { EventFreq } from '@/constant'
+
+export default {
+  data () {
+    return {
+      event: null,
+      dirty: false,
+      conflicts: [],
+      isPreviewing: false,
+      scheduleId: null
+    }
+  },
+  methods: {
+    onRemove (eventProxy) {
+      this.$confirm(
+        `确定移除 ${eventProxy.origin.target.name}`,
+        { type: 'warning' }
+      ).then(() => {
+        this.removeEventProxy(eventProxy)
+        this.correct()
+      })
+    },
+    removeEventProxy (eventProxy) {
+      if (eventProxy) {
+        const { key } = eventProxy
+        const index = this.events.findIndex(event => event.key === key)
+        if (~index) {
+          this.events.splice(index, 1)
+          this.dirty = true
+          return index
+        }
+      }
+      return -1
+    },
+    onAdd (event) {
+      if (this.editable) {
+        if (event && event.until && toDate(event.until) <= new Date()) {
+          this.calculate()
+          return
+        }
+        this.editEvent(event)
+      }
+    },
+    onEditProxy (eventProxy) {
+      const event = eventProxy.origin
+      if (this.editable) {
+        this.$eventProxy = eventProxy
+        this.editEvent(event)
+      } else {
+        switch (event.target.type) {
+          case 2:
+            this.scheduleId = event.target.id
+            this.isPreviewing = true
+            break
+          default:
+            window.open(this.$router.resolve({
+              name: 'view',
+              params: { id: event.target.id }
+            }).href, '_blank')
+            break
+        }
+      }
+    },
+    onClosePreviewDialog () {
+      this.scheduleId = null
+      this.isPreviewing = false
+    },
+    editEvent (event) {
+      if (this.editable) {
+        this.event = event || {}
+        this.$refs.editDialog.show()
+      }
+    },
+    onSaveEvent (done) {
+      const event = this.$refs.editor.getValue()
+      if (!event) {
+        done()
+        return
+      }
+      console.log(event)
+      if (this.$eventProxy && this.getEventUnique(this.$eventProxy.origin) === this.getEventUnique(event)) {
+        done()
+        this.dirty = true
+        this.$eventProxy.origin.target = event.target
+        return
+      }
+      this.conflicts = this.getConflicts({
+        key: this.$eventProxy?.key,
+        origin: event
+      })
+      if (!this.conflicts.length) {
+        done()
+        this.removeEventProxy(this.$eventProxy)
+        this.saveEvent(event)
+        return
+      }
+      this.$conflictSource = event
+      this.$refs.conflictDialog.show()
+    },
+    getEventUnique (event) {
+      const { freq, start, until, byDay, startTime, endTime } = event
+      switch (freq) {
+        case EventFreq.ONCE:
+          return `${freq};${start};${until}`
+        case EventFreq.WEEKLY:
+          return `${freq};${start};${until};${byDay};${startTime};${endTime}`
+        default:
+          return Math.random().toString(16).slice(2)
+      }
+    },
+    onCover (done) {
+      done()
+      this.$refs.editDialog.onCancel()
+      this.conflicts.forEach(this.removeEventProxy)
+      this.saveEvent(this.$conflictSource)
+      this.$conflictSource = null
+    },
+    saveEvent (event) {
+      this.dirty = true
+      const minDate = toDate(event.start)
+      const insertIndex = this.events.findIndex(eventProxy => toDate(eventProxy.origin.start) > minDate)
+      if (~insertIndex) {
+        this.events.splice(insertIndex, 0, this.createEventProxy(event))
+      } else {
+        this.events.push(this.createEventProxy(event))
+      }
+      this.toDay(minDate)
+    },
+    getConflicts ({ key, origin: event }) {
+      const conflicts = []
+      if (this.events.length) {
+        this.events.forEach(eventProxy => {
+          if (eventProxy.key !== key) {
+            const date = getConflict(event, eventProxy.origin)
+            if (date) {
+              conflicts.push({
+                key: eventProxy.key,
+                info: `与 ${eventProxy.origin.target.name} 于 ${toDateStr(date)} ${toTimeStr(date)} 有冲突`,
+                desc: this.getConflectMessage(eventProxy.origin)
+              })
+            }
+          }
+        })
+      }
+      return conflicts
+    },
+    getConflectMessage (event) {
+      const { freq, start, until, byDay, startTime, endTime } = event
+      switch (freq) {
+        case EventFreq.WEEKLY:
+          return `自${start.split(' ')[0]}开始${until ? `至${until.split(' ')[0]}前` : ''} 每周${byDay.split(',').map(val => ['日', '一', '二', '三', '四', '五', '六'][val]).join('、')} ${startTime} - ${endTime}`
+        default:
+          return until ? `${start} - ${until}` : `自${start}开始`
+      }
+    },
+    onSave () {
+      this.fix().then(this.save).then(() => { this.dirty = false })
+    },
+    fix () {
+      let maxDate = Date.now() + 60000
+      if (this.events.some(({ origin: { until } }) => until && toDate(until) <= maxDate)) {
+        return this.$confirm(
+          '存在过期或将要过期(<=60s)节目,是否移除?',
+          {
+            type: 'warning',
+            distinguishCancelAndClose: true,
+            confirmButtonText: '移除',
+            cancelButtonText: '保留'
+          }
+        ).then(() => {
+          maxDate = Date.now() + 60000
+          this.scheduleOptions.events = this.events.filter(({ origin: { until } }) => !until || toDate(until) > maxDate)
+          this.correct()
+        }, action => {
+          if (action === 'cancel') {
+            return this.events
+          }
+          return Promise.reject()
+        })
+      }
+      return Promise.resolve()
+    },
+    correct () {
+      const today = new Date()
+      const minDate = toZeroPoint(this.events.length ? pickMin(toDate(this.events[0].origin.start), today) : today)
+      if (this.minDate < minDate) {
+        this.minDate = minDate
+        if (this.current < this.minDate) {
+          this.toDay(this.current)
+          return
+        }
+      }
+      this.calculate()
+    }
+  }
+}

+ 5 - 8
src/components/Schedule/mixins/schedule.js

@@ -39,26 +39,23 @@ export default {
     this.editable = this.detail.status === State.READY
     this.scheduleOptions = {
       ...this.detail,
-      events: this._transformEvents(this.detail.events || []).map(this._transformEvent)
+      events: this.transformEvents(this.detail.events || [], this.detail.type)
     }
     this.init()
   },
   methods: {
     init () { },
-    _transformEvents (events) {
+    transformEvents (events) {
       return events
     },
-    _transformEvent (event) {
-      return event
-    },
-    _getEvents () {
+    getEvents () {
       return this.events
     },
     save () {
-      return saveScheduleEvents(this.scheduleOptions, this._getEvents())
+      return saveScheduleEvents(this.scheduleOptions, this.getEvents())
     },
     submit () {
-      submitSchedule(this.scheduleOptions, this._getEvents()).then(() => {
+      submitSchedule(this.scheduleOptions, this.getEvents()).then(() => {
         this.editable = false
         this.$emit('submit')
       })

+ 1 - 1
src/constant.js

@@ -21,7 +21,7 @@ export const State = {
 export const ScheduleType = {
   CALENDAR: 1,
   RECUR: 2,
-  COMPLEX: __SUPPORT_COMPLEX__ ? 3 : 1
+  COMPLEX: 3
 }
 
 export const PublishType = {

+ 8 - 3
src/router/index.js

@@ -138,9 +138,7 @@ export const asyncRoutes = [
       {
         name: 'schedule-deploy',
         path: 'deploy',
-        component: __SUPPORT_COMPLEX__
-          ? () => import('@/views/schedule/deploy/complex')
-          : () => import('@/views/schedule/deploy/index'),
+        component: () => import('@/views/schedule/deploy/complex'),
         access: Access.PUBLISH_CALENDAR,
         meta: { title: '排期发布' }
       },
@@ -255,6 +253,13 @@ export const asyncRoutes = [
         access: Access.MANAGE_PRODUCTS,
         meta: { title: '产品管理' }
       },
+      {
+        name: 'transfer-deploy',
+        path: 'deploy',
+        component: () => import('@/views/platform/transfer/deploy/index'),
+        access: Access.SUPER_ADMIN,
+        meta: { title: '旧排期发布' }
+      },
       {
         name: 'transfer',
         path: 'transfer',

+ 1 - 1
src/utils/event.js

@@ -112,7 +112,7 @@ function canContinue (startDate, endDate) {
   return !endDate || startDate < endDate
 }
 
-function isIn (target, start, end) {
+export function isIn (target, start, end) {
   return target >= start && (!end || target < end)
 }
 

+ 121 - 8
src/views/schedule/deploy/index.vue → src/views/platform/transfer/deploy/index.vue

@@ -14,7 +14,13 @@
         <div class="l-flex__auto" />
         <button
           class="l-flex__none c-sibling-item o-button"
-          @click="publish"
+          @click="onResolvePublish"
+        >
+          审核
+        </button>
+        <button
+          class="l-flex__none c-sibling-item o-button"
+          @click="onPublish"
         >
           发布
         </button>
@@ -23,7 +29,7 @@
         <div
           class="o-choose-button"
           :class="{ ready: isReady }"
-          @click="toChoose"
+          @click="onChoose"
         >
           {{ tip }}
         </div>
@@ -117,16 +123,55 @@
         />
       </c-table>
     </el-dialog>
+    <el-dialog
+      title="发布审核"
+      :visible.sync="reviewing"
+      custom-class="c-dialog"
+    >
+      <c-table
+        v-if="reviewing"
+        :options="publishOptions"
+        @pagination="getPublishes"
+      >
+        <el-table-column
+          prop="programCalendarName"
+          label="名称"
+          align="center"
+          show-overflow-tooltip
+        />
+        <el-table-column
+          prop="createTime"
+          label="创建时间"
+          align="center"
+          show-overflow-tooltip
+        />
+        <el-table-column
+          label="操作"
+          align="center"
+          width="180"
+        >
+          <template v-slot="scope">
+            <div
+              class="c-table__btn u-pointer"
+              @click.stop="onResolve(scope.row)"
+            >
+              通过
+            </div>
+          </template>
+        </el-table-column>
+      </c-table>
+    </el-dialog>
   </wrapper>
 </template>
 
 <script>
 import { getSchedules } from '@/api/calendar'
-import { publishSchedule } from '@/api/publish'
+import { messageSend } from '@/api/base'
 import {
   State,
   ScheduleType
 } from '@/constant'
+import request from '@/utils/request'
 import {
   createListOptions,
   parseTime
@@ -150,7 +195,9 @@ export default {
         { value: ScheduleType.CALENDAR, label: '日程' },
         { value: ScheduleType.RECUR, label: '轮播' }
       ],
-      isFilterSchedules: true
+      isFilterSchedules: true,
+      reviewing: false,
+      publishOptions: null
     }
   },
   computed: {
@@ -263,13 +310,13 @@ export default {
         options.loading = false
       })
     },
-    toChoose () {
+    onChoose () {
       if (!this.isReady) {
         return
       }
       this.scheduleOptions = createListOptions({
         status: State.RESOLVED,
-        type: ScheduleType.CALENDAR
+        type: ScheduleType.RECUR
       })
       this.isFilterSchedules = true
       this.getSchedules()
@@ -286,7 +333,7 @@ export default {
         this.endDateTime = ''
       }
     },
-    publish () {
+    onPublish () {
       switch (this.status) {
         case -3:
         case -2:
@@ -322,13 +369,29 @@ export default {
           return
         }
       }
-      publishSchedule(this.schedule, this.selectedDevices, {
+      this.publishSchedule(this.schedule, this.selectedDevices, {
         startDateTime: this.startDateTime,
         endDateTime: this.endDateTime ? parseTime(new Date(this.endDateTime).getTime() + 1000, '{y}-{m}-{d} {h}:{i}:{s}') : ''
       }).then(() => {
         this.schedule = null
       })
     },
+    publishSchedule ({ id, type, name }, devices, options) {
+      return this.$confirm(`确定对设备 ${devices.map(device => device.name)} 发布排期 ${name}?`).then(() => {
+        const data = {
+          programCalendarId: id,
+          deviceIds: devices.map(device => device.id)
+        }
+        if (type === ScheduleType.RECUR) {
+          Object.assign(data, options)
+        }
+        return messageSend({
+          url: '/orchestration/calendarRelease',
+          method: 'POST',
+          data
+        }, '发布')
+      })
+    },
     isDisableDate (date) {
       return date < this.$min || date > this.$max
     },
@@ -346,6 +409,56 @@ export default {
           this.endDateTime = this.startDateTime
         }
       }
+    },
+    onResolvePublish () {
+      this.publishOptions = createListOptions({ status: State.SUBMITTED })
+      this.getPublishes()
+      this.reviewing = true
+    },
+    getPublishes () {
+      const options = this.publishOptions
+      options.error = false
+      options.loading = true
+      this.getPublishesApi(options.params).then(
+        ({ data, totalCount }) => {
+          options.list = data.map(this.transform)
+          options.totalCount = totalCount
+        },
+        () => {
+          options.error = true
+          options.list = []
+        }
+      ).finally(() => {
+        options.loading = false
+      })
+    },
+    getPublishesApi (query) {
+      const { pageNum: pageIndex, pageSize, ...params } = query
+      return request({
+        url: '/orchestration/calendarRelease/page',
+        method: 'GET',
+        params: {
+          pageIndex, pageSize,
+          ...params
+        }
+      })
+    },
+    onResolve ({ id }) {
+      return request({
+        url: `/orchestration/calendarRelease/${id}/approval`,
+        method: 'POST',
+        data: { remark: '' }
+      }).then(() => {
+        this.$message({
+          type: 'success',
+          message: '审核通过成功'
+        })
+        const options = this.publishOptions
+        if (options.list.length === 1 && options.params.pageNum > 1) {
+          options.params.pageNum -= 1
+        }
+        this.getPublishes()
+      })
     }
   }
 }

+ 1 - 3
vue.config.js

@@ -16,9 +16,7 @@ const features = {
   // 传感器
   __SENSOR__: isEnable('__SENSOR__'),
   // 摄像头
-  __CAMERA__: isEnable('__CAMERA__'),
-  // 周期性排期
-  __SUPPORT_COMPLEX__: !isProd || isStaging
+  __CAMERA__: isEnable('__CAMERA__')
 }
 
 const copyFiles = [