[: currentTime | date:'mm:ss' :] [: timeLeft | date:'mm:ss' :]
结果
HTML
CSS
JS
运行
整页预览
<!doctype html>
<html lang="zh-cn">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>搞事情</title>
    <link rel="stylesheet" href="lib/normalize.css">
    <link rel="stylesheet" href="css/main.css">
</head>
<body>
<div id="main">
    <div class="navbar">
        <h1>表事情</h1>
    </div>
    <div class="header">
        开黑了么就写作业?
    </div>
    <form @submit.prevent="merge" id="task-form">
        <div class="wrap">
            <input v-model="current.title" id="task-input" type="text" autocomplete="off">
            <div v-if="current.id" class="detail">
                <textarea v-model="current.desc"></textarea>
                <input v-model="current.alert_at" type="datetime-local">
                <button class="primary" type="submit">submit</button>
            </div>
        </div>
    </form>
    <div class="task-list">
        <div class="wrap">
            <div class="segment-title">未完成</div>
            <task :todo="todo"
                  v-if="!todo.completed"
                  v-for="(todo, index) in list"
            ></task>
        </div>
    </div>
    <div class="task-list">
        <div class="wrap">
            <div class="segment-title">已完成</div>
            <div v-if="todo.completed" v-for="(todo, index) in list" class="item completed">
                <div @click="toggle_complete(todo.id)" class="toggle-complete"></div>
                {{todo.title}}
                <button @click="remove(todo.id)">删除</button>
            </div>
        </div>
    </div>
</div>
<audio id="alert-sound">
    <source src="./sound/alert.mp3">
</audio>
<template id="task-tpl">
    <div class="item">
        <div @click="action('toggle_complete', todo.id)" class="toggle-complete"></div>
        <span class="title">{{todo.title}}</span>
        <button @click="action('remove', todo.id)">删除</button>
        <button @click="action('set_current', todo)">更新</button>
        <button @click="action('toggle_detail', todo.id)">详情</button>
        <div v-if="todo.show_detail" class="detail">
            {{todo.desc || '暂无详情'}}
        </div>
    </div>
</template>
<script src="https://cdn.bootcss.com/vue/2.4.4/vue.min.js"></script>
</body>
</html>
* {
    -webkit-box-sizing: border-box;
    -moz-box-sizing: border-box;
    box-sizing: border-box;
    position: relative;
}

body {
    color: #444;
}

input, button, textarea {
    border: 1px solid rgba(0, 0, 0, .1);
    border-radius: 3px;
    padding: 5px 10px;
}

input, textarea {
    outline: none;
    display: block;
    width: 100%;
    margin-bottom: 10px;
    -webkit-box-shadow: 0 2px 3px rgba(0, 0, 0, .1);
    -moz-box-shadow: 0 2px 3px rgba(0, 0, 0, .1);
    box-shadow: 0 2px 3px rgba(0, 0, 0, .1);
}

input:focus, textarea:focus {
    border: 1px solid #db4c3f;
}

button.primary {
    background: #db4c3f;
    color: #f7f3ea;
}

.wrap {
    max-width: 500px;
    margin: 0 auto;
    padding-left: 10px;
    padding-right: 10px;
}

.navbar, .header {
    text-align: center;
}

.navbar {
    background: #db4c3f;
    color: #f7f3ea;
    padding: 10px 0;
}

.navbar h1 {
    font-size: 24px;
    margin: 0;
}

.header {
    padding: 20px 0;
    color: #555;
    font-size: 18px;
}

#alert-sound {
    display: none;
}

.task-list .item {
    padding: 5px;
    -webkit-border-radius: 3px;
    -moz-border-radius: 3px;
    border-radius: 3px;
}

.task-list .item button {
    display: none;
    font-size: 80%;
    background: #fff;
}

.task-list .item:hover {
    background: rgba(0, 0, 0, .03);
}

.task-list .item:hover button {
    display: inline-block;
}

.task-list .item .title {
    padding: 10px;
}

.task-list .item .detail {
    display: block;
    font-size: 80%;
    color: #666;
}

.task-list .item > * {
    padding: 5px;
    display: inline-block;
    line-height: 1.2;
    vertical-align: middle;
}

.toggle-complete {
    width: 30px;
    height: 30px;
    background: #50beff;
    border: 1px solid #46a9e4;
    -webkit-border-radius: 50%;
    -moz-border-radius: 50%;
    border-radius: 50%;
}

.toggle-complete:hover {
    background: #3e9bd2;
    border-color: #3587b7;
}

.completed .toggle-complete {
    background: #ccc;
    border-color: #bbb;
}

.segment-title {
    color: #aaa;
    margin-top: 15px;
    margin-bottom: 5px;
    font-weight: lighter;
}
/* myStorage.js */
;(function () {
  window.ms = {
    set: set,
    get: get,
  };

  function set(key, val) {
    localStorage.setItem(key, JSON.stringify(val));
  }

  function get(key) {
    var json = localStorage.getItem(key);
    if (json) {
      return JSON.parse(json);
    }
  }
})();

/* main.js */
;(function () {
  'use strict';

  var Event = new Vue();

  var alert_sound = document.getElementById('alert-sound');

  function copy(obj) {
    return Object.assign({}, obj);
  }

  Vue.component('task', {
    template: '#task-tpl',
    props: ['todo'],
    methods: {
      action: function (name, params) {
        Event.$emit(name, params);
      }
    }
  })

  new Vue({
    el: '#main',
    data: {
      list: [],
      last_id: 0,
      current: {}
    },

    mounted: function () {
      var me = this;
      this.list = ms.get('list') || this.list;
      this.last_id = ms.get('last_id') || this.last_id;

      setInterval(function () {
        me.check_alerts();
      }, 1000);

      Event.$on('remove', function (id) {
        if (id) {
          me.remove(id);
        }
      });

      Event.$on('toggle_complete', function (id) {
        if (id) {
          me.toggle_complete(id);
        }
      });

      Event.$on('set_current', function (id) {
        if (id) {
          me.set_current(id);
        }
      });

      Event.$on('toggle_detail', function (id) {
        if (id) {
          me.toggle_detail(id);
        }
      });
    },

    methods: {
      check_alerts: function () {
        var me = this;
        this.list.forEach(function (row, i) {
          var alert_at = row.alert_at;
          if (!alert_at || row.alert_confirmed) return;

          var alert_at = (new Date(alert_at)).getTime();
          var now = (new Date()).getTime();

          if (now >= alert_at) {
            alert_sound.play();
            var confirmed = confirm(row.title);
            Vue.set(me.list[i], 'alert_confirmed', confirmed);
          }
        })
      },

      merge: function () {
        var is_update, id;
        is_update = id = this.current.id;

        if (is_update) {
          var index = this.find_index(id);
          Vue.set(this.list, index, copy(this.current));
        } else {
          var title = this.current.title;
          if (!title && title !== 0) return;

          var todo = copy(this.current);
          this.last_id++;
          ms.set('last_id', this.last_id);
          todo.id = this.last_id;
          this.list.push(todo);
        }

        this.reset_current();
      },

      toggle_detail: function (id) {
        var index = this.find_index(id);
        Vue.set(this.list[index], 'show_detail', !this.list[index].show_detail)
      },

      remove: function (id) {
        var index = this.find_index(id);
        this.list.splice(index, 1);
      },

      next_id: function () {
        return this.list.length + 1;
      },

      set_current: function (todo) {
        this.current = copy(todo);
      },

      reset_current: function () {
        this.set_current({});
      },

      find_index: function (id) {
        return this.list.findIndex(function (item) {
          return item.id == id;
        })
      },

      toggle_complete: function (id) {
        var i = this.find_index(id);
        Vue.set(this.list[i], 'completed', !this.list[i].completed);
      }
    },

    watch: {
      list: {
        deep: true,
        handler: function (n, o) {
          if (n) {
            ms.set('list', n);
          } else {
            ms.set('list', []);
          }
        }
      }

    }
  });

})();
登录后评论