一、确定布局情况
我们核心功能做完以后,我们就要确定布局部分了,根据思维导图我们知道有侧边栏的情况,而且根据屏幕尺寸的情况下 尺寸过小的情况下我们需要的是抽屉的的组件 中心布局不改变,当尺寸过大的时候侧边栏则作为常驻在左侧。
我们开始调整布局 首先使用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在这一节点下我们已经完成了导图所需要的侧边栏功能 下一章我们就将分类功能以及一些缺失的可选功能加入到里面