阅读 462

使用vue3+vite+ts+element-plus制作的TodoList示例

初始化项目

  • 创建
yarn create @vitejs/app
复制代码

选择框架:vue, 选择变种:vue-ts

71f2dccd-a723-4141-b533-5d21d0052199.png

  • 进行项目目录,安装依赖
cd vue3-vite-todolist
yarn
复制代码
  • 启动
yarn dev
复制代码

出现如下,运行成功

f4ee6be2-3ad9-4cc1-98b8-62a3ad47b368.png

浏览器访问 http://localhost:3000/ 362f85f7-d310-491c-8601-9d2fe8895fd5.png

安装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.vuetodo-list/index.vue文件

首页 home/index.vue

<template>
  <div>
    首页
    <router-link :to="{ name: 'TodoList' }">去todoList页面</router-link>
  </div>
</template>
复制代码

todoListtodo-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>
复制代码

代码gitee地址

文章分类
前端
文章标签