瀏覽代碼

refactor: schedule timeline

Casper Dai 3 年之前
父節點
當前提交
0ebe60d047

+ 1 - 1
src/components/Schedule/components/ScheduleWrapper.vue

@@ -106,7 +106,7 @@ export default {
 <style lang="scss" scoped>
 .c-schedule-wrapper {
   &__header {
-    border-bottom: 1px solid #edf0f6;
+    border-bottom: 1px solid $border;
   }
 
   &__tip {

+ 0 - 1
src/router/index.js

@@ -166,7 +166,6 @@ export const asyncRoutes = [
     meta: { title: '大屏设备', icon: 'dm' },
     children: [
       {
-        dev: true,
         name: 'schedule-timeline',
         path: 'timeline',
         component: () => import('@/views/schedule/timeline/index'),

+ 2 - 0
src/scss/helpers/_variables.scss

@@ -21,5 +21,7 @@ $error--dark: #e51414;
 $info: #d5d9e4;
 $info--dark: #8e929c;
 
+$border: #edf0f6;
+
 $spacing: 16px;
 $radius: 8px;

+ 1 - 5
src/views/bigscreen/ast/index.vue

@@ -1,7 +1,6 @@
 <script>
 import '@/scss/iconfont/iconfont.css'
 
-import { mapGetters } from 'vuex'
 import { getProgram } from '@/api/program'
 import {
   State,
@@ -30,9 +29,6 @@ export default {
       activeComponent: null
     }
   },
-  computed: {
-    ...mapGetters(['accesses'])
-  },
   watch: {
     id: {
       handler () {
@@ -55,7 +51,7 @@ export default {
           }
 
           if (status === State.READY) {
-            if (this.accesses.has(Access.MANAGE_CALENDAR)) {
+            if (this.accessSet.has(Access.MANAGE_CALENDAR)) {
               this.activeComponent = 'Designer'
             } else {
               this.$message({

+ 1 - 1
src/views/device/detail/components/Sensor.vue

@@ -167,7 +167,7 @@ export default {
     padding: 8px 0;
     color: $info--dark;
     font-size: 14px;
-    border-bottom: 1px solid #edf0f6;
+    border-bottom: 1px solid $border;
   }
 
   &__name {

+ 206 - 126
src/views/schedule/timeline/index.vue

@@ -15,7 +15,7 @@
       <div class="l-flex__auto l-flex--col">
         <div class="l-flex__none c-device-detail__name u-ellipsis">{{ deivceName }}</div>
         <template v-if="programProxy">
-          <div class="l-flex__none c-device-detail__program">
+          <div class="l-flex__none c-device-detail__program u-ellipsis">
             <span
               class="u-pointer"
               @click="onView"
@@ -27,7 +27,7 @@
     </div>
     <div
       v-loading="deviceOptions.loading"
-      class="l-flex__auto l-flex--col c-timeline"
+      class="l-flex__fill l-flex--col c-timeline"
     >
       <div class="l-flex__none l-flex--row has-bottom-padding">
         <div class="l-flex__auto c-sibling-item" />
@@ -69,46 +69,56 @@
           搜索
         </button>
       </div>
-      <div class="l-flex__none l-flex--col u-relative">
-        <div class="l-flex c-timeline__row header">
-          <div class="l-flex__none c-timeline__col c-timeline__left">设备</div>
-          <div class="l-flex__auto l-flex--row c-timeline__col">
-            <i
-              class="l-flex__none c-timeline__arrow el-icon-arrow-left u-pointer"
-              :class="{ display: canPrevious }"
-              @click="offsetTime(-1)"
-            />
-            <div class="l-flex__auto l-flex--row c-timeline__time">
-              <div
-                v-for="time in times"
-                :key="time"
-                class="l-flex__none"
-              >
-                {{ time }}
-              </div>
+      <div class="l-flex__none l-flex c-timeline__row header">
+        <div class="l-flex__none c-timeline__left">
+          <span class="o-priority priority3">高</span>
+          <span class="o-priority priority2">中</span>
+          <span class="o-priority priority1">低</span>
+        </div>
+        <div class="l-flex__auto l-flex--row c-timeline__right">
+          <i
+            class="l-flex__none c-timeline__arrow left el-icon-arrow-left u-pointer"
+            :class="{ display: canPrevious }"
+            @click="offsetTime(-1)"
+          />
+          <div class="l-flex__auto l-flex--row c-timeline__time">
+            <div
+              v-for="time in times"
+              :key="time"
+              class="l-flex__none"
+            >
+              {{ time }}
             </div>
-            <i
-              class="l-flex__none c-timeline__arrow display el-icon-arrow-right u-pointer"
-              @click="offsetTime(1)"
-            />
           </div>
+          <i
+            class="l-flex__none c-timeline__arrow right display el-icon-arrow-right u-pointer"
+            @click="offsetTime(1)"
+          />
         </div>
-        <div
-          v-for="item in deviceOptions.list"
-          :key="item.id"
-          class="l-flex c-timeline__row"
-          :class="{ selected: item.id === deviceId }"
-          @click="chooseProgramProxy(item)"
-        >
-          <div class="l-flex__none c-timeline__col c-timeline__left u-pointer">
-            <div class="u-ellipsis">{{ item.name }}</div>
-          </div>
-          <div class="l-flex__auto l-flex--row c-timeline__col u-relative">
-            <template v-if="item.options.loading">
-              <i class="el-icon-loading has-padding--h" />加载中...
-            </template>
-            <template v-else-if="item.options.error">
-              <div class="has-padding--h">
+      </div>
+      <div class="l-flex--col c-timeline__main u-relative">
+        <div class="l-flex__auto u-overflow-y--auto">
+          <div
+            v-for="item in deviceOptions.list"
+            :key="item.id"
+            class="l-flex c-timeline__row"
+            :class="{ selected: item.id === deviceId }"
+            @click="chooseProgramProxy(item)"
+          >
+            <div class="l-flex__none c-timeline__left u-relative u-pointer">
+              <div class="u-ellipsis">{{ item.name }}</div>
+            </div>
+            <div class="l-flex__auto l-flex--col c-timeline__right">
+              <div
+                v-if="item.options.loading"
+                class="l-flex--row c-timeline__programs"
+              >
+                <i class="el-icon-loading has-padding--h" />加载中...
+              </div>
+              <div
+                v-else-if="item.options.error"
+                class="l-flex--row c-timeline__programs has-padding--h"
+              >
                 <el-link
                   class="u-pointer"
                   type="warning"
@@ -117,39 +127,45 @@
                   出错了,点击重试
                 </el-link>
               </div>
-            </template>
-            <template v-else-if="item.options.list.length">
+              <template v-else-if="item.options.list.length">
+                <div
+                  v-for="(programs, index) in item.options.list"
+                  :key="index"
+                  class="c-timeline__programs l-flex--row u-relative"
+                >
+                  <div
+                    v-for="program in programs"
+                    :key="program.key"
+                    class="l-flex__none l-flex--row c-program u-pointer"
+                    :class="[{ 'selected': program.event.selected }, `priority${program.event.priority}`]"
+                    :style="program.style"
+                    @click.stop="chooseProgramProxy(item, program)"
+                  >
+                    <i
+                      class="l-flex__none c-program__img o-program"
+                      :style="program.event.style"
+                    />
+                    <div class="l-flex__auto">
+                      <auto-text
+                        class="c-program__time"
+                        :text="program.time"
+                        :tag="program.style.width"
+                      />
+                      <auto-text
+                        class="c-program__name"
+                        :text="program.event.target.name"
+                        :tag="program.style.width"
+                      />
+                    </div>
+                  </div>
+                </div>
+              </template>
               <div
-                v-for="(program, index) in item.options.list"
-                :key="index"
-                class="l-flex__none l-flex--row c-program u-pointer"
-                :class="{ 'selected': program.event && program.event.selected }"
-                :style="program.style"
-                @click.stop="chooseProgramProxy(item, program)"
+                v-else
+                class="l-flex--row c-timeline__programs has-padding--h"
               >
-                <i
-                  class="l-flex__none c-program__img o-program"
-                  :style="program.event.style"
-                />
-                <div class="l-flex__auto">
-                  <auto-text
-                    class="c-program__time"
-                    :text="program.time"
-                    :tag="program.style.width"
-                  />
-                  <auto-text
-                    class="c-program__name"
-                    :text="program.event.target.name"
-                    :tag="program.style.width"
-                  />
-                </div>
+                当前时段暂无节目
               </div>
-            </template>
-            <div
-              v-else
-              class="has-padding--h"
-            >
-              当前时段暂无节目
             </div>
           </div>
         </div>
@@ -207,7 +223,8 @@ import {
   getNearestHitDate,
   getStartDate,
   getFinishDate,
-  pickMin
+  pickMin,
+  pickMax
 } from '@/utils/event'
 import { EventCache } from '@/utils/cache'
 import {
@@ -346,12 +363,7 @@ export default {
         options.loading = false
       }).then(events => {
         const now = Date.now()
-        options.events = events.map(this.transformEvent).filter(({ until }) => !until || now < new Date(until)).sort((a, b) => {
-          if (b.priority === a.priority) {
-            return toDate(a.start) - toDate(b.start)
-          }
-          return b.priority - a.priority
-        })
+        options.events = this.transformEvents(events.map(this.transformEvent).filter(({ until }) => !until || now < new Date(until)))
         this.calcEvents(options)
       }, () => {
         options.error = true
@@ -375,6 +387,19 @@ export default {
         }
       }
     },
+    transformEvents (events) {
+      const map = {}
+      for (let i = 0; i < events.length; i++) {
+        const event = events[i]
+        if (!map[event.priority]) {
+          map[event.priority] = []
+        }
+        map[event.priority].push(event)
+      }
+      return Object.keys(map)
+        .sort((a, b) => a > b ? -1 : 1)
+        .map(key => map[key].sort((a, b) => toDate(a.start) - toDate(b.start)))
+    },
     getTime (event) {
       const { freq, start, until, byDay, startTime, endTime } = event
       switch (freq) {
@@ -391,7 +416,7 @@ export default {
       return date < min || date > max
     },
     initTimes (date) {
-      date = toDate(date)
+      date = pickMax(toDate(date), new Date())
       this.current = toZeroPoint(date)
       this.startHour = Math.min(20, date.getHours())
       this.refreshTimes(this.startHour)
@@ -416,7 +441,7 @@ export default {
         this.style = null
       } else {
         this.style = {
-          width: `${Math.min(100, (now - this.$startDateTime) / 144000)}%`
+          left: `${Math.min(100, (now - this.$startDateTime) / 144000)}%`
         }
       }
     },
@@ -428,10 +453,14 @@ export default {
     offsetTime (offset) {
       const next = this.startHour + offset
       if (offset < 0) {
-        const timestamp = this.$startDateTime.getTime() + next * 3600000
-        if (timestamp >= Date.now()) {
+        const timestamp = this.current.getTime() + next * 3600000
+        const now = new Date()
+        now.setMinutes(0)
+        now.setSeconds(0)
+        now.setMilliseconds(0)
+        if (timestamp >= now) {
           if (next < 0) {
-            this.initTimes(new Date(timestamp), true)
+            this.initTimes(timestamp)
           } else {
             this.refreshTimes(this.startHour = next)
           }
@@ -447,12 +476,8 @@ export default {
     onTimeChange (val) {
       this.initTimes(val)
     },
-    calcEvents (options) {
-      if (options.loading || options.error) {
-        return
-      }
+    calcSamePriorityEvents (events) {
       const total = 144000
-      const events = options.events
       const arr = []
       for (let i = 0; i < events.length; i++) {
         const event = events[i]
@@ -466,6 +491,7 @@ export default {
           const startDate = getStartDate(event, hit)
           const endDate = getFinishDate(event, hit)
           arr.push({
+            key: event.target.id,
             event,
             time: `${toDateStr(startDate)} ${toTimeStr(startDate)} - ${toDateStr(endDate)} ${toTimeStr(endDate)}`,
             style: {
@@ -476,9 +502,18 @@ export default {
           })
           event.invoke?.()
           event.img?.()
+          if (endDate >= this.$endDateTime) {
+            break
+          }
         }
       }
-      options.list = arr
+      return arr
+    },
+    calcEvents (options) {
+      if (options.loading || options.error) {
+        return
+      }
+      options.list = options.events.map(this.calcSamePriorityEvents).filter(events => events.length)
     },
     chooseProgramProxy (device, programProxy) {
       this.device = device
@@ -550,27 +585,31 @@ export default {
 .c-timeline {
   min-height: 400px;
 
-  &__row {
-    height: 56px;
+  &__main {
+    flex: 0 1 auto;
+    min-height: 0;
+    border: 1px solid $border;
+  }
 
+  &__row {
     & + & {
-      margin-top: 1px;
+      border-top: 1px solid $border;
     }
   }
 
-  &__row.header &__col {
-    color: $black;
-    background-color: #f4f7fb;
-    user-select: none;
+  &__row.header {
+    .c-timeline__left {
+      border-right: none;
+    }
   }
 
-  &__row.header &__left {
-    justify-content: center;
-    padding: 0;
-    font-weight: bold;
+  &__row.header &__right {
+    color: $black;
+    user-select: none;
+    background-color: transparent;
   }
 
-  &__row.selected &__col {
+  &__row.selected &__left {
     color: #fff;
     background-color: #9fbfe8;
   }
@@ -585,32 +624,53 @@ export default {
     background-color: $blue;
   }
 
-  &__col {
-    position: relative;
-    color: $info--dark;
-    font-size: 14px;
-    line-height: 1;
-    background-color: #edf0f6;
-    overflow: hidden;
-  }
-
   &__left {
     display: inline-flex;
     align-items: center;
     width: 210px;
     padding: 0 10px 0 20px;
     margin-right: 1px;
+    color: $black;
     font-size: 16px;
+    background-color: #fafbfc;
+    border-right: 1px solid $border;
+  }
+
+  &__right {
+    position: relative;
+    color: $info--dark;
+    font-size: 14px;
+    line-height: 1;
+    overflow: hidden;
+  }
+
+  &__programs {
+    box-sizing: content-box;
+    height: 48px;
+
+    & + & {
+      border-top: 1px dashed $border;
+    }
   }
 
   &__arrow {
     visibility: hidden;
+    position: absolute;
+    top: 50%;
     padding: 6px;
-    margin: 0 16px;
     color: #fff;
     font-size: 12px;
     border-radius: 4px;
     background-color: $blue;
+    transform: translateY(-50%);
+
+    &.left {
+      left: 0;
+    }
+
+    &.right {
+      right: 0;
+    }
 
     &.display {
       visibility: visible;
@@ -618,15 +678,16 @@ export default {
   }
 
   &__time {
-    justify-content: space-between;
+    justify-content: space-around;
+    padding: 10px 0;
     color: $black;
-    font-size: 16px;
+    font-size: 14px;
   }
 
   &__line {
     position: absolute;
-    top: 0;
-    left: 211px;
+    top: -12px;
+    left: 210px;
     right: 0;
     bottom: 0;
     pointer-events: none;
@@ -637,13 +698,9 @@ export default {
     top: 0;
     left: 0;
     bottom: 0;
-    color: #e51414;
+    color: #ff0000;
     border-right: 1px solid currentColor;
-    background-image: linear-gradient(
-      -90deg,
-      rgba(#ff8a8a, 0.33) 0%,
-      rgba(#fff, 0) 100%
-    );
+    z-index: 999;
 
     &::after {
       content: "";
@@ -662,15 +719,12 @@ export default {
   position: absolute;
   top: 0;
   bottom: 0;
-  padding: 0 4px;
-  color: #fff;
-  font-size: 14px;
-  background-color: rgba($blue, 0.4);
+  font-size: 12px;
   overflow: hidden;
 
   &:hover {
-    color: $black;
-    background-color: $blue--light;
+    color: #fff;
+    background-color: rgba($blue, 0.4);
   }
 
   &.selected {
@@ -682,7 +736,7 @@ export default {
   &__img {
     width: 84px;
     height: 48px;
-    margin-right: $spacing;
+    margin-right: 8px;
     border-radius: 4px;
   }
 
@@ -691,6 +745,32 @@ export default {
   }
 }
 
+.o-priority {
+  display: inline-block;
+  padding: 4px;
+  font-size: 12px;
+  border-radius: 4px;
+
+  & + & {
+    margin-left: 4px;
+  }
+}
+
+.priority1 {
+  color: #8e929c;
+  background-color: #edf0f6;
+}
+
+.priority2 {
+  color: #7642fd;
+  background-color: #eae2fe;
+}
+
+.priority3 {
+  color: #ff2222;
+  background-color: #ffecec;
+}
+
 .o-program {
   background: url("~@/assets/program_bg.png") 0 0 / 100% 100% no-repeat;
 }