初始化项目
- 创建
yarn create @vitejs/app
选择框架:vue, 选择变种:vue-ts
- 进行项目目录,安装依赖
cd vue3-vite-todolist
yarn
- 启动
yarn dev
出现如下,运行成功
浏览器访问 http://localhost:3000/
安装element-plus
```
yarn add element-plus --save
```
配置element-plus按需引入
- 安装
vite-plugin-style-import
yarn add vite-plugin-style-import -D
- 安装
sass依赖 在项目中使用了sass(在引入element-plus样式时,也需要引入sass的),需要先安装sass依赖,sass-loader已经在vite中内置有,无需安装
yarn add sass -D
- 安装path模块
在vite配置中需要使用path模块,这是Node.js的,需要安装 Node.js 的声明文件@type/node
yarn add @type/node -D
- 在
src/vite.config.ts内进行配置
// ...
import styleImport from 'vite-plugin-style-import'
import path from 'path'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
// ...
styleImport({
libs: [
{
libraryName: 'element-plus',
esModule: true,
ensureStyleFile: true,
resolveStyle: (name) => {
name = name.slice(3)
return `element-plus/packages/theme-chalk/src/${name}.scss`
},
resolveComponent: (name) => {
return `element-plus/lib/${name}`
}
}
]
})
],
resolve: {
alias: {
'@': path.resolve(__dirname, 'src')
}
}
})
插件目录下管理element-plus管理插件
在src目录下新建plugins目录,plugins目录下新建element-plus.ts文件,用于同一管理element的组件
import type { App } from 'vue'
import {
ElButton,
ElForm,
ElFormItem,
ElInput,
ElLoading,
ElMessage,
ElMessageBox,
ElTag,
ElDialog
} from 'element-plus'
import 'element-plus/lib/theme-chalk/index.css'
// 组件列表
const components = [ElButton, ElForm, ElFormItem, ElInput, ElTag, ElDialog]
// 插件列表
const plugins = [ElLoading, ElMessage, ElMessageBox]
// 设置语言为中文
import { locale } from 'element-plus'
import lang from 'element-plus/lib/locale/lang/zh-cn'
import 'dayjs/locale/zh-cn'
// https://github.com/anncwb/vite-plugin-style-import/issues/16
// 解决elementplus locale在不同模式下导出不同
if (typeof locale === 'function') {
locale(lang) // dev
} else {
// @ts-ignore
locale.use(lang) // production
}
// 暴露出useElementPlus方法,给vue实例调用
export function useElementPlus(app: App<Element>) {
components.forEach((component) => {
app.component(component.name, component)
})
plugins.forEach((plugin) => {
app.use(plugin)
})
}
注意: 设置语言时,需要判断locale是否有use,使用不同方式进行安装语言包
入口中引入
在src/main.ts内配置
import { useElementPlus } from '@/plugins/element-plus'
createApp(App).use(useElementPlus).mount('#app')
配置prettier代码美化&vscode配置(可选)
- 配置根目录下的.vscode/settings.json
{
"files.autoSave": "off",
"editor.formatOnSave": true, // 是否开启vscode的保存自动格式化
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"eslint.format.enable": true, //是否开启vscode的eslint
"editor.defaultFormatter": "esbenp.prettier-vscode", // 设置默认格式化工具为prettier
// ===========================================
// ================ Vetur ====================
// ===========================================
"vetur.experimental.templateInterpolationService": true
}
- 配置根目录下的prettier.config.js
module.exports = {
printWidth: 100, // 单行输出(不折行)的(最大)长度
tabWidth: 2, // 每个缩进级别的空格数
useTabs: false, // 是否使用缩进符
semi: false, // 是否在语句末尾打印分号
singleQuote: true, // 是否使用单引号
quoteProps: 'as-needed', // 仅在需要时在对象属性周围添加引号
bracketSpacing: true, // 是否在对象属性添加空格
trailingComma: 'none', // 去除对象最末尾元素跟随的逗号
jsxBracketSameLine: false, // 在jsx中把'>' 是否单独放一行
jsxSingleQuote: false, // jsx 不使用单引号,而使用双引号
htmlWhitespaceSensitivity: 'ignore', // 指定 HTML 文件的全局空白区域敏感度, "ignore" - 空格被认为是不敏感的
endOfLine: 'lf', // 换行符使用 lf,
insertPragma: false, // 在文件的顶部插入一个 @format的特殊注释
requirePragma: false // Prettier可以严格按照按照文件顶部的一些特殊的注释格式化代码
}
- 根目录下的tsconfig.json
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"moduleResolution": "node",
"strict": true,
"jsx": "preserve",
"sourceMap": true,
"resolveJsonModule": true,
"esModuleInterop": true,
"lib": ["esnext", "dom"],
"baseUrl": "./",
"paths": {
"@/*": ["src/*"]
}
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"]
}
安装vue-router@4
yarn add vue-router@4 --save
创建两个页面
在src下创建views目录,用于存放页面文件,在view目录下分别创建home/index.vue和todo-list/index.vue文件
首页 home/index.vue
<template>
<div>
首页
<router-link :to="{ name: 'TodoList' }">去todoList页面</router-link>
</div>
</template>
todoList页 todo-list/index.vue
<template>
<div>
<router-link :to="{ name: 'Home' }">回到首页</router-link>
</div>
</template>
创建路由
- 在src目录下新建
router/index.ts文件,内容如下
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'
const constantRoutes: Array<RouteRecordRaw> = [
{
path: '/',
name: 'Home',
component: () => import('../views/home/index.vue'),
meta: {
title: '首页'
}
},
{
path: '/todo-list',
name: 'TodoList',
component: () => import('../views/todo-list/index.vue'),
meta: {
title: 'todo-list'
}
}
]
const router = createRouter({
history: createWebHashHistory(),
scrollBehavior: () => ({ top: 0 }),
routes: constantRoutes
})
export default router
在src/main.ts中引入
// ...
import router from './router/index'
createApp(App)
.use(router)
.use(useElementPlus)
.mount('#app')
修改src/App.vue内容
<template>
<div id="app" class="app-container">
<router-view />
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
name: 'App'
})
</script>
<style lang="scss" scoped>
.app-container {
margin: 50px auto;
width: 1200px;
}
</style>
安装vuex状态管理
yarn add vuex@next --save
引入ElCheckbox组件
在src/plugins/element-plus.ts内,引入ElCheckbox组件并添加到components数组中
import {
// ...
ElCheckbox
} from 'element-plus'
const components = [
// ...
ElCheckbox
]
创建store
在src目录下新建store目录,在store目录中新建index.ts文件,内容如下
// src/store/index.ts
import { createStore } from 'vuex'
interface ItodoItem {
title: string
id: number
done: false
}
const todoList: Array<ItodoItem> = []
// state
const state = {
todoList
}
// 创建一个新的 store 实例
const store = createStore({
state,
mutations: {
ADD_TODO(state, todo: ItodoItem) {
if (todo?.title) {
state.todoList.push(todo)
}
},
REMOVE_TODO(state, id: number) {
const index = state.todoList.findIndex((todo) => todo.id === id)
if (index !== -1) {
state.todoList.splice(index, 1)
}
},
UPDATE_TODO_STATUS(state, todo: ItodoItem) {
const index = state.todoList.findIndex((item) => item.id === todo.id)
if (index !== -1) {
state.todoList.splice(index, 1, todo)
}
}
},
actions: {
addTodo({ commit }, todo) {
commit('ADD_TODO', todo)
},
removeTodo({ commit }, todoId) {
commit('REMOVE_TODO', todoId)
},
updateTodoStatus({ commit }, todo) {
commit('UPDATE_TODO_STATUS', todo)
}
}
})
export default store
引入store
在src/main.ts中配置
import store from './store/index'
createApp(App)
.use(router)
.use(store)
.use(useElementPlus)
.mount('#app')
简易例子ToDoList
在src/views/todo-list/index.vue中,编写TodoList代码,涉及功能有:
- 新增todo
- 移除todo
- 更新todo状态
<template>
<div>
<router-link :to="{ name: 'Home' }">回到首页</router-link>
<el-input v-model="title" placeholder="请输入" class="w500" @keyup.enter="handleAdd" />
<el-button type="primary" @click="handleAdd">新增</el-button>
<div>
<p v-for="undoTodo in undoTodos" :key="undoTodo.id">
<el-tag type="success" closable @close="handleRemove(undoTodo)">
<span>{{ undoTodo.id }}-</span>
<span>{{ undoTodo.title }}</span>
</el-tag>
<el-checkbox class="ml5" v-model="undoTodo.done"></el-checkbox>
</p>
</div>
<div>
<p>已完成</p>
<p v-for="doneTodo in doneTodos" :key="doneTodo.id">
<el-tag type="success">
<span>{{ doneTodo.id }}-</span>
<span>{{ doneTodo.title }}</span>
</el-tag>
<el-checkbox class="ml5" v-model="doneTodo.done"></el-checkbox>
</p>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, computed, reactive, toRefs } from 'vue'
import { useStore } from 'vuex'
export default defineComponent({
setup() {
const store = useStore()
const list = computed(() => store.state.todoList)
const undoTodos = computed(() => list.value.filter((v) => !v.done))
const doneTodos = computed(() => list.value.filter((v) => v.done))
const state = reactive({
title: '',
dialogVisible: false
})
const handleAdd = () => {
const { title } = state
if (title) {
// mock id
const endTodo = list.value[list.value.length - 1]
const id = endTodo?.id || 0
store.dispatch('addTodo', { title, id: id + 1 })
state.title = ''
}
}
const handleRemove = ({ id }) => {
store.dispatch('removeTodo', id)
}
return {
...toRefs(state),
list,
handleAdd,
handleRemove,
undoTodos,
doneTodos
}
}
})
</script>
<style lang="scss" scoped>
.ml5 {
margin-left: 5px;
}
</style>
首页中显示TodoList
在src/views/home/index.vue中,编写如下内容
<template>
<div>
首页
<router-link :to="{ name: 'TodoList' }">跳转至todoList页面</router-link>
<div>
<p v-for="todo in list" :key="todo.id">
<el-tag type="success">
<span>{{ todo.id }}-</span>
<span>{{ todo.title }}</span>
</el-tag>
</p>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, computed } from 'vue'
import { useStore } from 'vuex'
export default defineComponent({
setup() {
const store = useStore()
const list = computed(() => store.state.todoList)
return {
list
}
}
})
</script>