├── .gitignore ├── README.md ├── css ├── clearfix.css ├── reset-font-min.css ├── style.css └── style.less ├── gulpfile.js ├── index.html ├── js └── script.js ├── lib └── vue.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Todo List: Vue.js Example 2 | 利用 Vue.js 簡單實作一個 Todo List。 3 | 4 | [說明](https://cythilya.github.io/2017/03/07/todolist-vue-example/) 5 | -------------------------------------------------------------------------------- /css/clearfix.css: -------------------------------------------------------------------------------- 1 | .clearfix{zoom:1;} 2 | .clearfix:after{content:'.';display:block;clear:both;visibility:hidden;height:0;font-size:0;} -------------------------------------------------------------------------------- /css/reset-font-min.css: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2011, Yahoo! Inc. All rights reserved. 3 | Code licensed under the BSD License: 4 | http://developer.yahoo.com/yui/license.html 5 | version: 2.9.0 6 | */ 7 | body{font:13px/1.231 arial,helvetica,clean,sans-serif;*font-size:small;*font:x-small}select,input,textarea,button{font:99% arial,helvetica,clean,sans-serif}table{font-size:inherit;font:100%}pre,code,kbd,samp,tt{font-family:monospace;*font-size:108%;line-height:100%}/* 8 | Copyright (c) 2011, Yahoo! Inc. All rights reserved. 9 | Code licensed under the BSD License: 10 | http://developer.yahoo.com/yui/license.html 11 | version: 2.9.0 12 | */ 13 | html{color:#000;background:#FFF}body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,button,textarea,select,p,blockquote,th,td{margin:0;padding:0}table{border-collapse:collapse;border-spacing:0}fieldset,img{border:0}address,button,caption,cite,code,dfn,em,input,optgroup,option,select,strong,textarea,th,var{font:inherit}del,ins{text-decoration:none}li{list-style:none}caption,th{text-align:left}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal}q:before,q:after{content:''}abbr,acronym{border:0;font-variant:normal}sup{vertical-align:baseline}sub{vertical-align:baseline}legend{color:#000} -------------------------------------------------------------------------------- /css/style.css: -------------------------------------------------------------------------------- 1 | html{background:#41d2f2;font-family:'微軟正黑體',sans-serif}.todos{background:#fff;border-radius:10px;width:600px;margin:15px auto;padding:15px}.todo-heading{font-size:30px;margin:0 0 15px 0}.add-new-item{margin:0 0 15px 0;font-size:15px}.todo-list{font-size:15px;color:#525252}.todo-item{margin:0 0 15px 0}.msg{margin:0 0 15px 0;font-size:15px;color:#525252}.btn{border:1px solid #ccc;border-radius:10px;cursor:pointer;display:inline-block;margin:0 5px;padding:5px 10px;min-width:30px;text-align:center}.btn.active,.btn:hover{background:#ccc;color:#fff}.control{border-top:1px solid #ddd;color:#525252;font-size:15px;padding:15px 0 0 0}.mb{margin:0 0 15px 0}.completed{text-decoration:line-through}input[type=text]{border:1px solid #ddd;border-radius:10px;color:#525252;line-height:1.2;height:22px;padding:5px} -------------------------------------------------------------------------------- /css/style.less: -------------------------------------------------------------------------------- 1 | @space: 15px; 2 | 3 | html { 4 | background: #41D2F2; 5 | font-family: '微軟正黑體', sans-serif; 6 | } 7 | 8 | .todos { 9 | background: #fff; 10 | border-radius: 10px; 11 | width: 600px; 12 | margin: @space auto; 13 | padding: @space; 14 | } 15 | 16 | .todo-heading { 17 | font-size: 30px; 18 | margin: 0 0 @space 0; 19 | } 20 | 21 | .add-new-item { 22 | .mb; 23 | font-size: 15px; 24 | } 25 | 26 | .todo-list { 27 | font-size: 15px; 28 | color: #525252; 29 | } 30 | 31 | .todo-item { 32 | .mb; 33 | } 34 | 35 | .msg { 36 | .mb; 37 | font-size: 15px; 38 | color: #525252; 39 | } 40 | 41 | .btn { 42 | border: 1px solid #ccc; 43 | border-radius: 10px; 44 | cursor: pointer; 45 | display: inline-block; 46 | margin: 0px 5px; 47 | padding: 5px 10px; 48 | min-width: 30px; 49 | text-align: center; 50 | &:hover, &.active { 51 | background: #ccc; 52 | color: #fff; 53 | } 54 | } 55 | 56 | .control { 57 | border-top: 1px solid #ddd; 58 | color: #525252; 59 | font-size: 15px; 60 | padding: @space 0 0 0; 61 | } 62 | 63 | .mb { 64 | margin: 0 0 @space 0; 65 | } 66 | 67 | .completed { 68 | text-decoration: line-through; 69 | } 70 | 71 | input[type=text] { 72 | border: 1px solid #ddd; 73 | border-radius: 10px; 74 | color: #525252; 75 | line-height: 1.2; 76 | height: 22px; 77 | padding: 5px; 78 | } -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var less = require('gulp-less'); 3 | var cleanCss = require('gulp-clean-css'); 4 | var gutil = require('gulp-util'); 5 | var livereload = require('gulp-livereload'); 6 | 7 | gulp.task('default', ['styles', 'watch']); 8 | 9 | gulp.task('watch', function() { 10 | livereload.listen(); 11 | gulp.watch(['css/style.less'], ['styles']); 12 | }); 13 | 14 | gulp.task('styles', function() { 15 | return gulp.src('css/style.less') 16 | .pipe(less().on('error', gutil.log)) 17 | .pipe(cleanCss()) 18 | .pipe(gulp.dest('css')) 19 | .pipe(livereload()); 20 | }); -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Todo List: Vue.js Example 6 | 7 | 8 | 9 | 10 | 11 |
12 |

Todo List: Vue.js Example

13 |
14 | 15 |
16 | 19 |
恭喜完成所有的項目!
20 |
21 | 全部 ({{ allCount }}) 22 | 已完成 ({{ completedCount }}) 23 | 未完成 ({{ incompleteCount }}) 24 |
25 | 26 |
27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /js/script.js: -------------------------------------------------------------------------------- 1 | Vue.component( 'todo-item' , { 2 | props: ['todo', 'index', 'filter'], 3 | template:`
  • 4 | 5 | 6 | 7 | 編輯 8 | 刪除 9 |
  • `, 10 | methods: { 11 | remove: function(index) { 12 | this.$emit('remove'); 13 | }, 14 | updateTodo: function($event, todo) { 15 | if($event.target.value) { 16 | todo.text = $event.target.value; 17 | } 18 | todo.isEdit = !todo.isEdit; 19 | }, 20 | updateStatus: function(todo) { 21 | todo.isCompleted = !todo.isCompleted; 22 | }, 23 | editTodo: function(todo) { 24 | todo.isEdit = !todo.isEdit; 25 | }, 26 | showCompletedTodo: function(isCompleted, filter) { 27 | return !(!isCompleted && filter === 'show_completed'); 28 | } 29 | } 30 | }); 31 | 32 | var app = new Vue({ 33 | el: '#app', 34 | data: { 35 | todos: { 36 | "a5436691-350c-4ed0-862e-c8abc8509a4a": { 37 | "uuid": "a5436691-350c-4ed0-862e-c8abc8509a4a", 38 | "text": "買一本好書", 39 | "isCompleted": false, 40 | "isEdit": false 41 | }, 42 | "a98bf666-a710-43b2-81b2-60c68ec4688d": { 43 | "uuid": "a98bf666-a710-43b2-81b2-60c68ec4688d", 44 | "text": "打電話給小明", 45 | "isCompleted": true, 46 | "isEdit": false 47 | }, 48 | "452ef417-033d-48ff-9fec-9d686c105dce": { 49 | "uuid": "452ef417-033d-48ff-9fec-9d686c105dce", 50 | "text": "寫一篇文章", 51 | "isCompleted": false, 52 | "isEdit": false 53 | } 54 | }, 55 | newTodoText: '', 56 | filter: 'show_all' 57 | }, 58 | computed: { 59 | todosData: function() { 60 | return JSON.stringify(this.todos); 61 | }, 62 | list: function() { 63 | if(this.filter === 'show_all') { 64 | return this.todos; 65 | } else if (this.filter === 'show_completed') { 66 | return this._getTodos(true); 67 | } else { //show_incomplete 68 | return this._getTodos(false); 69 | } 70 | }, 71 | allCount: function() { 72 | return Object.keys(this.todos).length; 73 | }, 74 | completedCount: function() { 75 | var _this = this; 76 | 77 | return Object.keys(this.todos).filter(function(value) { 78 | return _this.todos[value].isCompleted 79 | }).length; 80 | }, 81 | incompleteCount: function() { 82 | var _this = this; 83 | 84 | return Object.keys(this.todos).filter(function(value) { 85 | return !_this.todos[value].isCompleted 86 | }).length; 87 | } 88 | }, 89 | methods: { 90 | add: function() { 91 | var id = this._uuid(); 92 | 93 | Vue.set(this.todos, id, { 94 | uuid: id, 95 | text: this.newTodoText, 96 | isCompleted: false, 97 | isEdit: false 98 | }); 99 | 100 | this.newTodoText = ''; 101 | }, 102 | del: function(index) { 103 | Vue.delete(this.todos, index); 104 | }, 105 | setFilter: function(filter) { 106 | this.filter = filter; 107 | }, 108 | _uuid: function() { 109 | var d = Date.now(); 110 | if (typeof performance !== 'undefined' && typeof performance.now === 'function'){ 111 | d += performance.now(); //use high-precision timer if available 112 | } 113 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { 114 | var r = (d + Math.random() * 16) % 16 | 0; 115 | d = Math.floor(d / 16); 116 | return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16); 117 | }); 118 | }, 119 | _getTodos: function(isCompleted) { 120 | var list = {}; 121 | 122 | for(var index in this.todos) { 123 | if(this.todos[index].isCompleted === isCompleted) { 124 | list[index] = this.todos[index]; 125 | } 126 | } 127 | return list; 128 | }, 129 | _getObjContent: function (data) { 130 | return Object.keys(data).map(function(index){ 131 | return data[index]; 132 | }); 133 | } 134 | } 135 | }); 136 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "todo_list_vue_example", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "start": "gulp" 6 | }, 7 | "dependencies": { 8 | "gulp": "^3.9.1", 9 | "gulp-clean-css": "^2.0.12", 10 | "gulp-image-optimization": "^0.1.3", 11 | "gulp-less": "^3.1.0", 12 | "gulp-livereload": "^3.8.1", 13 | "gulp-uglify": "^2.0.0", 14 | "gulp-util": "^3.0.7" 15 | } 16 | } --------------------------------------------------------------------------------