使用 Webpack 打造 vue - todo 应用实践( 四)

之前进行了webpack的相关配置,现在我们lai 实现一下具体页面


重构了目录结构
-src
  - assets
       - images
           - background-image.jpg
           - done.svg
           - round.svg
       - styles
           - footer.styl
           - global.styl
  - todo
        - footer.jsx
        - header.vue
        - item.vue
        - tabs.vue
        - todo.vue
  - app.vue     // 模板文件
  - index.js    // 入口文件
-package.json
-webpack.config.js
代码实现
// footer.styl
#footer
  margin 40px
  text-align: center
  color: #eee
  font-size 15px
  text-shadow 0 3px 0 2px #eee
// global.styl
html, body
  margin 0
  padding 0
  width 100%
  height 100%
body
  background-image url(../images/background-image.jpg)
  background-size cover
  background-position center
  font 14px 'microsoft yahei'
  color #000000
  font-weight 500
//  index.js
import Vue from 'vue'
import App from './app.vue'
import './assets/styles/global.styl'
const root = document.createElement('div'); // 根节点
document.body.appendChild(root);

new Vue({
  render: (h)=> h(App) // 将App渲染至根节点
}).$mount(root)
// app.vue
<template>
  <div id="app">
    <div class="cover">
      <m-header></m-header>
      <todo></todo>
      <Footer></Footer>
    </div>
  </div>
</template>

<script>
import MHeader from './todo/header.vue'
import Footer from './todo/footer.jsx'
import Todo from './todo/todo.vue'
export default {
  data() {
    return {
      test: "testsdsassadds"
    };
  },
  components: {
    MHeader,
    Footer,
    Todo
  }
};
</script>

<style lang="stylus" scoped>
#app
  position absolute
  left 0
  right 0
  top 0
  bottom 0
  .cover
    position absolute
    left 0
    right 0
    top 0
    bottom 0
    background-color #555
    opacity 0.5
    z-index 1
</style>
// header.vue
<template>
  <header class="main-header">
    <h1>Meils - todo</h1>
  </header>
</template>

<style lang="stylus" scoped>
.main-header
  text-align: center
  h1
    font-size: 80px
    color rgba(175, 47, 47, 1)
    margin: 20px
</style>
// footer.jsx
import '../assets/styles/footer.styl'

export default{
  data () {
    return {
      author: 'Meils',
      blog: 'scopedSlots'
    }
  },
  render() {
    return (
      <div id="footer">
          <span>Power by {this.author},欢迎访问作者博客:{this.blog}</span>
      </div>
    )
  }
}
// item.vue
<template>
    <div :class="['todo-item', todo.completed ? 'completed' : '']">
        <input type="checkbox"
               class="toggle"
               v-model="todo.completed"
        >
        <label class="label">{{todo.content}}</label>
        <button class="destroy" @click="deleteTodo"></button>
    </div>
</template>

<script>
export default {
  props: {
    todo : {
      type: Object,
      require: true  
    }
  },
  methods: {
    deleteTodo () {
      // this.$emit 触发del事件,并返回 todo.id
      this.$emit('del', this.todo.id)
    }
  }
}
</script>

<style lang="stylus" scoped>
    .todo-item{
        position relative
        background-color #fff
        font-size 16px
        border-bottom 1px solid rgba(0,0,0,0.06)
        &:hover{
            .destroy:after{
                content: '×'
            }
        }
        label{
            white-space: pre-line;
            word-break: break-all;
            padding: 15px 60px 15px 15px;
            margin-left: 45px;
            display: block;
            line-height: 1.2;
            transition: color 0.4s;
        }
        &.completed{
            label{
                color: #d9d9d9;
                text-decoration line-through
            }
        }
    }
    .toggle{
        text-align: center;
        width: 40px;
        height: 40px;
        line-height: 40px
        position: absolute;
        top: 0;
        bottom: 0;
        margin: auto 0;
        border: none;
        appearance: none;
        outline none
        padding-left 5px
        cursor pointer
        &:after{
            content: url('../assets/images/round.svg')
        }
        &:checked:after{
            content: url('../assets/images/done.svg')
        }
    }
    .destroy{
        position: absolute;
        top: 0;
        right: 10px;
        bottom: 0;
        width: 40px;
        height: 40px;
        margin: auto 0;
        font-size: 30px;
        color: #cc9a9a;
        margin-bottom: 11px;
        transition: color 0.2s ease-out;
        background-color transparent
        appearance none
        border-width 0
        cursor pointer
        outline none
    }
</style>
// tabs.vue
<template>
    <div class="helper">
        <span class="left">{{unFinishedTodoLength}} 项待办</span>
        <span class="tabs">
            <span
                v-for="state in states"
                :key="state"
                :class="[state, filter === state ? 'actived' : '']"
                @click="toggleFilter(state)"
            >
                {{state}}
            </span>
        </span>
        <span class="clear" @click="clearAllCompleted()">
            清空
        </span>
    </div>
</template>

<script>
    export default {
        // props 接收父组件传过来的值
        props: {
            filter: {
                type: String,
                required: true,
            },
            todos: {
              type: Array,
              required: true
            }
        },
        data() {
            return {
                states: ['all', 'active', 'completed']
            }
        },
        computed: {
            unFinishedTodoLength() {
                return this.todos.filter(todo => !todo.completed).length;
            }
        },
        methods: {
            toggleFilter(state) {
              // 由父组件触发实现 
              this.$emit('toggle', state);
            },
            clearAllCompleted() {
                this.$emit('clearAllCompleted');
            }

        }
    }
</script>


<style lang="stylus" scoped>
    .helper {
        font-weight 100
        display flex
        justify-content space-between
        padding 5px 0
        line-height 30px
        background-color #ffffff
        font-size 14px
        font-smoothing: antialiased;
    }

    .left, .clear, .tabs {
        padding 0 10px
        box-sizing border-box
    }

    .left, .clear {
        width 150px
    }

    .left {
        text-align left
    }

    .clear {
        text-align: right
        cursor pointer
    }

    .tabs {
        width 200px
        display flex
        justify-content space-around
        * {
            display inline-block
            padding 0 10px
            cursor pointer
            border 1px solid rgba(175, 47, 47, 0)
            &.actived {
                border-color rgba(175, 47, 47, 0.4)
                border-radius 5px
            }
        }
    }

</style>
// todo.vue
<template>
  <section class="real-app">
    <input type="text"
          class="add-input"
          autofocus="autofocus"
          placeholder="接下来要做什么?"
          @keyup.enter="addTodo($event)">
    <items :todo="todo" v-for="todo in filteredTodos" :key="todo.id" @del="del"></items>
    <tabs @clearAllCompleted="clearAllCompleted"
          :filter="filters" 
          :todos="todos" 
          @toggle="toggleFilter"></tabs>
  </section>
</template>
 
<script>
import Items from './item.vue'
import Tabs from './tabs.vue'
let id = 0;
export default {
  data () {
    return {
      todos: [],
      filters: 'all'
    }
  },
  computed: {
    filteredTodos() {
      if (this.filters === 'all') {
        return this.todos
      }
      const completed = this.filters === 'completed';

      // 将todos数组中,completed为true的值过滤出来,并返回一个新数组
      return this.todos.filter(todo => completed === todo.completed);
    }
  },
  methods: {
    addTodo (e) {
      this.todos.unshift({
        id: id++,
        content: e.target.value.trim(),
        completed: false
      })
    },
    del (id) {
      this.todos.splice(this.todos.findIndex(todo => todo.id === id), 1)
      // findIndex方法接受一个函数,返回对应元素的索引值,
      /* 获取数组中年龄大于 18 的第一个元素索引位置
      var ages = [3, 10, 18, 20];
 
        function checkAdult(age) {
            return age >= 18;
        }
        
        function myFunction() {
            document.getElementById("demo").innerHTML = ages.findIndex(checkAdult);
        }

        // 2
      */
    },
    toggleFilter(state) {
      this.filters = state
    },
    clearAllCompleted() {
     // 给todos赋一个新的值(即todo.completed为false的值)
    this.todos = this.todos.filter(todo => todo.completed === false)
    } 
  },
  components: {
    Items,
    Tabs
  }
}
</script>

<style lang="stylus" scoped>
.real-app
  width: 700px
  margin: 0px auto
  box-shadow : 0 0 5px #666666
  .add-input
    position: relative
    margin: 0
    width: 100%
    font-size: 20px
    font-family: inherit
    font-weight: inherit
    line-height: 1.4em
    border: 0
    outline: none
    color: inherit
    box-sizing: border-box
    font-smoothing: antialiased
    padding: 16px 16px 16px 60px
    border: none
    box-shadow: inset 0 -2px 1px rgba(0, 0, 0, 0.03)
</style>
未完待续 ~~ 下一篇会是webpack的一些优化

相关推荐