SOURCE

console 命令行工具 X clear

                    
>
console
new Vue({
  el: '#queue',
  data: {
    queue: [],
    today: new Date(),
    oclock: false,
    number: 2,
  },

  computed: {
    timeString: function () {
      return `${`0${this.today.getHours()}`.slice(-2)} ${`0${this.today.getMinutes()}`.slice(-2)}`;
    },
  },

  methods: {
    beforeEnterQueue (item) {
      item.style.opacity = 0;
    },

    enterQueue (item, done) {
      Velocity(item, {
        opacity: 1,
      }, {
        duration: 900,
        complete: () => {
          item.classList.add('swinging');
          done();
        },
      });
    },

    leaveQueue (item, done) {
      item.classList.remove('swinging');
      item.style.position = 'absolute';

      Velocity(item, {
        opacity: 0,
      }, {
        duration: 333,
        complete: done,
      });
    },

    generate (template = 'cc dddd cc') {
      this.number = this.number + Math.random() * 20 >> 0;
      return {
        left: this.number,
        symbol: ['↥', '↧'][Math.random() * 7 >> 0],
        plate: template.split('').reduce((a, v) => a + (_ => {
          switch (v) {
            case 'c': return String.fromCharCode(65 + Math.random() * 1e4 % 26 >> 0);
            case 'd': return Math.random() * 10 >> 0;
            default: return v;
          }
        })(), ''),
      };
    },

    simulate() {
      if (
        this.queue.length < 5 && (
          this.queue.length === 0 || Math.random() >= 0.5
        )
      ) this.queue.splice(Math.random() * 10, 0, this.generate());
      else this.queue.splice(Math.random() * 10, 1);

      setTimeout(this.simulate, 4000 + Math.random() * 2e3);
    },
  },

  mounted: function () {
    setInterval(() => this.today = new Date(), 1000);
    setTimeout(() => {
      this.oclock = true;
      this.simulate();
    }, 600);
  },
});
#screen
  #reflex
  #queue
    span#clock(:class="{ oclock }") {{ timeString }}

    transition-group(
      tag="ul"
      :css="false"
      @before-enter="beforeEnterQueue"
      @enter="enterQueue"
      @leave="leaveQueue"
    )
      li(v-for="(item, index) in queue"
        class="queued-item"
        :key="item.plate"
        :data-index="index"
        :data-reverse-index="queue.length - index - 1"
      )
        .item-content
          .plate {{ item.plate }}
          .countdown.blinking {{ item.left }}
            .symbol {{ item.symbol }}

#title Queue Management Display app made for the local car service. Used Node, Vue, 1C:Enterprise.
html, body {
  height: 100%;
}

body {
  display: flex;
  overflow: hidden;
  font-size: 1.44em;
  font-family: 'Open Sans', sans-serif;
  background: linear-gradient(45deg, #F2E635 30%, #F2BE22 90%);
}

#title {
  position: absolute;
  opacity: 0.5;
  font-size: 0.5em;
  width: 50%;
}

#screen {
  margin-top: 4em;
  margin-left: 1em;
  margin-right: 1em;
  width: 20em;
  border: solid #222 0.12em;
  border-left-width: 0.24em;
  border-top-left-radius: 0.2em;
  border-top-right-radius: 0.2em;
  border-bottom: none;
  overflow: hidden;
  margin-left: auto;
  margin-right: auto;
  transform: skewY(2deg) scaleX(0.98);
  box-shadow: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23);
  transition: all ease 0.33s;

  &:hover {
    transform: skewY(0.1deg);
    border-left-width: 0.12em;
  }
}

#reflex {
  width: 5em;
  height: 0.7em;
  position: absolute;
  transform: skewY(-5deg) translateX(-1em) translateY(1em);
  border-bottom: solid 12em #ffffff15;
  border-top: solid 6em #ffffff15;
  z-index: 1;
  transition: transform ease 0.33s;
}

#screen:hover > #reflex {
  transform: skewY(-7deg) translateX(2.3em) translateY(1em);
}

#queue {
  background-color: #4A44F2;
  min-height: 100%;
  border: inset 0.05em #ffffff55;
  animation: rainbow 20s ease-in-out infinite;

  ul {
    transform-style: preserve-3d;
    perspective: 190vw;
    padding-left: 0;

    > li.queued-item {
      margin-bottom: 0.5em;
      transition: transform ease-in 500ms;
      width: 100%;

      > .item-content {
        display: flex;
        align-items: baseline;
        justify-content: space-between;
        padding-left: 1.8em;
        padding-right: 1.8em;
      }
    }
  }
}

#clock {
  position: relative;
  left: 15%;
  top: -2em;
  padding: 0.25em 0.33em;
  border-radius: 0 0 0.1em 0.1em;
  background-color: #222;
  color: white;
  transition: top ease 1s;
  
  &.oclock {
    top: 0;
  }

  &::after {
    content: ":";
    position: absolute;
    right: 2.567ch;
    bottom: .5ch;
    animation: blinking 1s ease infinite;
  }
}

$swings: 8;
$swinging-degree: 12deg;

.plate {
  font-size: 2em;
  line-height: 90%;
  word-break: break-word;
  margin-right: 0.25em;
  padding: 0.27em 0.33em;
  border-radius: 0.1em;
  color: #333;
  background-color: white;
  text-transform: uppercase;
  box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);
  animation: swinging 1s * $swings cubic-bezier(0.45, 0.05, 0.55, 0.95);
  animation-play-state: paused;
}

.swinging .plate {
  animation-play-state: running;
}

@keyframes swinging {
  @for $i from 1 through 2 * $swings - 1 {
    #{100% / $swings * $i / 2} {
      transform: rotateY($swinging-degree * (2 * $swings - $i) / (2 * $swings - 1) * ($i % 2 * 2 - 1));
    }
  }
}

.countdown {
  font-size: 1.5em;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.06), 0 1px 2px rgba(0, 0, 0, 0.12);
  color: white;

  .symbol {
    float: right;
    margin-right: -0.67em;
    color: #F2E635;
  }
}

@keyframes rainbow {
  33% { background-color: #4A44F2; }
  67% { background-color: #842EA0; }
}

.blinking {
  animation: blinking 1s ease 5;
}

@keyframes blinking {
  50% { opacity: 0; }
}

本项目引用的自定义外部资源