Преглед изворни кода

feat: support program conflict handling in scheduling

Casper Dai пре 3 година
родитељ
комит
fdf51c6bc3
2 измењених фајлова са 181 додато и 46 уклоњено
  1. 177 46
      src/components/Schedule/ScheduleCalendar/index.vue
  2. 4 0
      src/scss/bem/_utility.scss

+ 177 - 46
src/components/Schedule/ScheduleCalendar/index.vue

@@ -77,9 +77,7 @@
               @edit="editProgram"
               @remove="removeProgram"
             >
-              <div
-                class="c-day-card__count u-ellipsis u-pointer"
-              >
+              <div class="c-day-card__count u-ellipsis u-pointer">
                 +{{ day.programs.length - 1 }}
               </div>
             </pop-list>
@@ -93,7 +91,6 @@
       custom-class="c-dialog"
       :close-on-click-modal="false"
       :before-close="handleCloseEditDialog"
-      append-to-body
     >
       <schedule-edit
         v-if="program"
@@ -118,6 +115,44 @@
         </button>
       </template>
     </el-dialog>
+    <el-dialog
+      title="冲突提醒"
+      :visible.sync="conflicting"
+      custom-class="c-dialog"
+      :close-on-click-modal="false"
+      append-to-body
+    >
+      <div class="has-bottom-padding u-bold">
+        <div>局部覆盖:被冲突的节目的时间段将被切割</div>
+        <div>覆盖:被冲突的节目将被移除</div>
+      </div>
+      <div
+        v-for="conflict in conflicts"
+        :key="conflict.key"
+      >
+        与 {{ conflict.target.name }} 于 {{ conflict.start }} ~ {{ conflict.end }} 冲突
+      </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>
+    </el-dialog>
   </schedule-wrapper>
 </template>
 
@@ -148,7 +183,9 @@ export default {
       current: null,
       editing: false,
       program: null,
-      dirty: false
+      dirty: false,
+      conflicting: false,
+      conflicts: []
     }
   },
   computed: {
@@ -203,16 +240,46 @@ export default {
       this.current = current
       this._calculate()
     },
+    fix () {
+      let endTime = Date.now() + 60000
+      if (this.programs.some(({ endDateTime }) => new Date(endDateTime).getTime() <= endTime)) {
+        return this.$confirm(
+          '存在过期或将要过期(<=60s)节目,是否移除?',
+          '提示',
+          {
+            type: 'warning',
+            distinguishCancelAndClose: true,
+            confirmButtonText: '移除',
+            cancelButtonText: '保留'
+          }
+        ).then(() => {
+          endTime = Date.now() + 60000
+          this.scheduleOptions.programs = this.programs.filter(({ endDateTime }) => new Date(endDateTime).getTime() > endTime)
+          if (this._compare(this.current, this._transform(endTime), false) <= 0) {
+            this._calculate()
+          }
+          return this.programs
+        }, action => {
+          if (action === 'cancel') {
+            return this.programs
+          }
+          return Promise.reject()
+        })
+      }
+      return Promise.resolve(this.programs)
+    },
     onSave () {
-      this.save(this.programs.map(({ id, name, startDateTime, endDateTime }) => {
-        endDateTime = this._offsetDateTime(endDateTime, 1)
-        return {
-          programId: id,
-          programName: name,
-          startDateTime, endDateTime
-        }
-      })).then(() => {
-        this.dirty = false
+      this.fix().then(programs => {
+        this.save(programs.map(({ id, name, startDateTime, endDateTime }) => {
+          endDateTime = this._offsetDateTime(endDateTime, 1)
+          return {
+            programId: id,
+            programName: name,
+            startDateTime, endDateTime
+          }
+        })).then(() => {
+          this.dirty = false
+        })
       })
     },
     _transformPrograms (programs) {
@@ -223,13 +290,13 @@ export default {
     },
     _transformProgram ({ programId, programName, startDateTime, endDateTime }) {
       // 服务器保存的时间采用左闭右开原则
-      endDateTime = this._offsetDateTime(endDateTime, -1)
+      return this._createProgram(programId, programName, startDateTime, this._offsetDateTime(endDateTime, -1))
+    },
+    _createProgram (id, name, startDateTime, endDateTime) {
       return {
+        id, name, startDateTime, endDateTime,
         key: Math.random().toString(16).slice(2),
-        id: programId,
-        name: programName,
-        days: this._getDays(startDateTime, endDateTime),
-        startDateTime, endDateTime
+        days: this._getDays(startDateTime, endDateTime)
       }
     },
     _offsetDateTime (dateTime, val) {
@@ -265,6 +332,9 @@ export default {
       })
     },
     _removeProgram (key) {
+      if (!key) {
+        return
+      }
       const index = this.programs.findIndex(program => program.key === key)
       this.programs.splice(index, 1)
       if (index === 0) {
@@ -309,8 +379,7 @@ export default {
       }
     },
     saveProgram () {
-      const program = this.$refs.editor.getValue()
-      const { id, startDateTime, endDateTime } = program
+      const { id, name, startDateTime, endDateTime } = this.$refs.editor.getValue()
       if (!startDateTime || !endDateTime) {
         this.$message({
           type: 'warning',
@@ -333,41 +402,103 @@ export default {
         this.handleCloseEditDialog()
         return
       }
-      const conflict = this.checkConflict(program, this.program.key)
-      if (conflict) {
-        this.$message({
-          type: 'warning',
-          message: `与 ${conflict.name} ${conflict.startDateTime}-${conflict.endDateTime} 冲突`
-        })
-        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
-      program.key = Math.random().toString(16).slice(2)
-      program.days = this._getDays(program.startDateTime, program.endDateTime)
-      if (this.program.id) {
-        this._removeProgram(this.program.key)
-      }
-      const timestamp = new Date(startDateTime).getTime()
+      this._calculate()
+      this.handleCloseEditDialog()
+    },
+    _mergeOrAdd () {
+      let program = this._program
+      const timestamp = new Date(program.startDateTime).getTime()
       const insertIndex = this.programs.findIndex(item => new Date(item.startDateTime).getTime() > timestamp)
+      let pre = null
+      let next = null
+      let add = true
       if (~insertIndex) {
-        this.programs.splice(insertIndex, 0, program)
+        pre = this.programs[insertIndex - 1]
+        next = this.programs[insertIndex]
       } else {
-        this.programs.push(program)
+        pre = this.programs[this.programs.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.programs.splice(insertIndex, 0, program)
+        } else {
+          this.programs.push(program)
+        }
       }
-      this._calculate()
-      this.handleCloseEditDialog()
     },
-    checkConflict (program, key) {
+    _checkConflict (program, key) {
       if (this.programs.length) {
         const cstartDateTime = new Date(program.startDateTime)
         const cendDateTime = new Date(program.endDateTime)
-        return this.programs.find(item => {
+        this.conflicts = this.programs.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 null
+      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.programs.findIndex(({ key }) => key === target.key)
+            this.programs.splice(index + 1, 0, this._createProgram(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()
     },
     handleCloseEditDialog () {
       this.program = null
@@ -411,7 +542,7 @@ export default {
     },
     _calculate () {
       const { year, month } = this.current
-      const monthDays = this._getPreentMonthDays(year, month).concat(this._currentMonthDays(year, month))
+      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 = []
@@ -434,7 +565,7 @@ export default {
       }
       return dateArr
     },
-    _getPreentMonthDays (year, month) {
+    _getPresentMonthDays (year, month) {
       const dateArr = []
       const week = new Date(year, month, 1).getDay()
       const fill = week === 0 ? 7 : week
@@ -522,7 +653,7 @@ export default {
   &__content {
     min-height: 0;
     border: 1px solid $gray--light;
-    overflow-y: auto
+    overflow-y: auto;
   }
 
   &__row + &__row {
@@ -545,11 +676,11 @@ export default {
       cursor: not-allowed;
     }
 
-    &.day.enabled{
+    &.day.enabled {
       cursor: pointer;
 
       &:hover {
-        transition: background-color .4s;
+        transition: background-color 0.4s;
         background-color: #f2f8fe;
       }
     }

+ 4 - 0
src/scss/bem/_utility.scss

@@ -49,6 +49,10 @@
   color: $gray--dark;
 }
 
+.u-bold {
+  font-weight: bold;
+}
+
 .u-text-center {
   text-align: center;
 }