如何从零实现一个todo list (2)

一、确定布局情况

我们核心功能做完以后,我们就要确定布局部分了,根据思维导图我们知道有侧边栏的情况,而且根据屏幕尺寸的情况下 尺寸过小的情况下我们需要的是抽屉的的组件 中心布局不改变,当尺寸过大的时候侧边栏则作为常驻在左侧。

我们开始调整布局 首先使用vuetify的内置布局 使用它的左右布局

二、调整App.vue中的布局代码

html 复制代码
<!-- App.vue -->
<template>
  <div id="app">
    <v-app>
      <v-system-bar
        color="#303F9F"
        dark
      >
        <v-spacer></v-spacer>
        <v-icon>mdi-window-minimize</v-icon>
        <v-icon>mdi-window-maximize</v-icon>
        <v-icon>mdi-close</v-icon>
      </v-system-bar>
      <!-- 这里使用我们自己封装的drawer 要根据页面宽度去解决侧边栏常驻 -->
      <side-bar />
      <v-main>
        <v-toolbar
          color="#3F51B5"
          dark
        >
          <v-app-bar-nav-icon
            @click="drawer=!drawer"
            class="d-sm-flex d-md-none"
          ></v-app-bar-nav-icon>
          <v-toolbar-title>{{active_key}}</v-toolbar-title>
          <v-spacer></v-spacer>
        </v-toolbar>
        <v-container>
          <task-list />
        </v-container>
      </v-main>
    </v-app>
  </div>
</template>

<script>
import SideBar from "./components/SideBar.vue";
import TaskList from "./components/TaskList.vue";
import mixin from "@/mixins/store";
export default {
  mixins: [mixin],
  name: "App",
  components: {
    TaskList,
    SideBar,
  },
};
</script>

<style lang="scss">
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  color: #2c3e50;
  height: 100vh;
  overflow: auto;
}
.v-system-bar {
  z-index: 99;
}
</style>

三、App中的mixin是什么呢?

我们之前技术选型了vuex,这时候就要开始发挥它的作用,如果在App.vue中定义的话 子组件使用props不能改值,加上.sync修饰符又要在里面定义compute很麻烦 v-model也是一样 vue2中的v-model只能定义一个 使用provide和inject又没有双向绑定数据监听的功能 有没有统一的方法呢?

答案就是vuex 我们在vuex中写下如下代码

javascript 复制代码
// store/index.js
import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    // 侧边栏点击的是否显示
    drawer: false,
    // 当前侧边栏选择的项
    active_key: "我的一天",
    // 侧边栏固定的目录
    fixed_menu: ["我的一天", "我的任务"],
    // 侧边栏可添加的目录
    sort_menu: [],
    // 当前完整列表
    current_list: [
      {
        id: "1",
        title: "测试数据1",
        complete: false,
        create_time: "2024-03-28 16:43:00",
        category: "",
      },
      {
        id: "1",
        title: "测试数据1",
        complete: true,
        create_time: "2024-03-28 16:43:00",
        category: "",
      },
    ],
  },
  mutations: {
    // 设置侧边栏显示
    setDrawer(state, show) {
      state.drawer = show;
    },
    // 设置当前选中的侧边栏值
    setActiveKey(state, key) {
      state.active_key = key;
    },
    // 设置当前的分类目录
    setSortMenu(state, data) {
      state.sort_menu = data;
    },
    // 设置列表
    setList(state, list) {
      state.current_list = list;
    },
  },
  actions: {},
  modules: {},
});

这时候我们定义了几个字段 但是我们想要在每个组件中可以修改里面的值 并且便于维护的情况下 mixin就出场了 在src下创建一个文件夹mixins/store.js 只需要导入进去 任何一个组件都可以使用这些值

javascript 复制代码
//mixins/store.js
import moment from "moment";
export default {
  computed: {
    // 操作侧边栏的打开
    drawer: {
      get() {
        return this.$store.state.drawer;
      },
      set(value) {
        this.$store.commit("setDrawer", value);
      },
    },
    // 当前侧边栏选了什么
    active_key: {
      get() {
        return this.$store.state.active_key;
      },
      set(value) {
        this.$store.commit("setActiveKey", value);
      },
    },
    // 侧边栏固定的项目
    fixed_menu() {
      return this.$store.state.fixed_menu;
    },
    // 分类项目
    sort_menu: {
      get() {
        return this.$store.state.sort_menu;
      },
      set(value) {
        this.$store.commit("setSortMenu", value);
      },
    },
    // 当前列表
    current_list: {
      get() {
        return this.$store.state.current_list;
      },
      set(value) {
        this.$store.commit("setList", value);
      },
    },
    // 为了能用v-for 渲染写的列表目录 根据不同的key去获取它的待办列表和已完成列表
    lists() {
      return (key) => {
        switch (key) {
          case "我的一天":
            let current_date = moment(new Date()).format("yyyy-MM-DD");
            return [
              {
                name: "待办列表",
                data: this.current_list.filter(
                  (list_item) => !list_item.complete && moment(list_item.create_time).format("yyyy-MM-DD") == current_date
                ),
              },
              {
                name: "已完成",
                data: this.current_list.filter(
                  (list_item) => list_item.complete && moment(list_item.create_time).format("yyyy-MM-DD") == current_date
                ),
              },
            ];
          case "我的任务":
            return [
              {
                name: "待办列表",
                data: this.current_list.filter((list_item) => !list_item.complete),
              },
              {
                name: "已完成",
                data: this.current_list.filter((list_item) => list_item.complete),
              },
            ];
          default:
            break;
        }
      };
    },
  },
};

四、侧边栏代码编写

在上面我们画了一个图,根据视窗大小来确定侧边栏是固定还是以drawer的形式显示 并且还要显示出侧边栏的菜单 那我们创建一个components/SideBar.vue

html 复制代码
<template>
  <v-navigation-drawer
    v-model="drawer"
    :absolute="isAbsolute"
    :temporary="isAbsolute"
    app
    :class="{top:isAbsolute}"
    disable-resize-watcher
    mobile-breakpoint="0"
  >
    <v-toolbar
      color="#fff"
      flat
      v-if="isAbsolute"
    >
      <v-app-bar-nav-icon @click="drawer=!drawer"></v-app-bar-nav-icon>
    </v-toolbar>
    <v-list class="pt-md-6">
      <v-list-item
        class="list-item text-h6"
        v-for="item in menus"
        :key="item"
        :class="{'active':item==active_key}"
        @click="changeKey(item)"
      >
        <v-list-item-content>
          {{item}}
        </v-list-item-content>
        <v-list-item-action>
          <v-chip
            class="ml-4"
            v-if="lists(item)[0].data.length"
          >{{lists(item)[0].data.length}}</v-chip>
        </v-list-item-action>
      </v-list-item>
      <v-divider></v-divider>
    </v-list>
  </v-navigation-drawer>
</template>

<script>
import mixin from "@/mixins/store";
export default {
  mixins: [mixin],
  computed: {
    // 侧边栏是否是绝对布局
    isAbsolute() {
      return this.width <= 960;
    },
    // 目录列表
    menus() {
      return this.fixed_menu.concat(this.sort_menu);
    },
  },
  data() {
    return {
      width: "",
    };
  },
  mounted() {
    // 初始化drawer是否显示 并且监听窗口宽度变化情况
    this.responseWidth();
    window.addEventListener("resize", this.responseWidth);
  },
  methods: {
    // 监听窗口宽度变化
    responseWidth(event) {
      let width = window.innerWidth;
      if (this.width != width && width > 960) {
        this.drawer = true;
      }
      this.width = width;
    },
    // 切换侧边栏选中项 如果小于960 代表是在移动端的情况下 要把侧边栏收起来
    changeKey(key) {
      this.active_key = key;
      if (this.width < 960) {
        this.drawer = false;
      }
    },
  },
  beforeDestroy() {
    window.removeEventListener("resize", this.responseWidth);
  },
};
</script>

<style lang="scss" scoped>
.top {
  z-index: 100;
}
.list-item {
  cursor: pointer;
  &:hover {
    background-color: #eee;
  }
  &.active {
    background: #eee;
  }
}
</style>

五、改动任务列表显示

我们改动了这么多代码 那原来的components/TaskList.vue的原型功能也要接入仓库的代码 保证全局变化的侧边栏选中情况能够反馈到我们现在任务列表的渲染

html 复制代码
<!-- components/TaskList.vue -->
<template>
  <div class="py-4">
    <v-text-field
      v-model="todo"
      counter="25"
      placeholder="请输入待办事项"
      label="待办事项"
      outlined
      dense
      persistent-placeholder
      @keydown.enter="addTodo"
    ></v-text-field>

    <v-list dense>
      <div
        v-for="list in lists(active_key)"
        :key="list.name"
      >
        <template v-if="list.data.length">
          <v-subheader class="text-h6 mb-2">{{list.name}}<v-chip
              small
              class="ml-2"
            >{{list.data.length}}</v-chip></v-subheader>
          <v-list-item
            v-for="(item, i) in list.data"
            :key="i"
            class="task-item mb-4"
            :class="{complete:item.complete}"
          >
            <v-list-item-avatar>
              <v-checkbox
                v-model="item.complete"
                @change="forceUpdate"
                v-if="refresh"
              />
            </v-list-item-avatar>
            <v-list-item-content>
              <v-list-item-title class="text-subtitle-1">{{item.title}}</v-list-item-title>
            </v-list-item-content>
            <v-list-item-action>
              <v-icon
                v-if="!item.complete"
                @click="deleteItem(item.id)"
              >mdi-close</v-icon>
            </v-list-item-action>
          </v-list-item>
        </template>
      </div>
    </v-list>
  </div>
</template>

<script>
import { uuid } from "vue-uuid";
import mixin from "@/mixins/store";
import moment from "moment";
export default {
  mixins: [mixin],
  data() {
    return {
      // 待办事项输入值
      todo: "",
      // 这里处理渲染的时候v-checkbox值变动样式不刷新的问题
      refresh: true,
    };
  },
  methods: {
    // 新增todolist
    addTodo() {
      if (!this.todo) return;
      this.current_list.push({
        // 增加id是因为使用了computed 它的index是不准的
        id: uuid.v4(),
        title: this.todo,
        complete: false,
        create_time: moment(new Date()).format("yyyy-MM-DD HH:mm:ss"),
        category: "",
      });
      this.todo = "";
    },
    // 强制刷新
    forceUpdate() {
      this.refresh = false;
      this.$nextTick(() => {
        this.refresh = true;
      });
    },
    // 删除事项
    deleteItem(id) {
      this.current_list.splice(
        this.current_list.findIndex((item) => item.id == id),
        1
      );
    },
  },
};
</script>

<style lang="scss" scoped>
.task-item {
  background-color: #eee;
  border-radius: 10px;
}
.task-item.complete {
  .v-list-item__title {
    text-decoration: line-through;
    color: #ccc;
  }
}
</style>

ok在这一节点下我们已经完成了导图所需要的侧边栏功能 下一章我们就将分类功能以及一些缺失的可选功能加入到里面

相关推荐
头顶秃成一缕光10 分钟前
若依——基于AI+若依框架的实战项目(实战篇(下))
java·前端·vue.js·elementui·aigc
冴羽yayujs14 分钟前
SvelteKit 最新中文文档教程(17)—— 仅服务端模块和快照
前端·javascript·vue.js·前端框架·react
木木黄木木26 分钟前
HTML5图片裁剪工具实现详解
前端·html·html5
念九_ysl28 分钟前
基数排序算法解析与TypeScript实现
前端·算法·typescript·排序算法
海石28 分钟前
vue2升级vue3踩坑——【依赖注入】可能成功了,但【依赖注入】成功了不太可能
前端·vue.js·响应式设计
uhakadotcom40 分钟前
Vite 与传统 Bundler(如 Webpack)在 Node.js 应用的性能对比
前端·javascript·面试
uhakadotcom1 小时前
Socket.IO 简明教程:实时通信的基础知识
前端·javascript·面试
机器视觉知识推荐、就业指导1 小时前
QML 批量创建模块 【Repeater】 组件详解
前端·c++·qml
lmryBC491 小时前
golang接口-interface
java·前端·golang