搭建 vite + vue3 + ts + pinia 项目框架

一、创建项目

  1. 安装vite
    npm i vite -g
    复制代码
  2. 创建项目
    一步创建

npm 6.x

npm create vite@latest my-vue-app --template vue-ts

npm 7+, extra double-dash is needed:

npm create vite@latest my-vue-app – --template vue-ts

yarn

yarn create vite my-vue-app --template vue-ts

pnpm

pnpm create vite my-vue-app --template vue-ts
复制代码

配置创建
npm init vue@latest
在这里插入图片描述

如果安装依赖后运行 npm run dev 报以下错误
在这里插入图片描述

解决方法: 更新node版本

nodejs.org/zh-cn/

二、项目基本配置

  1. 项目icon

在 public目录 下,添加一个 favicon.icon 图片

  1. 项目标题

在 index.html 文件的 title标签 中配置

  1. 配置 tsconfig.json

能让 代码提示 变得更加友好

{
“compilerOptions”: {
// 允许从没有设置默认导出的模块中默认导入。这并不影响代码的输出,仅为了类型检查。
“allowSyntheticDefaultImports”: true,
// 解析非相对模块名的基准目录
“baseUrl”: “.”,
// 模块加载兼容模式,可以是呀import from语法导入commonJS模块
“esModuleInterop”: true,
// 从 tslib 导入辅助工具函数(比如 __extends, __rest等)
“importHelpers”: true,
// 指定生成哪个模块系统代码
“module”: “esnext”,
// 决定如何处理模块。
“moduleResolution”: “node”,
// 启用所有严格类型检查选项。
// 启用 --strict相当于启用 --noImplicitAny, --noImplicitThis, --alwaysStrict,
// --strictNullChecks和 --strictFunctionTypes和–strictPropertyInitialization。
“strict”: true,
“noImplicitAny”: false, //关闭implicitly has an ‘any’ type
// 支持jsx语法
“jsx”: “preserve”,
// 生成相应的 .map文件。
“sourceMap”: true,
// 忽略所有的声明文件( .d.ts)的类型检查。
“skipLibCheck”: true,
// 指定ECMAScript目标版本
“target”: “esnext”,
// 要包含的类型声明文件名列表
“types”: [
“node”
],
“typeRoots”: [
“…/node_modules/@types”
],
// isolatedModules 设置为 true 时,如果某个 ts 文件中没有一个import or export 时,ts 则认为这个模块不是一个 ES Module 模块,它被认为是一个全局的脚本,
“isolatedModules”: true,
// 模块名到基于 baseUrl的路径映射的列表。
“paths”: {
"@/
": [
“src/"
]
},
“vueCompilerOptions”: {
“experimentalDisableTemplateSupport”: true //去掉volar下el标签红色波浪线问题
},
// 编译过程中需要引入的库文件的列表。
“lib”: [
“ESNext”,
“DOM”,
“DOM.Iterable”,
“ScriptHost”
]
},
// 解析的文件
“include”: [
“env.d.ts”,
"src/**/
”,
“src//*.ts",
"src/
/.d.ts",
"src/**/
.tsx”,
“src//.vue",
"src/
.js",
"src/
/*.jsx”
],
“exclude”: [
“node_modules”
],
“references”: [
{
“path”: “./tsconfig.node.json”
}
]
}
复制代码
4. 设置 .prettierrc.json 文件

eslint 配置格式化选项说明

// 1.一行代码的最大字符数,默认是80(printWidth: )
printWidth: 80,
// 2.tab宽度为2空格(tabWidth: )
tabWidth: 2,
// 3.是否使用tab来缩进,我们使用空格(useTabs: )
useTabs: false,
// 4.结尾是否添加分号,false的情况下只会在一些导致ASI错误的其工况下在开头加分号,我选择无分号结尾的风格(semi: )
semi: false,
// 5.使用单引号(singleQuote: )
singleQuote: true,
// 6.object对象中key值是否加引号(quoteProps: “<as-needed|consistent|preserve>”)as-needed只有在需求要的情况下加引号,consistent是有一个需要引号就统一加,preserve是保留用户输入的引号
quoteProps: ‘as-needed’,
// 7.在jsx文件中的引号需要单独设置(jsxSingleQuote: )
jsxSingleQuote: false,
// 8.尾部逗号设置,es5是尾部逗号兼容es5,none就是没有尾部逗号,all是指所有可能的情况,需要node8和es2017以上的环境。(trailingComma: “<es5|none|all>”)
trailingComma: ‘es5’,
// 9.object对象里面的key和value值和括号间的空格(bracketSpacing: )
bracketSpacing: true,
// 10.jsx标签多行属性写法时,尖括号是否另起一行(jsxBracketSameLine: )
jsxBracketSameLine: false,
// 11.箭头函数单个参数的情况是否省略括号,默认always是总是带括号(arrowParens: “<always|avoid>”)
arrowParens: ‘always’,
// 12.range是format执行的范围,可以选执行一个文件的一部分,默认的设置是整个文件(rangeStart: rangeEnd: )
rangeStart: 0,
rangeEnd: Infinity,
// 18. vue script和style标签中是否缩进,开启可能会破坏编辑器的代码折叠
vueIndentScriptAndStyle: false,
// 19. endOfLine: “<lf|crlf|cr|auto>” 行尾换行符,默认是lf,
endOfLine: ‘lf’,
// 20.embeddedLanguageFormatting: “off”,默认是auto,控制被引号包裹的代码是否进行格式化
embeddedLanguageFormatting: ‘off’,
复制代码
{
“singleQuote”: true,
“tabWidth”: 4,
“semi”: false,
}
复制代码
5. 设置 vite.config.ts 文件

安装 gzip 和 mock 依赖
npm i vite-plugin-compression vite-plugin-mock -D
复制代码

import { defineConfig } from ‘vite’
import vue from ‘@vitejs/plugin-vue’
import vueJsx from ‘@vitejs/plugin-vue-jsx’

import path from ‘path’
// gzip插件
import viteCompression from ‘vite-plugin-compression’
// mock插件
import { viteMockServe } from ‘vite-plugin-mock’

const resolve = (dir) => path.resolve(__dirname, dir)

export default defineConfig({
base: ‘./’, //打包路径
publicDir: resolve(‘public’), //静态资源服务的文件夹
plugins: [
vue(),
vueJsx(),
// gzip压缩 生产环境生成 .gz 文件
viteCompression({
verbose: true,
disable: false,
threshold: 10240,
algorithm: ‘gzip’,
ext: ‘.gz’,
}),
//mock
viteMockServe({
mockPath: ‘./mocks’, // 解析,路径可根据实际变动
localEnabled: true, // 此处可以手动设置为true,也可以根据官方文档格式
}),
],
// 配置别名
resolve: {
alias: {
‘@’: resolve(‘src’),
},
// 导入时想要省略的扩展名列表
extensions: [‘.mjs’, ‘.js’, ‘.ts’, ‘.jsx’, ‘.tsx’, ‘.json’, ‘.vue’],
},
css: {
// css预处理器
preprocessorOptions: {
scss: {
additionalData:
‘@import “@/assets/styles/common.scss”;@import “@/assets/styles/reset.scss”;’,
},
},
},
//启动服务配置
server: {
host: ‘0.0.0.0’,
port: 8000,
open: true, // 自动在浏览器打开
proxy: {},
},
// 打包配置
build: {
//浏览器兼容性 “esnext”|“modules”
target: ‘modules’,
//指定输出路径
outDir: ‘build’,
//生成静态资源的存放路径
assetsDir: ‘assets’,
//启用/禁用 CSS 代码拆分
cssCodeSplit: true,
sourcemap: false,
assetsInlineLimit: 10240,
// 打包环境移除console.log, debugger
minify: ‘terser’,
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true,
},
},
rollupOptions: {
input: {
main: resolve(‘index.html’),
},
output: {
entryFileNames: js/[name]-[hash].js,
chunkFileNames: js/[name]-[hash].js,
assetFileNames: [ext]/[name]-[hash].[ext],
},
},
},
})

复制代码
三、项目目录结构划分

assets 存放 => 静态资源

css => 样式重置

img => 图片文件

font => 字体文件

components 存放 => 公共组件

hooks 存放 => 公共常用的hook

mock 存放 => 模拟接口数据

router 存放 => 路由管理

service 存放 => 接口请求

stores 存放 => 状态管理

utils 存放 => 插件、第三方插件

views 存放 => 视图、页面

四、css 样式重置

自定义的css公共文件放置在assets中的css文件中即可

  1. normalize.css
    01 - 安装
    npm i normalize.css
    复制代码
    02 - 引入
    // 在 main.js 中引入
    import ‘normalize.css’;
    复制代码
  2. reset.css
    01 - 代码
    html,
    body,
    div,
    span,
    applet,
    object,
    iframe,
    h1,
    h2,
    h3,
    h4,
    h5,
    h6,
    p,
    blockquote,
    pre,
    a,
    abbr,
    acronym,
    address,
    big,
    cite,
    code,
    del,
    dfn,
    em,
    font,
    img,
    ins,
    kbd,
    q,
    s,
    samp,
    small,
    strike,
    strong,
    sub,
    sup,
    tt,
    var,
    b,
    u,
    i,
    center,
    dl,
    dt,
    dd,
    ol,
    ul,
    li,
    fieldset,
    form,
    label,
    legend,
    caption {
    margin: 0;
    padding: 0;
    border: 0;
    outline: 0;
    font-size: 100%;
    vertical-align: baseline;
    background: transparent;
    }

    table,
    tbody,
    tfoot,
    thead,
    tr,
    th,
    td {
    margin: 0;
    padding: 0;
    outline: 0;
    font-size: 100%;
    vertical-align: baseline;
    background: transparent;
    }

    button,
    input,
    textarea {
    margin: 0;
    padding: 0;
    }

    /* form elements 表单元素 /
    body,
    button,
    input,
    select,
    textarea {
    font: normal 12px/1.5 ‘\5FAE\8F6F\96C5\9ED1’, tahoma, arial;
    }

    /设置的字体,行高/
    h1,
    h2,
    h3,
    h4,
    h5,
    h6,
    th {
    font-size: 100%;
    font-weight: normal;
    }

    /重置标题/
    address,
    cite,
    dfn,
    var {
    font-style: normal;
    }

    /
    将斜体扶正 /
    code,
    kbd,
    pre,
    samp {
    font-family: ‘courier new’, courier, monospace;
    }

    /
    统一等宽字体 /
    small {
    font-size: 12px;
    }

    /
    小于 12px 的中文很难阅读,让 small 正常化 /
    ul,
    ol {
    list-style: none;
    }

    /
    重置列表元素 /
    button,
    input[type=“submit”],
    input[type=“button”] {
    cursor: pointer;
    }

    input[type=“radio”],
    input[type=“checkbox”],
    input[type=“submit”],
    input[type=“reset”] {
    vertical-align: middle;
    cursor: pointer;
    border: none;
    }

    /
    * 重置文本格式元素 /
    a {
    text-decoration: none;
    }
    a:hover {
    text-decoration: underline;
    }
    a:focus {
    outline: 0;
    }
    sup {
    vertical-align: text-top;
    }

    /
    重置,减少对行高的影响 /
    sub {
    vertical-align: text-bottom;
    }

    /
    重置表单元素 /
    legend {
    color: #000;
    }

    /
    for ie6 /
    fieldset,
    img {
    border: 0;
    }

    /
    img 搭车:让链接里的 img 无边框 /
    button,
    input,
    select,
    textarea {
    background: transparent;
    font-size: 100%;
    outline: 0;
    }

    /
    使得表单元素在 ie 下能继承字体大小 /
    /
    注:optgroup 无法扶正 /
    table {
    border-collapse: collapse;
    border-spacing: 0;
    }

    td,
    th {
    vertical-align: middle;
    }

    /
    重置表格元素 */
    /
    重置 HTML5 元素 */
    article,
    aside,
    details,
    figcaption,
    figure,
    footer,
    header,
    hgroup,
    menu,
    nav,
    section,
    summary,
    time,
    mark,
    audio,
    video {
    display: block;
    margin: 0;
    padding: 0;
    }

    /回复标签重置/
    blockquote,
    q {
    quotes: none;
    }

    blockquote:before,
    blockquote:after,
    q:before,
    q:after {
    content: ‘’;
    display: none;
    }
    复制代码
    02 - 引入
    // 在 main.js 中引入
    import ‘./assets/css/reset.css’;
    复制代码
  3. common.css
    01 - 代码
    // 清除浮动
    .clearfix {
    *zoom: 1;
    }


复制代码
02 - 引入
// 在 main.js 中引入
import ‘./assets/css/common.css’;
复制代码
五、vue-router 路由配置

一步创建需要安装依赖、配置路由, 引入mian.ts, 配置创建则已自动生成

  1. 安装
    npm i vue-router
    复制代码
  2. 配置
    // 1. 导入
    import { createRouter, createWebHashHistory } from ‘vue-router’;

    // 2. 创建路由对象
    const router = createRouter({
    history: createWebHashHistory(),
    routes: [
    {
    path: ‘/’,
    redirect: ‘/home’
    },
    {
    path: ‘/home’,
    component: () => import(‘xxx/home.vue’)
    }
    ]
    });

    // 3. 导出
    export default router;
    复制代码
  3. 引入
    // main.js

import { createApp } from ‘vue’;
import App from ‘./App.vue’;
// 1. 导入
import router from ‘./router’;

import ‘normalize.css’;
import ‘./assets/css/reset.css’;
import ‘./assets/css/common.css’;

// 2. 使用
createApp(App).use(router).mount(‘#app’);
复制代码
4. 使用

在该用的地方加上

六、pinia 状态管理

一步创建需要安装依赖、配置路由, 引入mian.ts, 配置创建则已自动生成

  1. 安装
    npm i pinia
    复制代码
  2. 引入
    // main.js
    import { createApp } from ‘vue’;
    import { createPinia } from “pinia”;

    import App from ‘./App.vue’;
    // 1. 导入
    import router from ‘./router’;

    import ‘normalize.css’;
    import ‘./assets/css/reset.css’;
    import ‘./assets/css/common.css’;

    // 2. 使用
    createApp(App).use(createPinia()).use(router).mount(‘#app’);
    复制代码
  3. 模块
    // 1. 导入
    import { defineStore } from ‘pinia’;

    // 2. 使用
    const useDemoStore = defineStore(‘demoStore’, {
    state: () => ({
    arrList: []
    }),
    actions: {},
    getters: {}
    });

    // 3. 导出
    export default useDemoStore;
    复制代码
    七、集成 Axios HTTP 工具
    安装依赖
    npm i axios
    复制代码
    请求配置
    在 utils 目录下创建 request.ts 文件,配置好适合自己业务的请求拦截和响应拦截
    import axios, { AxiosRequestConfig, Method } from ‘axios’;

// 创建请求实例
const instance = axios.create({
baseURL: ‘/api’,
// 指定请求超时的毫秒数
timeout: 10000,
// 表示跨域请求时是否需要使用凭证
withCredentials: false,
});

// 设置请求头
instance.defaults.headers.post[‘Content-Type’] = ‘application/json;charset=UTF-8’;
instance.defaults.headers.put[‘Content-Type’] = ‘application/x-www-form-urlencoded’;
// instance.defaults.headers.put[‘Content-Type’] = ‘application/json’;

// 取消重复请求
const pending = [];

// 定义接口
interface PendingType {
url?: string;
method?: Method;
params: any;
data: any;
cancel: any;
}

// 移除重复请求
const removePending = (config: AxiosRequestConfig) => {
for (const key in pending) {
const item: number = +key;
const list: PendingType = pending[key];
// 当前请求在数组中存在时执行函数体
if (list.url === config.url && list.method === config.method && JSON.stringify(list.params) === JSON.stringify(config.params) && JSON.stringify(list.data) === JSON.stringify(config.data)) {
// 执行取消操作
list.cancel(‘操作太频繁,请稍后再试’);
// 从数组中移除记录
pending.splice(item, 1);
}
}
};

// 请求拦截器(发起请求之前的拦截)
instance.interceptors.request.use(
(config): AxiosRequestConfig => {
removePending(config);
config.cancelToken = new axios.CancelToken(c => {
pending.push({ url: config.url, method: config.method, params: config.params, data: config.data, cancel: c });
});
/**
* 在这里一般会携带前台的参数发送给后台,比如下面这段代码:
* const token = getToken()
* if (token) {
* config.headers.token = token
* }
*/
return config;
},
(error) => {
return Promise.reject(error);
},
);

// 响应拦截器(获取到响应时的拦截)
instance.interceptors.response.use(
(response) => {
removePending(response.config);
/**
* 根据你的项目实际情况来对 response 和 error 做处理
* 这里对 response 和 error 不做任何处理,直接返回
*/
return response;
},
(error) => {
return Promise.reject(error);
},
);

interface ResType {
code: number;
data?: T;
msg?: string;
message?: string;
err?: string;
}

interface Http {
post(url: string, data?: unknown, params?: unknown,): Promise<ResType>;
get(url: string, params?: unknown): Promise<ResType>;
put(url: string, data?: unknown, params?: any): Promise<ResType>;
_delete(url: string, params?: unknown): Promise<ResType>;
}

// 导出常用函数
const http: Http = {
post(url, data, params) {
return new Promise((resolve, reject) => {
instance
.post(url, JSON.stringify(data), params)
.then((res) => {
resolve(res.data);
})
.catch((err) => {
reject(err.data);
});
});
},
get(url, params) {
return new Promise((resolve, reject) => {
axios
.get(url, { params })
.then((res) => {
resolve(res.data);
})
.catch((err) => {
reject(err.data);
});
});
},
put(url, data, params) {
return new Promise((resolve, reject) => {
instance
.put(url, data, params)
.then((res) => {
resolve(res.data);
})
.catch((err) => {
reject(err.data);
});
});
},
_delete(url, params) {
return new Promise((resolve, reject) => {
instance
.delete(url, params)
.then((res) => {
resolve(res.data);
})
.catch((err) => {
reject(err.data);
});
});
}
}

export default http;
复制代码
之后在 api 文件夹中以业务模型对接口进行拆分,举个例子,将所有跟用户相关接口封装在 User 类中,此类称作用户模型。
在 User 类中比如有登录、注册、获取用户信息等方法,如果有业务逻辑变动,只需要修改相关方法即可。
import { post } from ‘@/utils/request’;

export default class User {
/**

  • 登录
  • @param {String} username 用户名
  • @param {String} password 密码
  • @returns
    */
    static async login(username: string, password: string) {
    return post(‘/login’, {
    username,
    password,
    });
    }
    }
    复制代码
    把每个业务模型独立成一个 js 文件,声明一个类通过其属性和方法来实现这个模型相关的数据获取,这样可以大大提升代码的可读性与可维护性。
    模拟演示
    在需要使用接口的地方,引入对应的业务模型文件,参考如下

复制代码
八、使用scss, 并定义全局scss变量
首先我们先安装sass和sass-loader:
npm i sass sass-loader -D
复制代码
然后我们需要在vite.config.ts中配置css预处理器
export default defineConfig({
css: {
preprocessorOptions: {
scss: {
additionalData: ‘@import “@/assets/styles/global.scss”;@import “@/assets/styles/reset.scss”;’,
},
}
}
})
复制代码
我们这里默认加载global.scss中的样式,那么我们就需要创建一个这样的文件:
// src/assets/style/global.scss
$primary-color: #5878e2; // 主题色
复制代码
最后在main.ts中引入即可:
import “./assets/style/global.scss”;
复制代码
然后在组件中使用时,就可以直接使用:

{{global.token}}
复制代码 样式穿透 在 Vue3 中,改变了以往样式穿透的语法,如果继续使用 ::v-deep、/deep/、>>> 等语法的话,会出现一个警告,下面是新的语法: /* 深度选择器 */ :deep(selector) { /* ... */ }

/* 插槽选择器 /
:slotted(selector) {
/
… */
}

/* 全局选择器 /
:global(selector) {
/
… */
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JackieChan_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值