🚀[新手向]从零开始搭建vue项目

1,472 阅读8分钟

logo-with-shadow.png

前言

node的版本如下:

  • node: v22.13.0
  • npm: v10.9.2
  • pnpm: 10.5.0

​ 最近在鼓捣一些新的项目,需要一个适合的项目模板,但是目前找到的模板功能都太全了反而不适合,于是打算自己从零搭建一个vue3项目,也刚好巩固一下知识。

创建项目

这边用vite创建vue3项目

执行下面命令,按照提示选择就会创建一个最基本的vue3项目

npm create vite 

image-20250226221440499.png

刚开始的项目结构如下

image-20250226221840173.png

运行项目

因为是高版本的node推荐用pnpm,后面的操作都用pnpm

安装相关的node

pnpm install 

下面这样依赖就安装成功了,warning里面的不用管

image-20250226222544032.png

运行下面的命令

pnpm run dev 

image-20250226223041322.png

这样项目就成功运行起来了,第一步就完成了接下来安装常用的第三方库。

路由配置

安装依赖

pnpm add vue-router@4

image-20250226223844977.pngsrc下面创建一个router文件夹专门用来放路由文件,并且新建index.ts文件 image-20250226224014648.png

我个人比较习惯按照业务模块来拆分我的路由文件,所以我在router下面创建了一个modules文件夹用来管理不同的子模块路由

最终结构如下

image-20250227224948738.png

index.ts的内容

import { createMemoryHistory, createRouter, type RouteRecordRaw } from 'vue-router'
import arcgisRoute from './modules/arcgis'
import cesiumRouter from './modules/cesium'
import openlayerRoute from './modules/openlayer'

const routes:RouteRecordRaw[] = [
  { path: '/', name:"home",redirect:'/arcgis',
    children:[
      arcgisRoute,
      cesiumRouter,
      openlayerRoute
    ]
   },
]

const router = createRouter({
  history: createMemoryHistory(),
  routes,
})
export default router

modules模块里面的内容

// cesium.ts文件

import type { RouteRecordRaw } from "vue-router"

const arcgisRouter:RouteRecordRaw = { path: '/arcgis', component: ()=> import('@/pages/arcgis/index.vue') }
export default arcgisRouter

// openlayer.ts文件

import type { RouteRecordRaw } from "vue-router"

const openlayerRouter:RouteRecordRaw = { path: '/openlayer', component: ()=> import('@/pages/openlayer/index.vue') }
export default openlayerRouter

// arcgis.ts文件
import type { RouteRecordRaw } from "vue-router"

const arcgisRouter:RouteRecordRaw = { path: '/arcgis', component: ()=> import('@/pages/arcgis/index.vue') }
export default arcgisRouter

最后在main.ts文件里面注册我们的路由

import { createApp } from 'vue'
import './styles/style.css'
import App from './App.vue'
import router from './router'
createApp(App).use(router).mount('#app')

App.vue代码如下

<template>
  <div class="container">
    <router-view></router-view> 
  </div>
</template>

<script setup lang="ts">
</script>

<style scoped>
 .container{
  position: relative;
  height: 100%;
  background: #fff;
  color: #000;
  padding: 24px;
  box-sizing: border-box;
 }
</style>

arcgis.vue代码如下

<template>
    <div>
        arcgis
    </div>
</template>

<script setup lang="ts">

</script>

<style scoped>

</style>

操作完成之后,最终你的页面上应该就可以正常显示对应路由的内容了

image-20250227232247047.png

这里有点要注意,我们的路由用的是默认的createMemoryHistory模式,直接改url是不会触发页面变化的,如果你要触发页面变化,就用vue-router提供的api

如果想要修改url就改变页面,就把路由状态修改为别的模式

import { createWebHistory , createRouter, type RouteRecordRaw } from 'vue-router'
import arcgisRoute from './modules/arcgis'
import cesiumRouter from './modules/cesium'
import openlayerRoute from './modules/openlayer'

const routes:RouteRecordRaw[] = [
  { path: '/', name:"home",redirect:'/arcgis',
    children:[
      arcgisRoute,
      cesiumRouter,
      openlayerRoute
    ]
   },
]

const router = createRouter({
  // history: createMemoryHistory(),
  history:createWebHistory(),
  routes,
})
export default router

修改完成之后,路由就可以通过修改url进行修改

路由演示.gif

状态管理

安装pinia

pnpm add pinia

main.ts文件中注册pinia

import { createApp } from 'vue'
import './styles/style.css'
import App from './App.vue'
import router from './router'
import { createPinia   } from 'pinia'
const pinia = createPinia()
createApp(App).use(pinia).use(router).mount('#app')

src下面创建store文件夹,这个文件夹以后用来管理各模块的状态

image-20250302224229194.png

user.ts内容如下


import { defineStore } from 'pinia'
 
export const useUserStore = defineStore('userStore', {
  state: () => {
    return {
      name: ''
    }
  },
  getters: {
     getStatus: (state) => state.name?'登录':'未登录'
  },
  actions: {
    updatUserName(name:string = '') {
        this.name = name
    },
  }
})

使用例子

<template>
    <div class="demo">
        <span> 用户名:{{ userStroe.name }}</span>
        <span>状态:{{ userStroe.getStatus  }}</span>
        <button @click="setName">设置名字</button>        
    </div>
</template>

<script setup lang="ts">
   import { useUserStore } from '../store/user'
   const userStroe = useUserStore()
   const setName = ()=>{
    userStroe.updatUserName('测试名字')
   }
</script>

<style scoped>
    .demo{
        display: flex;
        gap: 24px;
        flex-direction: column;
        button{
            width: 160px;
            height: 38px;
            line-height: 38px;
        }
    }
</style>

效果如下

测试pinia.gif

路径别名

安装依赖

pnpm add install @types/node

因为需要用到node的一些模块所以需要安装对应的类型依赖

vite.config.ts配置如下

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path';
export default defineConfig({
  plugins: [vue()],
      resolve: {
        alias: {
        '@': path.resolve(__dirname, 'src'),
        },
    },
})

最后在tsconfig.app.jsontsconfig.node.json配置里面添加如下内容

 "compilerOptions": {
     "baseUrl": ".",
        "paths": {
          "@/*": ["src/*"]
        },
 }

这样配置完成之后就可以通过@/xxx的方式进行访问了

image-20250302231302574.png

代码规范

安装依赖

 pnpm add eslint prettier eslint-plugin-prettier  globals  @eslint/js eslint-plugin-vue typescript-eslint vue-eslint-parser

eslint

创建eslint.config.ts文件,直接复制下面的配置填入

import globals from 'globals'
import eslint from '@eslint/js'
import tseslint from 'typescript-eslint'
import eslintPluginVue from 'eslint-plugin-vue'
import vueParser from 'vue-eslint-parser'
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'

export default [
  {
    ignores: [
      'node_modules',
      'dist',
      '.gitignore',
      'package.json',
      'package-lock.json',
      'dist-ssr',
      '*.local',
      '.npmrc',
      '.DS_Store',
      'dev-dist',
      'dist_electron',
      '*.d.ts',
      'src/assets/**'
    ]
  },
  /** js推荐配置 */
  eslint.configs.recommended,
  /** vue推荐配置 */
  ...eslintPluginVue.configs['flat/recommended'],
  /** prettier 配置 */
  eslintPluginPrettierRecommended,

  //javascript 规则
  {
    files: ['**/*.{js,mjs,cjs,vue,ts}'],
    rules: {
      // 对象结尾逗号
      'comma-dangle': 'off',

      // 关闭未定义变量
      'no-undef': 'off',

      // 确保 Prettier 的行为不会被 ESLint 覆盖
      quotes: ['error', 'single', { allowTemplateLiterals: true }],

      // 关闭对未定义变量的警告
      'no-undefined': 'off',

      //不使用的变量不报错
      'no-unused-vars': 'off',

      // 禁止使用不规范的空格
      'no-irregular-whitespace': 'off',

      // 函数括号前的空格
      'space-before-function-paren': 0,

      // 箭头函数的空格
      'arrow-spacing': [
        2,
        {
          before: true,
          after: true
        }
      ],

      // 代码块的空格
      'block-spacing': [2, 'always'],

      // 大括号风格
      'brace-style': [
        2,
        '1tbs',
        {
          allowSingleLine: true
        }
      ],

      // 对象属性换行
      'object-property-newline': 'off',

      // JSX 引号风格
      'jsx-quotes': [2, 'prefer-single'],

      // 对象键值对之间的空格
      'key-spacing': [
        2,
        {
          beforeColon: false,
          afterColon: true
        }
      ],

      // 关键字之间的空格
      'keyword-spacing': [
        2,
        {
          before: true,
          after: true
        }
      ],

      // 构造函数首字母大写
      'new-cap': [
        2,
        {
          newIsCap: true,
          capIsNew: false
        }
      ],

      // new 操作符使用时需要括号
      'new-parens': 2,

      // 禁止使用 Array 构造函数
      'no-array-constructor': 2,

      // 禁止调用 caller 和 callee
      'no-caller': 2,

      // 禁止重新分配类名
      'no-class-assign': 2,

      // 禁止条件中的赋值操作
      'no-cond-assign': 2,

      // 禁止 const 重新分配
      'no-const-assign': 2,

      // 正则表达式中的控制字符
      'no-control-regex': 0,

      // 禁止删除变量
      'no-delete-var': 2,

      // 禁止在函数参数中使用重复名称
      'no-dupe-args': 2,

      // 禁止在类中使用重复名称的成员
      'no-dupe-class-members': 2,

      // 禁止在对象字面量中使用重复的键
      'no-dupe-keys': 2,

      // 禁止重复的 case 标签
      'no-duplicate-case': 2,

      // 禁止空的字符类
      'no-empty-character-class': 2,

      // 禁止空的解构模式
      'no-empty-pattern': 2,

      // 禁止使用 eval
      'no-eval': 2,

      // 不允许出现空的代码块
      'no-empty': 2,

      // 禁止不必要的布尔转换
      'no-extra-boolean-cast': 2,

      // 禁止不必要的括号
      'no-extra-parens': [2, 'functions'],

      // 禁止 case 语句落空
      'no-fallthrough': 2,

      // 禁止在数字后面添加小数点
      'no-floating-decimal': 2,

      // 禁止对函数声明重新赋值
      'no-func-assign': 2,

      // 禁止出现歧义多行表达式
      'no-unexpected-multiline': 2,

      // 禁止不需要的转义
      'no-useless-escape': 0,

      // 数组的括号前后的间距
      'array-bracket-spacing': [2, 'never']
    }
  },

  // vue 规则
  {
    files: ['**/*.vue'],
    languageOptions: {
      parser: vueParser,
      globals: { ...globals.browser, ...globals.node },
      parserOptions: {
        /** typescript项目需要用到这个 */
        parser: tseslint.parser,
        ecmaVersion: 'latest',
        /** 允许在.vue 文件中使用 JSX */
        ecmaFeatures: {
          jsx: true
        }
      }
    },
    rules: {
      'vue/component-definition-name-casing': 'off',
      'vue/singleline-html-element-content-newline': ['off'],
      'vue/no-mutating-props': [
        'error',
        {
          shallowOnly: true
        }
      ],
      // 要求组件名称始终为 “-” 链接的单词
      'vue/multi-word-component-names': 'off',

      // 关闭 index.html 文件报 clear 错误
      'vue/comment-directive': 'off',

      // 关闭对 defineProps 的有效性检查
      'vue/valid-define-props': 'off',

      // 允许在一个文件中定义多个组件
      'vue/one-component-per-file': 'off',

      // 关闭 Prop 类型要求的警告
      'vue/require-prop-types': 'off',
      // 关闭属性顺序要求
      'vue/attributes-order': 'off',

      // 关闭对默认 Prop 的要求
      'vue/require-default-prop': 'off',

      // 关闭连字符命名检验
      'vue/attribute-hyphenation': 'off',

      // 关闭自闭合标签的要求
      'vue/html-self-closing': 'off',

      // 禁止在关闭的括号前有换行
      'vue/html-closing-bracket-newline': 'off',
      // 允许使用 v-html 指令
      'vue/no-v-html': 'off'
    }
  }
]


prettier

创建.prettierrc.cjs文件并填入下面内容

module.exports = {
    printWidth: 120, // 一行的字符数换行
    tabWidth: 2, // 一个tab代表几个空格数
    useTabs: false, // 是否使用tab进行缩进
    singleQuote: true, // 字符串是否使用单引号
    semi: false, // 行尾是否使用分号,默认为true
    trailingComma: 'none', // 是否使用尾逗号
    arrowParens: 'avoid', // 箭头函数单变量省略括号
    bracketSpacing: true, // 对象大括号直接是否有空格,默认为true,效果:{ foo: bar }
    endOfLine: 'auto', // 保留在 Windows 和 Unix 下的换行符
    quoteProps: 'preserve' // 对象属性的引号使用
    }

vscode这边记得设置一下配置文件路径,不然格式化的时候找不到配置文件

打开vscodesettting.json文件

image-20250302234929757.png 写入配置

  "prettier.configPath": ".prettierrc.cjs"

创建.prettierignore文件,用来忽略不想被格式化的文件

node_modules
dist
*.local
.npmrc
dist_electron
auto-imports.d.ts

最后package.json里面配置相关命令

{
  "name": "vue-gis",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vue-tsc -b && vite build",
    "preview": "vite preview",
    "lint:fix": "eslint . --fix",
    "lint": "eslint ."
  },
  "dependencies": {
    "@eslint/js": "^9.21.0",
    "@types/node": "^22.13.5",
    "element-plus": "^2.9.5",
    "eslint-plugin-prettier": "^5.2.3",
    "globals": "^16.0.0",
    "pinia": "^3.0.1",
    "prettier": "^3.5.2",
    "sass": "^1.85.1",
    "vue": "^3.5.13",
    "vue-eslint-parser": "^9.4.3",
    "vue-router": "4"
  },
  "devDependencies": {
    "@vitejs/plugin-vue": "^5.2.1",
    "@vue/tsconfig": "^0.7.0",
    "eslint": "^9.21.0",
    "eslint-plugin-vue": "^9.32.0",
    "typescript": "~5.7.2",
    "typescript-eslint": "^8.25.0",
    "vite": "^6.1.0",
    "vue-tsc": "^2.2.0"
  },
  "pnpm": {
    "ignoredBuiltDependencies": [
      "esbuild"
    ]
  }
}

这样就可以通过命令修复全局文件了

pnpm run lint:fix

这样最基本的代码格式化就配置完成,如果需要stylelintgit hook就需要额外配置这边略过

UI库

vue比较常用的还是element系列,这边以element-plus为例子

安装依赖

pnpm add element-plus

main.ts中引入element-plus

import { createApp } from 'vue'
import './styles/style.css'
import App from './App.vue'
import router from './router'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import { createPinia  } from 'pinia'
const pinia = createPinia()
createApp(App).use(ElementPlus).use(pinia).use(router).mount('#app')

使用

<template>
    <div class="demo">
        <span> 用户名:{{ userStroe.name }}</span>
        <span>状态:{{ userStroe.getStatus  }}</span>
        <button @click="setName">设置名字</button>       
        <el-table :data="tableData" style="width: 100%">
    <el-table-column prop="date" label="Date" width="180" />
    <el-table-column prop="name" label="Name" width="180" />
    <el-table-column prop="address" label="Address" />
  </el-table>
        
    </div>
</template>

<script setup lang="ts">
   import { useUserStore } from '../store/user'
   const userStroe = useUserStore()
   const setName = ()=>{
    userStroe.updatUserName('测试名字')
   }
   const tableData = [
  {
    date: '2016-05-03',
    name: 'Tom',
    address: 'No. 189, Grove St, Los Angeles',
  },
  {
    date: '2016-05-02',
    name: 'Tom',
    address: 'No. 189, Grove St, Los Angeles',
  },
  {
    date: '2016-05-04',
    name: 'Tom',
    address: 'No. 189, Grove St, Los Angeles',
  },
  {
    date: '2016-05-01',
    name: 'Tom',
    address: 'No. 189, Grove St, Los Angeles',
  },
]
</script>

<style lang="scss" scoped>
$primary-color: #cb3837;
    .demo{
        display: flex;
        gap: 24px;
        flex-direction: column;
        button{
            width: 160px;
            height: 38px;
            line-height: 38px;
            background:$primary-color;
        }
    }
</style>

image-20250302233308965.png

现在就已经可以使用element-plus组件了

样式处理

日常开发用的比较多的就就是sass,vue3sass安装非常方便

安装依赖

pnpm add sass 

安装完成之后就可以了,vue组件中就可以使用sass

<template>
    <div class="demo">
        <span> 用户名:{{ userStroe.name }}</span>
        <span>状态:{{ userStroe.getStatus  }}</span>
        <button @click="setName">设置名字</button>        
    </div>
</template>

<script setup lang="ts">
   import { useUserStore } from '../store/user'
   const userStroe = useUserStore()
   const setName = ()=>{
    userStroe.updatUserName('测试名字')
   }
</script>

<style lang="scss" scoped>
$primary-color: #cb3837;
    .demo{
        display: flex;
        gap: 24px;
        flex-direction: column;
        button{
            width: 160px;
            height: 38px;
            line-height: 38px;
            background:$primary-color;
        }
    }
</style>

image-20250302231648231.png

最后

经过上面的各种操作,一个vue3项目就搭建起来了,如果没有从0开始搭建过项目,强烈建议动手一次,说不定会有额外的收获.

当然如果是正常开发,肯定是直接使用别人搭建好的,功能全的模板来进行开发.