小兔鲜儿 - 项目起步
效果预览
资料说明
📀 视频学习
https://www.bilibili.com/video/BV1Bp4y1379L/
📗 接口文档
https://www.apifox.cn/apidoc/shared-0e6ee326-d646-41bd-9214-29dbf47648fa/
✏️ 在线笔记
https://megasu.gitee.io/uni-app-shop-note/
📦 项目源码
https://gitee.com/Megasu/uniapp-shop-vue3-ts/
项目架构
项目架构图

拉取项目模板代码
项目模板包含:目录结构,项目素材,代码风格。
模板地址
1
| git clone http://git.itcast.cn/heimaqianduan/erabbit-uni-app-vue3-ts.git heima-shop
|
::: tip 注意事项
小程序真机预览需在 manifest.json
中添加微信小程序的 appid
:::
引入 uni-ui 组件库
操作步骤
安装 uni-ui 组件库
配置自动导入组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| { "easycom": { "autoscan": true, "custom": { "^uni-(.*)": "@dcloudio/uni-ui/lib/uni-$1/uni-$1.vue" } }, "pages": [ ] }
|
安装类型声明文件
1
| pnpm i -D @uni-helper/uni-ui-types
|
配置类型声明文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| { "compilerOptions": { "types": [ "@dcloudio/types", "miniprogram-api-typings", "@uni-helper/uni-app-types", "@uni-helper/uni-ui-types" ] }, "vueCompilerOptions": { "nativeTags": ["block", "component", "template", "slot"] } }
|
小程序端 Pinia 持久化
说明:Pinia
用法与 Vue3
项目完全一致,uni-app
项目仅需解决持久化插件兼容性问题。
持久化存储插件
安装持久化存储插件: pinia-plugin-persistedstate
1
| pnpm i pinia-plugin-persistedstate
|
插件默认使用 localStorage
实现持久化,小程序端不兼容,需要替换持久化 API。
基本用法
::: code-group
{28-31} [stores/modules/member.ts]1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| import { defineStore } from "pinia"; import { ref } from "vue";
export const useMemberStore = defineStore( "member", () => { const profile = ref<any>();
const setProfile = (val: any) => { profile.value = val; };
const clearProfile = () => { profile.value = undefined; };
return { profile, setProfile, clearProfile, }; }, { persist: true, } );
|
{2,7} [stores/index.ts]1 2 3 4 5 6 7 8 9 10 11 12 13
| import { createPinia } from "pinia"; import persist from "pinia-plugin-persistedstate";
const pinia = createPinia();
pinia.use(persist);
export default pinia;
export * from "./modules/member";
|
{2,8} [main.ts]1 2 3 4 5 6 7 8 9 10 11 12
| import { createSSRApp } from "vue"; import pinia from "./stores";
import App from "./App.vue"; export function createApp() { const app = createSSRApp(App);
app.use(pinia); return { app, }; }
|
:::
多端兼容
网页端持久化 API
1 2 3
| localStorage.setItem(); localStorage.getItem();
|
多端持久化 API
1 2 3
| uni.setStorageSync(); uni.getStorageSync();
|
参考代码
{7-20}1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| export const useMemberStore = defineStore( "member", () => { }, { persist: { storage: { setItem(key, value) { uni.setStorageSync(key, value); }, getItem(key) { return uni.getStorageSync(key); }, }, }, } );
|
uni.request 请求封装
请求和上传文件拦截器
uniapp 拦截器: uni.addInterceptor
接口说明:接口文档
::: tip 实现需求
- 拼接基础地址
- 设置超时时间
- 添加请求头标识
- 添加 token
:::
参考代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
|
const baseURL = "https://pcapi-xiaotuxian-front-devtest.itheima.net";
const httpInterceptor = { invoke(options: UniApp.RequestOptions) { if (!options.url.startsWith("http")) { options.url = baseURL + options.url; } options.timeout = 10000; options.header = { "source-client": "miniapp", ...options.header, }; const memberStore = useMemberStore(); const token = memberStore.profile?.token; if (token) { options.header.Authorization = token; } }, };
uni.addInterceptor("request", httpInterceptor);
uni.addInterceptor("uploadFile", httpInterceptor);
|
::: warning 注意事项
微信小程序端,需登录 微信公众平台 配置合法域名 👇
https://pcapi-xiaotuxian-front-devtest.itheima.net
:::
封装 Promise 请求函数
::: tip 实现需求
- 返回 Promise 对象,用于处理返回值类型
- 成功 resolve
- 提取数据
- 添加泛型
- 失败 reject
- 401 错误
- 其他错误
- 网络错误
:::
参考代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
|
type Data<T> = { code: string; msg: string; result: T; };
export const http = <T>(options: UniApp.RequestOptions) => { return new Promise<Data<T>>((resolve, reject) => { uni.request({ ...options, success(res) { if (res.statusCode >= 200 && res.statusCode < 300) { resolve(res.data as Data<T>); } else if (res.statusCode === 401) { const memberStore = useMemberStore(); memberStore.clearProfile(); uni.navigateTo({ url: "/pages/login/login" }); reject(res); } else { uni.showToast({ icon: "none", title: (res.data as Data<T>).msg || "请求错误", }); reject(res); } }, fail(err) { uni.showToast({ icon: "none", title: "网络错误,换个网络试试", }); reject(err); }, }); }); };
|
【拓展】代码规范
为什么需要代码规范
如果没有统一代码风格,团队协作不便于查看代码提交时所做的修改。

统一代码风格
1
| pnpm i -D eslint prettier eslint-plugin-vue @vue/eslint-config-prettier @vue/eslint-config-typescript @rushstack/eslint-patch @vue/tsconfig
|
- 新建
.eslintrc.cjs
文件,添加以下 eslint
配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| require("@rushstack/eslint-patch/modern-module-resolution");
module.exports = { root: true, extends: [ "plugin:vue/vue3-essential", "eslint:recommended", "@vue/eslint-config-typescript", "@vue/eslint-config-prettier", ], globals: { uni: true, wx: true, WechatMiniprogram: true, getCurrentPages: true, getApp: true, UniApp: true, UniHelper: true, App: true, Page: true, Component: true, AnyObject: true, }, parserOptions: { ecmaVersion: "latest", }, rules: { "prettier/prettier": [ "warn", { singleQuote: true, semi: false, printWidth: 100, trailingComma: "all", endOfLine: "auto", }, ], "vue/multi-word-component-names": ["off"], "vue/no-setup-props-destructure": ["off"], "vue/no-deprecated-html-element-is": ["off"], "@typescript-eslint/no-unused-vars": ["off"], }, };
|
1 2 3 4 5 6
| { "script": { "lint": "eslint . --ext .vue,.js,.ts --fix --ignore-path .gitignore" } }
|
::: tip 温馨提示
到此,你已完成 eslint
+ prettier
的配置。
:::
Git 工作流规范
::: code-group
[pnpx]
[npx]
:::
1 2 3 4 5 6 7 8
| { "script": { }, "lint-staged": { "*.{vue,ts,js}": ["eslint --fix"] } }
|
1 2
| npm test // [!code --] npm run lint-staged // [!code ++]
|
::: tip 温馨提示
到此,你已完成 husky
+ lint-staged
的配置。
:::