搭建 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
搭建 vite + vue3 + ts + pinia 项目框架_第1张图片

如果安装依赖后运行 npm run dev 报以下错误

搭建 vite + vue3 + ts + pinia 项目框架_第2张图片

解决方法: 更新node版本

https://nodejs.org/zh-cn/

二、项目基本配置

1. 项目icon

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

2. 项目标题

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

3. 配置 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是保留用户输入的引号
  quoteProps: 'as-needed',
  //7.在jsx文件中的引号需要单独设置(jsxSingleQuote: )
  jsxSingleQuote: false,
  //8.尾部逗号设置,es5是尾部逗号兼容es5,none就是没有尾部逗号,all是指所有可能的情况,需要node8和es2017以上的环境。(trailingComma: "")
  trailingComma: 'es5',
  //9.object对象里面的key和value值和括号间的空格(bracketSpacing: )
  bracketSpacing: true,
  //10.jsx标签多行属性写法时,尖括号是否另起一行(jsxBracketSameLine: )
  jsxBracketSameLine: false,
  //11.箭头函数单个参数的情况是否省略括号,默认always是总是带括号(arrowParens: "")
  arrowParens: 'always',
  //12.range是format执行的范围,可以选执行一个文件的一部分,默认的设置是整个文件(rangeStart:   rangeEnd: )
  rangeStart: 0,
  rangeEnd: Infinity,
  //18. vue script和style标签中是否缩进,开启可能会破坏编辑器的代码折叠
  vueIndentScriptAndStyle: false,
  //19.    endOfLine: "" 行尾换行符,默认是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'
​
constresolve = (dir) => path.resolve(__dirname, dir)
​
export defaultdefineConfig({
    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]`,
            },
        },
    },
})
​

三、项目目录结构划分

  1. assets 存放 => 静态资源

  • css => 样式重置

  • img => 图片文件

  • font => 字体文件

  1. components 存放 => 公共组件

  1. hooks 存放 => 公共常用的hook

  1. mock 存放 => 模拟接口数据

  1. router 存放 => 路由管理

  1. service 存放 => 接口请求

  1. stores 存放 => 状态管理

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

  1. 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. 创建路由对象constrouter = createRouter({
    history: createWebHashHistory(),
    routes: [
        {
            path: '/',
            redirect: '/home'
        },
        {
            path: '/home',
            component: () => import('xxx/home.vue')
        }
    ]
});
​
// 3. 导出
export default router;

3. 引入

// main.jsimport { createApp } from'vue';
importAppfrom'./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.jsimport { createApp } from'vue';
import { createPinia } from"pinia";
​
importAppfrom'./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. 导出exportdefault 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>;
    get(url: string, params?: unknown): Promise>;
    put(url: string, data?: unknown, params?: any): Promise>;
    _delete(url: string, params?: unknown): Promise>;
}

// 导出常用函数  
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 defaultdefineConfig({
    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";

然后在组件中使用时,就可以直接使用:



你可能感兴趣的:(vue.js,javascript,前端)