Tìm kiếm bài viết

Lập trình App đơn giản với Vuejs

09.11.2020

5.0/5 (1 Reviews)

Todo App đơn giản với Vuejs, tìm hiểu về VueJS đơn giản đến nâng cao qua từng ví dụ bạn nhé.

    Cài đặt môi trường cho Vuejs

    Như mọi framework khác, để xài được nó thì bạn phải cài đặt nó đã npm install -g @vue/cli,

    Tạo vue project bằng dòng lệnh sau vue create todo-app, sau đó bạn sẽ tới bước chọn preset, bạn có thể dùng dấu mũi tên lên xuống để lựa chọn, với mình thì mình sẽ chọn là default. Sau đó bạn cứ nhấn enter và mở project và bắt đầu code thôi

    Cấu trúc project và phân tích components

    Trong folder src, chúng ta sẽ tạo 1 folder components để chứa những component sẽ được tạo ra.

    Trong file main.js, chúng ta sẽ viết như sau:

    import Vue from 'vue'
    import VueRouter from 'vue-router'
    import App from './App.vue'
    import routes from './routes';
    import stores from './stores';
    
    Vue.config.productionTip = false
    Vue.use(VueRouter);
    
    const router = new VueRouter({
      routes
    })
    
    new Vue({
      router,
      stores,
      render: h => h(App),
    }).$mount('#app')
    

    Tới bước này thì bạn nhớ cài đặt thêm vue-routervuex cho nó nhé. Bạn có thể thấy là chúng ta đã import file routes và stores vào trong main.js để sử dụng. Nội dung trong file cũng rất đơn giản thôi, bạn sẽ define cho nó 3 route chính mà t sẽ sử dụng ở trong app: listNew, listInprogress và listDone, bạn có thể tham khảo nội dung file routes ở bên dưới:

    import ListNew from './components/ListNew.vue'
    import ListInprogress from './components/ListInprogress.vue'
    import ListDone from './components/ListDone.vue'
    
    export default  const routes = [
      {
        path: '/listNew',
        name: 'ListNew',
        component: ListNew
      },
      {
        path: '/listInprogress',
        name: 'ListInprogress',
        component: ListInprogress
      },
      {
        path: '/listDone',
        name: 'ListDone',
        component: ListDone
      },
    ];
    

    Trong file stores.js, chúng ta sẽ định nghĩa state, mutations, getters , actions cho app, bạn có thể tham khảo nội dung dưới đây,

    import Vue from 'vue'
    import Vuex from 'vuex'
    import io from 'socket.io-client';
    const socket = io('localhost:6969');
    Vue.use(Vuex);
    
    export default const store = new Vuex.Store({
      state: function () {
      const todolist = {
          listNew: [
              {
                  id: 0,
                  value: "Đi ngủ",
              },
          ],
          listInprogress: [
              {
                  id: 0,
                  value: "Làm việc",
              },
          ],
          listDone: [
              {
                  id: 0,
                  value: "Đi tè",
              },
          ]
      }
        return todolist;
      },
      mutations: {
          // function để add thêm task vào cho listNew
        addNew: (state, {value}) => state.listNew.push(value), //
        
        // Xóa task được chọn
        deleteTask: (state, {listType, index}) => state[listType].splice(index, 1
        
        // chuyển task qua giai đoạn tiếp theo
        transferTask: (state, {currentList, nextList, index }) => {
          const taskTransfered =  state[currentList].splice(index, 1);
          state[nextList].push(taskTransfered[0]);
        },
        
        // lưu lại những chỉnh sửa của task
        saveEdit: (state, {listType, value, index}) => state[listType][index] = value,
      }
    })
    actions: {}
    getters: {}
    

    Trong file App.vue, chúng ta sẽ tạo giao diện cơ bản cho app todolist,

    <template>
      <div id="app">
        <AddTodo />
        <div class='containerTab'>
          <router-link to="/listNew">List New</router-link>
          <router-link to="/listInprogress">List Inprogress</router-link>
          <router-link to="/listDone">List Done</router-link>
        </div>
        <div class='containerContent'>
          <router-view></router-view>
        </div>
      </div>
    </template>
    
    <script>
    import AddTodo from './components/AddTodo.vue'
    export default {
      name: 'app',
      components: {
        AddTodo,
      }
    }
    </script>
    
    <>

    Trong folder components, chúng ta sẽ tạo ra 4 file là AddTodo.vue, ListNew.vue, ListInprogress.vue và ListDone.vue

    Trong file AddTodo thực chất là 1 input để chúng ta nhập task mới để add vào ListNew, bạn có thể tham khảo code dưới đây

    <template>
      <div class="container">
        <input
          id='inputNew'
          placeholder="Công việc muốn thêm"
          v-model="inputNew"
          @keydown.enter='addNew(inputNew)'
        />
        <button
          v-on:click='addNew(inputNew)'
        >Thêm vào</button>
      </div>
    </template>
    
    <script>
    export default {
      name: 'AddTodo',
      methods: {
        addNew (value) {
          this.$stores.commit('addNew', {value})
          this.inputNew = ''
        },
      },
      data: function() {
        return {
          inputNew: '',
        }
      }
    }
    </script>
    
    <>

    Trong ListNew.vue, ListInprogress.vue và ListDone.vue lần lượt chính là 3 bảng thể hiện list task, về cơ bản UI của cả 3 folder đều giống hết nhau, vì vậy chúng ta có thể để phần >

    <template>
      <div>
        <div v-for='(item, index) in listNew' :key="item" class='listItem'>
          <p v-show='indexItemEdited !== index'>{{item}}</p>
          <input
            class="inputEdit"
            type="text"
            v-show='indexItemEdited === index' 
            :placeholder='item'
            v-model='item.index'
            @blur='saveEdit("listNew", $item[index], index)'
          />
          <div class='buttonSection'>
            <button 
              v-show='indexItemEdited !== index'
              @click='toggleEdit(index)'
            >
              Sửa
            </button>
            <button
              v-show='indexItemEdited === index'
              @click='toggleEdit(index), saveEdit("listNew", item, index)'
            >
              Lưu
            </button>
            <button
              @click='transferTask("listNew", "listInprogress", index)'
            >Làm</button>
            <button v-on:click='deleteTask("listNew", index)' >Xóa</button>
          </div>
        </div>
      </div>
    </template>
    
    <script>
    export default {
      name: 'ListNew',
      data: function() {
        return {
          indexItemEdited: -1,
          item: '',
        };
      },
      computed: {
        listNew() {
          return this.$stores.state.listNew;
        }
      },
      methods: {
        toggleEdit: function (index) {
          if(this.indexItemEdited === index) {
            return this.indexItemEdited = -1;
          }
          return this.indexItemEdited = index;
        },
        deleteTask: function (listType, index) {
          this.$stores.commit('deleteTask', {listType, index})
        },
        saveEdit: function (listType, value, index) {
          this.$stores.commit('saveEdit', {listType, value, index})
        },
        transferTask: function (currentList, nextList, index) {
          this.$stores.commit('transferTask', {currentList, nextList, index})
        }
      }
    }
    </script>
    

    riêng về css, bạn có thể thêm nó vào chung với css ở file App.js

    .listItem {
      display: flex;
      align-items: center;
      justify-content: space-between;
      padding: 10px;
      padding-left: 16px;
      margin-bottom: 2px;
      font-size: 16px;
      background: #4267b2;
      color: #fff;
    }
    .listItem:last-child {
      border-radius: 0 0 10px 10px;
      margin-bottom: 0;
    }
    .inputEdit {
      padding: 2.5px 10px;
      font-size: 16px;
      color: #4267b2;
      background: #ffffff;
    }
    .buttonSection {
      background: transparent;
    }
    .buttonSection > button {
      color: #4267b2;
      font-weight: bold;
      margin-right: 10px;
      background: #ffffff;
      border: none;
      padding: 5px 10px;
    }
    

    file ListInprogress:

    <template>
      <div>
        <div v-for='(item, index) in listInprogress' :key="item" class='listItem'>
          <p>{{item}}</p>
          <div class='buttonSection'>
            <button
              @click='transferTask("listInprogress", "listDone", index)'
            >Hoàn thành</button>
            <button v-on:click='deleteTask("listInprogress", index)' >Xóa</button>
          </div>
        </div>
      </div>
    </template>
    
    <script>
    export default {
      name: 'ListInprogress',
      computed: {
        listInprogress() {
          return this.$stores.state.listInprogress;
        }
      },
      methods: {
        deleteTask (listType, index) {
          this.$stores.commit('deleteTask', {listType, index})
        },
        transferTask (currentList, nextList, index) {
          this.$stores.commit('transferTask', {currentList, nextList, index})
        }
      }
    }
    </script>
    

    Riêng về file ListDone thì khá giống với ListInprogress, nên mình sẽ không để tham khảo ở đây, các bạn có thể xem nó như 1 bài tập nhỏ cho bản thân.

    Giờ bạn có thể yên tâm và chạy thử app của mình với lệnh là npm run server.

    Kết luận sơ sơ

    Trên đây là app todo cực kỳ đơn giản của mình để có thể ôn lại kiến thức đã học và giúp đỡ một số bạn gặp khó khăn trong việc tiếp cận với Vuejs. Với app trên bạn có thể cải thiện thêm bằng cách sử dụng và tìm hiểu thêm về các khái niệm như actions, getters , mapActions,... Sau đó có thể tạo 1 server json nho nhỏ để làm việc với api, bạn có thể sử dụng axios để xử lý với dữ liệu ở server.

    Nếu các bạn có hứng thú với bài viết của mình, xin hãy upvote và comment ý kiến của bạn về app hoặc bài viết. Nếu bài viết này có upvote trên 10 hoặc trong thời gian tới mình rảnh rỗi, sẽ phát triển tiếp app todo này với realtime bằng cách sử dụng , refactor code để app todo này càng hoàn thiện hơn.

    CÓ THỂ BẠN QUAN TÂM

    Bài Viết Cùng Chuyên Mục

    XEM THÊM
    thumbnail

    Kubernetes bài 6 - Vận hành k8s Day-Two Operations và Quản trị bằng GitOps

    22.05.2026

    Khi cụm Kubernetes của bạn đã được bảo mật cấu hình, tối ưu tài nguyên và thiết lập tự phục hồi, câu hỏi đặt ra là làm sao để duy trì sự ổn định đó trong nhiều năm tiếp theo mà không bị phụ thuộc

    thumbnail

    Kubernetes bài 5 - bảo mật Cloud Native và chuẩn DevSecOps cho K8s

    22.05.2026

    Việc siết chặt an ninh (Hardening) không phải là cấu hình một vài thông số rồi bỏ đó, mà là một tư duy phòng thủ chiều sâu.

    thumbnail

    Kubernetes bài 4 - Tối ưu Resource Auto-Healing và Scale Zero-Downtime

    22.05.2026

    Bài viết này sẽ đi sâu vào các cơ chế ở tầng Kernel giúp hệ thống tự phục hồi, chống lại các đợt tấn công cạn kiệt tài nguyên và cập nhật phiên bản mới mà người dùng không hề hay biết.

    thumbnail

    Kubernetes bài 3 - Bảo mật cấu hình k8s và config Security trên Production

    22.05.2026

    Kubernetes giải quyết bài toán này bằng hai đối tượng chuyên biệt nhưng nếu không hiểu rõ bản chất bảo mật ở tầng dưới, bạn đang tự tay dâng toàn bộ chìa khóa hệ thống cho hacker.

    thumbnail

    Kubernetes bài 2 - Mạng lưới k8s và luồng Traffic ở Packet Level

    22.05.2026

    Pod không chỉ là một container: Rất nhiều người nhầm lẫn Pod 1-1 với Container. Thực chất, Pod là đơn vị triển khai nhỏ nhất, có thể chứa một hoặc nhiều container

    Mục lục bài viết