作品需要用到的知识
- vue2
- js
- html
- css
作品的功能
- 用户的注册和登录功能
- 对todoList的添加,删除,修改操作
- 使用本地储存进行数据的保存
- 显示当前时间以及倒计时
项目结构:
作品组件:
登录组件:
<template>
<div class="login_div">
<div class="input_head">
<h2>登录</h2>
<router-link to="/register">没有账号?点击注册</router-link>
</div>
<div class="input_div">
<form>
<div class="form-group">
<label for="text">账号</label>
<input type="text" class="form-control" id="text" v-model.trim="name" placeholder="账号">
</div>
<div class="form-group">
<label for="password">密码</label>
<input type="password" class="form-control" id="password" v-model.trim="password" placeholder="密码">
</div>
<router-link :to="`/home?name=${name}&password=${password}`" class="btn btn-default">登录
</router-link>
</form>
</div>
</div>
</template>
<script>
export default {
name: 'Login',
data() {
return {
name: '',
password: ''
}
},
</script>
<style scoped>
.input_head {
width: 68%;
padding-top: 40px;
margin-left: auto;
margin-right: auto;
}
.login_div {
margin: 70px 140px;
padding-bottom: 30px;
border: 1px solid black;
border-radius: 10px;
}
.input_div {
width: 68%;
margin-left: auto;
margin-right: auto;
}
form div {
margin: 20px 0;
}
label {
font-size: 20px;
}
</style>
主页面组件
<template>
<div class="big_div">
<div class="nav_header">
<ul>
<li>
<router-link to="/home/todos" active-class="active">todos</router-link>
</li>
<li>
<router-link to="/home/countdown" active-class="active">倒计时</router-link>
</li>
<li>
<router-link to="/login" active-class="active">退出</router-link>
</li>
</ul>
</div>
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'Home',
props: ['user']
}
</script>
<style scoped>
.big_div {
height: 100px;
}
.nav_header {
display: flex;
justify-content: center;
}
ul {
list-style-type: none;
margin: 0;
padding: 0;
overflow: hidden;
border: 1px solid #e7e7e7;
background-color: #f3f3f3;
border-radius: 3px;
}
li {
float: left;
background: #daffdc;
width: 140px;
}
li a {
display: block;
color: #666;
text-align: center;
padding: 14px 16px;
text-decoration: none;
}
li a:hover:not(.active) {
background-color: #ddd;
}
li a.active {
color: white;
background-color: #4CAF50;
}
</style>
todos组件
<template>
<div class="todo-container">
<div class="todo-wrap">
<TodosHeader @addTodo="addTodo"></TodosHeader>
<TodosList :todos="todos"></TodosList>
<TodosFooter :todos="todos"></TodosFooter>
</div>
</div>
</template>
<script>
import TodosHeader from '../components/TodosHeader.vue';
import TodosList from '../components/TodosList.vue';
import TodosFooter from '../components/TodosFooter.vue';
export default {
name: "Todos",
components: { TodosHeader, TodosList, TodosFooter },
data() {
return {
todos: JSON.parse(localStorage.getItem('todos')) || []
}
},
methods: {
//添加todo
addTodo(todoObj) {
this.todos.unshift(todoObj)
},
//删除todo
deleteTodo(id) {
this.todos = this.todos.filter((todo) => {
return todo.id !== id;
})
},
//清除已做的todo
clearAllTodo() {
this.todos = this.todos.filter(todo => {
return !todo.done;
})
},
//点击更改已做和未做状态
checkTodo(id) {
this.todos.forEach(todo => {
if (todo.id === id) {
todo.done = !todo.done;
}
});
},
//切换所有事项的做或未做
checkAllTodo(isA) {
this.todos.forEach(todo => {
todo.done = !isA;
})
},
updateTodo(id, title) {
this.todos.forEach(todo => {
if (todo.id === id) {
todo.title = title;
}
})
}
},
watch: {
todos: {
deep: true,
handler(value) {
localStorage.setItem('todos', JSON.stringify(value));
}
}
},
mounted() {
this.$bus.$on('deleteTodo', this.deleteTodo);
this.$bus.$on('clearAllTodo', this.clearAllTodo);
this.$bus.$on('checkTodo', this.checkTodo);
this.$bus.$on('checkAllTodo', this.checkAllTodo);
this.$bus.$on('updateTodo', this.updateTodo);
},
beforeDestroy() {
this.$bus.$off('deleteTodo');
this.$bus.$off('clearAllTodo');
this.$bus.$off('checkTodo');
this.$bus.$off('checkAllTodo');
this.$bus.$off('updateTodo');
}
}
</script>
<style>
/*base*/
.btn {
display: inline-block;
padding: 4px 12px;
margin-bottom: 0;
font-size: 14px;
line-height: 20px;
text-align: center;
vertical-align: middle;
cursor: pointer;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
border-radius: 4px;
}
.btn-danger {
color: #fff;
background-color: #da4f49;
border: 1px solid #bd362f;
}
.btn-danger:hover {
color: #fff;
background-color: #bd362f;
}
.btn:focus {
outline: none;
}
.todo-container {
width: 600px;
margin: 0 auto;
padding-top: 20px;
}
.todo-container .todo-wrap {
padding: 10px;
border: 1px solid #ccc;
border-radius: 5px;
}
</style>
todolist头部组件
<template>
<div class="todo-header">
<input type="text" @keyup.enter="add" v-model="title" placeholder="请输入你的任务名称,按回车键确认" />
</div>
</template>
<script>
import { nanoid } from 'nanoid'
export default {
name: 'TodosHeader',
data() {
return {
title: ''
}
},
methods: {
//添加一条todo
add() {
if (!this.title.trim()) {
return alert('输入框不能为空!')
}
const todoObj = { id: nanoid(), title: this.title, done: false }
this.$emit('addTodo', todoObj);
this.title = '';
},
},
}
</script>
<style scoped>
/*header*/
.todo-header input {
width: 560px;
height: 28px;
font-size: 14px;
border: 1px solid #ccc;
border-radius: 4px;
padding: 4px 7px;
}
.todo-header input:focus {
outline: none;
border-color: rgba(82, 168, 236, 0.8);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
}
</style>```
### todolist表单组件
```javascript
<template>
<ul class="todo-main">
<TodosItem v-for="todoObj in todos" :key="todoObj.id" :todo="todoObj" />
</ul>
</template>
<script>
import TodosItem from './TodosItem.vue';
export default {
name: "TodosList",
components: { TodosItem },
props: ['todos'],
}
</script>
<style scoped>
/*main*/
.todo-main {
margin-left: 0px;
margin-top: 20px;
border: 1px solid #ddd;
border-radius: 2px;
padding: 0px;
}
.todo-empty {
height: 40px;
line-height: 40px;
border: 1px solid #ddd;
border-radius: 2px;
padding-left: 5px;
margin-top: 10px;
}
</style>
todolist底部组件
<template>
<div class="todo-footer">
<label>
<input type="checkbox" :checked="isAll" @change="checkAll(isAll)" />
</label>
<span>
<span>已完成{{doneTotal}}</span> / 全部{{total}}
</span>
<button class="btn btn-danger" @click="clearAllDone">清除已完成任务</button>
</div>
</template>
<script>
export default {
name: 'TodosFooter',
props: ['todos'],
computed: {
total() {
return this.todos.length
},
doneTotal() {
let i = 0;
this.todos.forEach(todo => {
if (todo.done) { i++; }
})
return i;
},
isAll() {
return this.total === this.doneTotal && this.total > 0;
}
},
methods: {
clearAllDone() {
this.$bus.$emit('clearAllTodo');
},
checkAll(isA) {
this.$bus.$emit('checkAllTodo', isA);
}
}
}
</script>
<style scoped>
/*footer*/
.todo-footer {
height: 40px;
line-height: 40px;
padding-left: 6px;
margin-top: 5px;
}
.todo-footer label {
display: inline-block;
margin-right: 20px;
cursor: pointer;
}
.todo-footer label input {
position: relative;
top: -1px;
vertical-align: middle;
margin-right: 5px;
}
.todo-footer button {
float: right;
margin-top: 5px;
}
</style>
tosolist单个词条组件
<template>
<li>
<label>
<input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)" /> 
<span v-show="!todo.isEdit">{{todo.title}}</span>
<input v-show="todo.isEdit" type="text" :value="todo.title" @blur="handleBlur(todo,$event)" ref="inputTitle">
</label>
<button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button>
<button class="btn btn_edit" v-show="!todo.isEdit" @click="handleEdit(todo)">编辑</button>
</li>
</template>
<script>
export default {
name: 'TodosItem',
props: ['todo'],
methods: {
handleDelete(id) {
if (confirm('确认删除吗?')) {
this.$bus.$emit('deleteTodo', id);
}
},
handleCheck(id) {
this.$bus.$emit('checkTodo', id);
},
handleEdit(todo) {
if (todo.hasOwnProperty('isEdit')) {
todo.isEdit = true;
} else {
this.$set(todo, 'isEdit', true);
}
this.$nextTick(function () {
this.$refs.inputTitle.focus();
})
},
handleBlur(todo, e) {
todo.isEdit = false;
if (!e.target.value.trim()) {
return alert('输入不能为空!')
}
this.$bus.$emit('updateTodo', todo.id, e.target.value);
console.log(todo.id);
}
}
}
</script>
<style scoped>
/*item*/
li {
list-style: none;
height: 40px;
line-height: 40px;
padding: 0 6px;
border-bottom: 1px solid #ddd;
margin: 5px 0;
line-height: 40px;
}
li label {
float: left;
cursor: pointer;
padding-left: 10px;
margin: -1px;
}
li label li input {
vertical-align: middle;
margin-right: 6px;
position: relative;
top: -1px;
}
li button {
float: right;
display: none;
color: #fff;
margin-top: 3px;
}
li:before {
content: initial;
}
li:last-child {
border-bottom: none;
}
li:hover button {
display: block;
color: #fff;
}
.btn_edit {
background: rgb(174, 116, 255);
margin-right: 10px;
}
label input {
border: rgb(182, 182, 182) solid 1px;
height: 20px;
}
</style>
代码下载链接🔗:https://pan.baidu.com/s/1iwQxik8n53PBlYYN0GPwtg?pwd=l5od