当前位置:   article > 正文

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

vue3+ts+vite+pinia

一、创建项目

1. 安装vite
npm i vite -g
2. 创建项目
  • 一步创建

  1. # npm 6.x
  2. npm create vite@latest my-vue-app --template vue-ts
  3. # npm 7+, extra double-dash is needed:
  4. npm create vite@latest my-vue-app -- --template vue-ts
  5. # yarn
  6. yarn create vite my-vue-app --template vue-ts
  7. # pnpm
  8. pnpm create vite my-vue-app --template vue-ts
  • 配置创建

npm init vue@latest

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

解决方法: 更新node版本

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

二、项目基本配置

1. 项目icon
在 public目录 下,添加一个 favicon.icon 图片
2. 项目标题
在 index.html 文件的 title标签 中配置
3. 配置 tsconfig.json
能让 代码提示 变得更加友好
  1. {
  2. "compilerOptions":{
  3. // 允许从没有设置默认导出的模块中默认导入。这并不影响代码的输出,仅为了类型检查。
  4. "allowSyntheticDefaultImports":true,
  5. // 解析非相对模块名的基准目录
  6. "baseUrl":".",
  7. // 模块加载兼容模式,可以是呀import from语法导入commonJS模块
  8. "esModuleInterop":true,
  9. // 从 tslib 导入辅助工具函数(比如 __extends, __rest等)
  10. "importHelpers":true,
  11. // 指定生成哪个模块系统代码
  12. "module":"esnext",
  13. // 决定如何处理模块。
  14. "moduleResolution":"node",
  15. // 启用所有严格类型检查选项。
  16. // 启用 --strict相当于启用 --noImplicitAny, --noImplicitThis, --alwaysStrict,
  17. // --strictNullChecks和 --strictFunctionTypes和--strictPropertyInitialization。
  18. "strict":true,
  19. "noImplicitAny":false,//关闭implicitly has an 'any' type
  20. // 支持jsx语法
  21. "jsx":"preserve",
  22. // 生成相应的 .map文件。
  23. "sourceMap":true,
  24. // 忽略所有的声明文件( *.d.ts)的类型检查。
  25. "skipLibCheck":true,
  26. // 指定ECMAScript目标版本
  27. "target":"esnext",
  28. // 要包含的类型声明文件名列表
  29. "types":[
  30. "node"
  31. ],
  32. "typeRoots":[
  33. "../node_modules/@types"
  34. ],
  35. // isolatedModules 设置为 true 时,如果某个 ts 文件中没有一个import or export 时,ts 则认为这个模块不是一个 ES Module 模块,它被认为是一个全局的脚本,
  36. "isolatedModules":true,
  37. // 模块名到基于 baseUrl的路径映射的列表。
  38. "paths":{
  39. "@/*":[
  40. "src/*"
  41. ]
  42. },
  43. "vueCompilerOptions":{
  44. "experimentalDisableTemplateSupport":true//去掉volar下el标签红色波浪线问题
  45. },
  46. // 编译过程中需要引入的库文件的列表。
  47. "lib":[
  48. "ESNext",
  49. "DOM",
  50. "DOM.Iterable",
  51. "ScriptHost"
  52. ]},
  53. // 解析的文件
  54. "include":[
  55. "env.d.ts",
  56. "src/**/*",
  57. "src/**/*.ts",
  58. "src/**/*.d.ts",
  59. "src/**/*.tsx",
  60. "src/**/*.vue",
  61. "src/*.js",
  62. "src/**/*.jsx"],
  63. "exclude":[
  64. "node_modules"],
  65. "references":[
  66. {
  67. "path":"./tsconfig.node.json"
  68. }]}
4. 设置 .prettierrc.json 文件
eslint 配置格式化选项说明
  1. // 1.一行代码的最大字符数,默认是80(printWidth: <int>)
  2. printWidth: 80,
  3. //2.tab宽度为2空格(tabWidth: <int>)
  4. tabWidth: 2,
  5. //3.是否使用tab来缩进,我们使用空格(useTabs: <bool>)
  6. useTabs: false,
  7. //4.结尾是否添加分号,false的情况下只会在一些导致ASI错误的其工况下在开头加分号,我选择无分号结尾的风格(semi: <bool>)
  8. semi: false,
  9. //5.使用单引号(singleQuote: <bool>)
  10. singleQuote: true,
  11. //6.object对象中key值是否加引号(quoteProps: "<as-needed|consistent|preserve>")as-needed只有在需求要的情况下加引号,consistent是有一个需要引号就统一加,preserve是保留用户输入的引号
  12. quoteProps: 'as-needed',
  13. //7.在jsx文件中的引号需要单独设置(jsxSingleQuote: <bool>)
  14. jsxSingleQuote: false,
  15. //8.尾部逗号设置,es5是尾部逗号兼容es5,none就是没有尾部逗号,all是指所有可能的情况,需要node8和es2017以上的环境。(trailingComma: "<es5|none|all>")
  16. trailingComma: 'es5',
  17. //9.object对象里面的key和value值和括号间的空格(bracketSpacing: <bool>)
  18. bracketSpacing: true,
  19. //10.jsx标签多行属性写法时,尖括号是否另起一行(jsxBracketSameLine: <bool>)
  20. jsxBracketSameLine: false,
  21. //11.箭头函数单个参数的情况是否省略括号,默认always是总是带括号(arrowParens: "<always|avoid>")
  22. arrowParens: 'always',
  23. //12.range是format执行的范围,可以选执行一个文件的一部分,默认的设置是整个文件(rangeStart: <int> rangeEnd: <int>)
  24. rangeStart: 0,
  25. rangeEnd: Infinity,
  26. //18. vue script和style标签中是否缩进,开启可能会破坏编辑器的代码折叠
  27. vueIndentScriptAndStyle: false,
  28. //19. endOfLine: "<lf|crlf|cr|auto>" 行尾换行符,默认是lf,
  29. endOfLine: 'lf',
  30. //20.embeddedLanguageFormatting: "off",默认是auto,控制被引号包裹的代码是否进行格式化
  31. embeddedLanguageFormatting: 'off',
  1. {
  2. "singleQuote":true,
  3. "tabWidth":4,
  4. "semi":false,
  5. }
5. 设置 vite.config.ts 文件
安装 gzip 和 mock 依赖
npm i vite-plugin-compression vite-plugin-mock -D
  1. import { defineConfig } from'vite'
  2. import vue from'@vitejs/plugin-vue'
  3. import vueJsx from'@vitejs/plugin-vue-jsx'
  4. import path from'path'// gzip插件
  5. import viteCompression from'vite-plugin-compression'// mock插件
  6. import { viteMockServe } from'vite-plugin-mock'
  7. constresolve = (dir) => path.resolve(__dirname, dir)
  8. export defaultdefineConfig({
  9. base: './', //打包路径
  10. publicDir: resolve('public'), //静态资源服务的文件夹
  11. plugins: [
  12. vue(),
  13. vueJsx(),
  14. // gzip压缩 生产环境生成 .gz 文件
  15. viteCompression({
  16. verbose: true,
  17. disable: false,
  18. threshold: 10240,
  19. algorithm: 'gzip',
  20. ext: '.gz',
  21. }),
  22. //mock
  23. viteMockServe({
  24. mockPath: './mocks', // 解析,路径可根据实际变动
  25. localEnabled: true, // 此处可以手动设置为true,也可以根据官方文档格式
  26. }),
  27. ],
  28. // 配置别名
  29. resolve: {
  30. alias: {
  31. '@': resolve('src'),
  32. },
  33. // 导入时想要省略的扩展名列表
  34. extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue'],
  35. },
  36. css: {
  37. // css预处理器
  38. preprocessorOptions: {
  39. scss: {
  40. additionalData:
  41. '@import "@/assets/styles/common.scss";@import "@/assets/styles/reset.scss";',
  42. },
  43. },
  44. },
  45. //启动服务配置
  46. server: {
  47. host: '0.0.0.0',
  48. port: 8000,
  49. open: true, // 自动在浏览器打开
  50. proxy: {},
  51. },
  52. // 打包配置
  53. build: {
  54. //浏览器兼容性 "esnext"|"modules"
  55. target: 'modules',
  56. //指定输出路径
  57. outDir: 'build',
  58. //生成静态资源的存放路径
  59. assetsDir: 'assets',
  60. //启用/禁用 CSS 代码拆分
  61. cssCodeSplit: true,
  62. sourcemap: false,
  63. assetsInlineLimit: 10240,
  64. // 打包环境移除console.log, debugger
  65. minify: 'terser',
  66. terserOptions: {
  67. compress: {
  68. drop_console: true,
  69. drop_debugger: true,
  70. },
  71. },
  72. rollupOptions: {
  73. input: {
  74. main: resolve('index.html'),
  75. },
  76. output: {
  77. entryFileNames: `js/[name]-[hash].js`,
  78. chunkFileNames: `js/[name]-[hash].js`,
  79. assetFileNames: `[ext]/[name]-[hash].[ext]`,
  80. },
  81. },
  82. },
  83. })

三、项目目录结构划分

  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 - 代码

  1. html,
  2. body,
  3. div,
  4. span,
  5. applet,
  6. object,
  7. iframe,
  8. h1,
  9. h2,
  10. h3,
  11. h4,
  12. h5,
  13. h6,
  14. p,
  15. blockquote,
  16. pre,
  17. a,
  18. abbr,
  19. acronym,
  20. address,
  21. big,
  22. cite,
  23. code,
  24. del,
  25. dfn,
  26. em,
  27. font,
  28. img,
  29. ins,
  30. kbd,
  31. q,
  32. s,
  33. samp,
  34. small,
  35. strike,
  36. strong,
  37. sub,
  38. sup,
  39. tt,
  40. var,
  41. b,
  42. u,
  43. i,
  44. center,
  45. dl,
  46. dt,
  47. dd,
  48. ol,
  49. ul,
  50. li,
  51. fieldset,
  52. form,
  53. label,
  54. legend,
  55. caption {
  56. margin: 0;
  57. padding: 0;
  58. border: 0;
  59. outline: 0;
  60. font-size: 100%;
  61. vertical-align: baseline;
  62. background: transparent;
  63. }
  64. table,
  65. tbody,
  66. tfoot,
  67. thead,
  68. tr,
  69. th,
  70. td {
  71. margin: 0;
  72. padding: 0;
  73. outline: 0;
  74. font-size: 100%;
  75. vertical-align: baseline;
  76. background: transparent;
  77. }
  78. button,
  79. input,
  80. textarea {
  81. margin: 0;
  82. padding: 0;
  83. }
  84. /* form elements 表单元素 */body,
  85. button,
  86. input,
  87. select,
  88. textarea {
  89. font: normal 12px/1.5'\5FAE\8F6F\96C5\9ED1', tahoma, arial;
  90. }
  91. /*设置的字体,行高*/h1,
  92. h2,
  93. h3,
  94. h4,
  95. h5,
  96. h6,
  97. th {
  98. font-size: 100%;
  99. font-weight: normal;
  100. }
  101. /*重置标题*/address,
  102. cite,
  103. dfn,
  104. var {
  105. font-style: normal;
  106. }
  107. /* 将斜体扶正 */code,
  108. kbd,
  109. pre,
  110. samp {
  111. font-family: 'courier new', courier, monospace;
  112. }
  113. /* 统一等宽字体 */
  114. small {
  115. font-size: 12px;
  116. }
  117. /* 小于 12px 的中文很难阅读,让 small 正常化 */ul,
  118. ol {
  119. list-style: none;
  120. }
  121. /* 重置列表元素 */button,
  122. input[type="submit"],
  123. input[type="button"] {
  124. cursor: pointer;
  125. }
  126. input[type="radio"],
  127. input[type="checkbox"],
  128. input[type="submit"],
  129. input[type="reset"] {
  130. vertical-align: middle;
  131. cursor: pointer;
  132. border: none;
  133. }
  134. /** 重置文本格式元素 **/a {
  135. text-decoration: none;
  136. }
  137. a:hover {
  138. text-decoration: underline;
  139. }
  140. a:focus {
  141. outline: 0;
  142. }
  143. sup {
  144. vertical-align: text-top;
  145. }
  146. /* 重置,减少对行高的影响 */
  147. sub {
  148. vertical-align: text-bottom;
  149. }
  150. /** 重置表单元素 **/legend {
  151. color: #000;
  152. }
  153. /* for ie6 */fieldset,
  154. img {
  155. border: 0;
  156. }
  157. /* img 搭车:让链接里的 img 无边框 */button,
  158. input,
  159. select,
  160. textarea {
  161. background: transparent;
  162. font-size: 100%;
  163. outline: 0;
  164. }
  165. /* 使得表单元素在 ie 下能继承字体大小 *//* 注:optgroup 无法扶正 */table {
  166. border-collapse: collapse;
  167. border-spacing: 0;
  168. }
  169. td,
  170. th {
  171. vertical-align: middle;
  172. }
  173. /** 重置表格元素 **//* 重置 HTML5 元素 */article,
  174. aside,
  175. details,
  176. figcaption,
  177. figure,
  178. footer,
  179. header,
  180. hgroup,
  181. menu,
  182. nav,
  183. section,
  184. summary,
  185. time,
  186. mark,
  187. audio,
  188. video {
  189. display: block;
  190. margin: 0;
  191. padding: 0;
  192. }
  193. /*回复标签重置*/blockquote,
  194. q {
  195. quotes: none;
  196. }
  197. blockquote:before,
  198. blockquote:after,
  199. q:before,
  200. q:after {
  201. content: '';
  202. display: none;
  203. }

02 - 引入

// 在 main.js 中引入import'./assets/css/reset.css';
3. common.css

01 - 代码

  1. // 清除浮动.clearfix {
  2. *zoom: 1;
  3. }
  4. ......

02 - 引入

// 在 main.js 中引入import'./assets/css/common.css';

五、vue-router 路由配置

一步创建需要安装依赖、配置路由, 引入mian.ts, 配置创建则已自动生成
1. 安装
npm i vue-router
2. 配置
  1. // 1. 导入
  2. import { createRouter, createWebHashHistory } from'vue-router';
  3. // 2. 创建路由对象constrouter = createRouter({
  4. history: createWebHashHistory(),
  5. routes: [
  6. {
  7. path: '/',
  8. redirect: '/home'
  9. },
  10. {
  11. path: '/home',
  12. component: () => import('xxx/home.vue')
  13. }
  14. ]
  15. });
  16. // 3. 导出
  17. export default router;
3. 引入
  1. // main.jsimport { createApp } from'vue';
  2. importAppfrom'./App.vue';
  3. // 1. 导入import router from'./router';
  4. import'normalize.css';
  5. import'./assets/css/reset.css';
  6. import'./assets/css/common.css';
  7. // 2. 使用createApp(App).use(router).mount('#app');
4. 使用
在该用的地方加上

六、pinia 状态管理

一步创建需要安装依赖、配置路由, 引入mian.ts, 配置创建则已自动生成
1. 安装
 npm i pinia
2. 引入
  1. // main.jsimport { createApp } from'vue';
  2. import { createPinia } from"pinia";
  3. importAppfrom'./App.vue';
  4. // 1. 导入import router from'./router';
  5. import'normalize.css';
  6. import'./assets/css/reset.css';
  7. import'./assets/css/common.css';
  8. // 2. 使用createApp(App).use(createPinia()).use(router).mount('#app');
3. 模块
  1. // 1. 导入import { defineStore } from'pinia';
  2. // 2. 使用const useDemoStore = defineStore('demoStore', {
  3. state: () => ({
  4. arrList: []
  5. }),
  6. actions: {},
  7. getters: {}
  8. });
  9. // 3. 导出exportdefault useDemoStore;

七、集成 Axios HTTP 工具

安装依赖
npm i axios
请求配置

在 utils 目录下创建 request.ts 文件,配置好适合自己业务的请求拦截和响应拦截

  1. import axios, { AxiosRequestConfig, Method } from 'axios';
  2. // 创建请求实例
  3. const instance = axios.create({
  4. baseURL: '/api',
  5. // 指定请求超时的毫秒数
  6. timeout: 10000,
  7. // 表示跨域请求时是否需要使用凭证
  8. withCredentials: false,
  9. });
  10. // 设置请求头
  11. instance.defaults.headers.post['Content-Type'] = 'application/json;charset=UTF-8';
  12. instance.defaults.headers.put['Content-Type'] = 'application/x-www-form-urlencoded';
  13. // instance.defaults.headers.put['Content-Type'] = 'application/json';
  14. // 取消重复请求
  15. const pending = [];
  16. // 定义接口
  17. interface PendingType {
  18. url?: string;
  19. method?: Method;
  20. params: any;
  21. data: any;
  22. cancel: any;
  23. }
  24. // 移除重复请求
  25. const removePending = (config: AxiosRequestConfig) => {
  26. for (const key in pending) {
  27. const item: number = +key;
  28. const list: PendingType = pending[key];
  29. // 当前请求在数组中存在时执行函数体
  30. 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)) {
  31. // 执行取消操作
  32. list.cancel('操作太频繁,请稍后再试');
  33. // 从数组中移除记录
  34. pending.splice(item, 1);
  35. }
  36. }
  37. };
  38. // 请求拦截器(发起请求之前的拦截)
  39. instance.interceptors.request.use(
  40. (config): AxiosRequestConfig<any> => {
  41. removePending(config);
  42. config.cancelToken = new axios.CancelToken(c => {
  43. pending.push({ url: config.url, method: config.method, params: config.params, data: config.data, cancel: c });
  44. });
  45. /**
  46. * 在这里一般会携带前台的参数发送给后台,比如下面这段代码:
  47. * const token = getToken()
  48. * if (token) {
  49. * config.headers.token = token
  50. * }
  51. */
  52. return config;
  53. },
  54. (error) => {
  55. return Promise.reject(error);
  56. },
  57. );
  58. // 响应拦截器(获取到响应时的拦截)
  59. instance.interceptors.response.use(
  60. (response) => {
  61. removePending(response.config);
  62. /**
  63. * 根据你的项目实际情况来对 response 和 error 做处理
  64. * 这里对 response 和 error 不做任何处理,直接返回
  65. */
  66. return response;
  67. },
  68. (error) => {
  69. return Promise.reject(error);
  70. },
  71. );
  72. interface ResType<T> {
  73. code: number;
  74. data?: T;
  75. msg?: string;
  76. message?: string;
  77. err?: string;
  78. }
  79. interface Http {
  80. post<T>(url: string, data?: unknown, params?: unknown,): Promise<ResType<T>>;
  81. get<T>(url: string, params?: unknown): Promise<ResType<T>>;
  82. put<T>(url: string, data?: unknown, params?: any): Promise<ResType<T>>;
  83. _delete<T>(url: string, params?: unknown): Promise<ResType<T>>;
  84. }
  85. // 导出常用函数
  86. const http: Http = {
  87. post(url, data, params) {
  88. return new Promise((resolve, reject) => {
  89. instance
  90. .post(url, JSON.stringify(data), params)
  91. .then((res) => {
  92. resolve(res.data);
  93. })
  94. .catch((err) => {
  95. reject(err.data);
  96. });
  97. });
  98. },
  99. get(url, params) {
  100. return new Promise((resolve, reject) => {
  101. axios
  102. .get(url, { params })
  103. .then((res) => {
  104. resolve(res.data);
  105. })
  106. .catch((err) => {
  107. reject(err.data);
  108. });
  109. });
  110. },
  111. put(url, data, params) {
  112. return new Promise((resolve, reject) => {
  113. instance
  114. .put(url, data, params)
  115. .then((res) => {
  116. resolve(res.data);
  117. })
  118. .catch((err) => {
  119. reject(err.data);
  120. });
  121. });
  122. },
  123. _delete(url, params) {
  124. return new Promise((resolve, reject) => {
  125. instance
  126. .delete(url, params)
  127. .then((res) => {
  128. resolve(res.data);
  129. })
  130. .catch((err) => {
  131. reject(err.data);
  132. });
  133. });
  134. },
  135. }
  136. export default http;

之后在 api 文件夹中以业务模型对接口进行拆分,举个例子,将所有跟用户相关接口封装在 User 类中,此类称作用户模型。

在 User 类中比如有登录、注册、获取用户信息等方法,如果有业务逻辑变动,只需要修改相关方法即可。

  1. import { post } from '@/utils/request';
  2. export default class User {
  3. /**
  4. * 登录
  5. * @param {String} username 用户名
  6. * @param {String} password 密码
  7. * @returns
  8. */
  9. static async login(username: string, password: string) {
  10. return post('/login', {
  11. username,
  12. password,
  13. });
  14. }
  15. }

把每个业务模型独立成一个 js 文件,声明一个类通过其属性和方法来实现这个模型相关的数据获取,这样可以大大提升代码的可读性与可维护性。

模拟演示

在需要使用接口的地方,引入对应的业务模型文件,参考如下

  1. <script setup lang="ts">import User from '@/api/user';
  2. import { ref } from'vue'const username = ref('')
  3. const password = ref('')
  4. constlogin = async () => {
  5. const res = await User.login(username.value, password.value);
  6. console.log(res);
  7. },
  8. </script>

八、使用scss, 并定义全局scss变量

首先我们先安装sass和sass-loader:

npm i sass sass-loader -D

然后我们需要在vite.config.ts中配置css预处理器

  1. export defaultdefineConfig({
  2. css: {
  3. preprocessorOptions: {
  4. scss: {
  5. additionalData: '@import "@/assets/styles/global.scss";@import "@/assets/styles/reset.scss";',
  6. },
  7. }
  8. }
  9. })

我们这里默认加载global.scss中的样式,那么我们就需要创建一个这样的文件:

  1. // src/assets/style/global.scss
  2. $primary-color: #5878e2; // 主题色

最后在main.ts中引入即可:

import"./assets/style/global.scss";

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

  1. <script setup lang="ts">
  2. import {GlobalStore} from '@/store'
  3. const global = GlobalStore();
  4. </script>
  5. <template>
  6. <div>{{global.token}}</div>
  7. </template>
  8. <style scoped lang="scss">
  9. div {
  10. color: $primary-color; // 主题色
  11. }
  12. </style>
本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读