黑马程序员 uni-app 小兔鲜儿 项目及bug记录(上)
文档食用指南
- 心很挣扎,是要复制官方文档然后再上面基础上进行修改来写这篇博客还是什么(这样从头到尾只要打开我这一个网页即可)想了想,还是算了,就对官方文档做个补充吧。 📜
- 这篇博客能解决您写小兔线遇到的疑惑和bug,day1,我还记录了些代码,到了day2后,就是专注解决疑惑和bug与介绍项目这样些的好处:比如哑组件模式、Promise性能调优等。
- 一些没有放到官方文档的阶段性繁琐代码(比如写过很多遍的封装请求) 也加入了本文
- 📖本文档与官方文档配合使用 小兔鲜儿小程序 | uniapp+vue3+ts (gitee.io)
- 这个老师课和备案比vue3小兔鲜好太多 🥇,不过还有一些没有总和 比如说 day四最后没有将最终代码放出来 对于已经知道实现的小伙伴不太友好,网上的其他文章也没有放,但我这里有放
- 使用本文档 帮助您快速开发uniapp 😄
- 感觉不错就点赞关注吧 :😍
- 注意 有时候不是你的代码写的有问题 而是项目的后端有问题 看time 在day4里面我就遇到一个接口请求 一直10s 偶尔才返回数据 第二天零点几s就成功拿到数据了
- 估计是项目请求的后端访问量太大了
- 有时候 你会遇到一些莫名奇妙的报错 比如导入模板文件的时候报错了 这时候请重新打开编译器
Day 1
- 本项目建议使用vs开发 建议直接点击目录 通过命令行创建项目
语法
- 在pages.json中如此做,就可以实现
设置
模拟打开的时候,如果是灰的需要修改
使用命令行创建项目
https://blog.csdn.net/qq_42880714/article/details/126509087
使用vscode开发
插件安装
unit-create-view uni-helper uniapp小程序扩展 1、创建unit文件可自动注册路由 2、代码提示 3、鼠标悬停提示
TS配置
npm i -D @types/wechat-miniprogram @uni-helper/uni-app-types 安装类型声明文件
- 如果你没有ts配置文件 那是因为你创建了一个js项目 你需要打开cmd创建一个ts项目
npx degit dcloudio/uni-preset-vue#vite-ts uni-app-Vue3-TS
{ "extends": "@vue/tsconfig/tsconfig.json", "compilerOptions": { "sourceMap": true, "baseUrl": ".", "paths": { "@/*": [ "./src/*" ] }, "lib": [ "esnext", "dom" ], "types": [ "@dcloudio/types", "@types/wechat-miniprogram", "@uni-helper/uni-app-types" ], "ignoreDeprecations": "5.0" //TS废弃了之前的版本 所以使用这个 }, "include": [ "src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue" ] }
-
- uni-app自带的东西
解决js注释问题
- jsonc文件允许注释
- uni-app只允许图中两个写注释
基础架构
拉取小兔线项目
git clone http://git.itcast.cn/heimaqianduan/erabbit-uni-app-vue3-ts.git heima-shop
- 要使用pnpm来运行编译项目
npm install -g pnpm pnpm set registry https://registry.npmmirror.com
使用管理员身份进入cmd 运行以上命令即可
- 项目编译后会多出mp-weixin文件夹 这个才是才是微信小程序的本体 再微信小程序中打开这个而不是整个项目
安装uni-ui 配置easycom
npm i @dcloudio/uni-ui • 1
// pages.json // 组件自动映入规则 "easycom": { // 开始自动扫描 "autoscan": true, // 正则方式匹配 "custom": { // uni-ui 规则如下配置 "^uni-(.*)": "@dcloudio/uni-ui/lib/uni-$1/uni-$1.vue" } },
安装提示
pnpm i -D @uni-helper/uni-ui-types
{ "extends": "@vue/tsconfig/tsconfig.json", "compilerOptions": { "sourceMap": true, "baseUrl": ".", "paths": { "@/*": [ "./src/*" ] }, "lib": [ "esnext", "dom" ], "types": [ "@dcloudio/types", "@types/wechat-miniprogram", "@uni-helper/uni-app-types", "@uni-helper/uni-ui-types" // [!code 添加了ui ++] ], "ignoreDeprecations": "5.0" //TS废弃了之前的版本 所以使用这个 }, "include": [ "src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue" ] }
小程序端pinia持久化
- stores中进行如下配置
import { defineStore } from 'pinia' import { ref } from 'vue' // 定义 Store export const useMemberStore = defineStore( 'member', () => { // 会员信息 const profile = ref<any>() // 保存会员信息,登录时使用 const setProfile = (val: any) => { profile.value = val } // 清理会员信息,退出时使用 const clearProfile = () => { profile.value = undefined } // 记得 return return { profile, setProfile, clearProfile, } }, // TODO: 持久化 { // 配置持久化 网页端只要 persist:true persist: { // 调整为兼容多端的API storage: { getItem(key) { return uni.getStorageSync(key) }, setItem(key, value) { uni.setStorageSync(key, value) }, }, }, }, )
- 如果没有出来可以尝试点击编译 就能刷出来了
如何解决项目中的问题
- 遇到问题直接访问官方文档 然后查看代码作者编写的项目文档即可
文件拦截器
请求和上传文件
// src/utils/http.ts import { useMemberStore } from '@/stores' // 请求基地址 const baseURL = 'https://pcapi-xiaotuxian-front-devtest.itheima.net' // 拦截器配置 const httpInterceptor = { // 拦截前触发 invoke(options: UniApp.RequestOptions) { // 1. 非 http 开头需拼接地址 if (!options.url.startsWith('http')) { options.url = baseURL + options.url } // 2. 请求超时 options.timeout = 10000 // 3. 添加小程序端请求头标识 options.header = { 'source-client': 'miniapp', ...options.header, } // 4. 添加 token 请求头标识 const memberStore = useMemberStore() const token = memberStore.profile?.token if (token) { options.header.Authorization = token } }, } // 拦截 request 请求 uni.addInterceptor('request', httpInterceptor) // 拦截 uploadFile 文件上传 uni.addInterceptor('uploadFile', httpInterceptor)
<script setup lang="ts"> import { useMemberStore } from '@/stores' import '@/utils/http' const memberStore = useMemberStore() // 测试请求 const getDate = () => { uni.request({ method: 'GET', url: '/home/banner', }) } </script> <template> <view class="my"> <view>会员信息:{{ memberStore.profile }}</view> <button @tap=" memberStore.setProfile({ nickname: '黑马先锋', }) " size="mini" plain type="primary" > 保存用户信息 </button> <button @tap="memberStore.clearProfile()" size="mini" plain type="warn">清理用户信息</button> <!-- @tap是uni-app框架的监听 其他是演示什么的 --> <button @tap="getDate" size="mini" plain type="primary">测试请求</button> </view> </template> <style lang="scss"> // </style>
文件拦截器这一节出现的问题
Cannot find module '@/stores'. Did you mean to set the 'moduleResolution' option to 'node', or to add aliases to the 'paths' option? 报错信息
解决方案
{ "extends": "@vue/tsconfig/tsconfig.json", "compilerOptions": { "allowJs": true, "sourceMap": true, "moduleResolution": "node", "baseUrl": ".", "paths": { "@/*": ["./src/*"] }, "lib": ["esnext", "dom"], "types": ["@dcloudio/types", "miniprogram-api-typings", "@uni-helper/uni-app-types", "@uni-helper/uni-ui-types" // uni-ui 组件类型 ] }, "vueCompilerOptions": { // experimentalRuntimeMode 已废弃,现调整为 nativeTags,请升级 Volar 插件至最新版本 "nativeTags": ["block", "component", "template", "slot"] }, "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"] }
import { useMemberStore } from ‘@/stores’ 是在导入什么
Promise请求函数封装
/** * 请求函数 * @param UniApp.RequestOptions * @returns Promise * 1. 返回 Promise 对象,用于处理返回值类型 * 2. 获取数据成功 * 2.1 提取核心数据 res.data * 2.2 添加类型,支持泛型 * 3. 获取数据失败 * 3.1 401错误 -> 清理用户信息,跳转到登录页 * 3.2 其他错误 -> 根据后端错误信息轻提示 * 3.3 网络错误 -> 提示用户换网络 */ type Data<T> = { code: string msg: string result: T } // 2.2 添加类型,支持泛型 export const http = <T>(options: UniApp.RequestOptions) => { // 1. 返回 Promise 对象 一个异步处理函数 可以处于进行中、已成功、已失败三种状态 用于解决回调地域和异步代码复杂性问题 // 其中 resolve是成功调用的函数 另一个是失败调用的函数 return new Promise<Data<T>>((resolve, reject) => { uni.request({ // 使用展开符 具体情况可以看我笔记的下面 ...options, // 响应成功 success(res) { // 状态码 2xx,参考 axios 的设计 if (res.statusCode >= 200 && res.statusCode < 300) { // 2.1 提取核心数据 res.data as是强制转换 resolve(res.data as Data<T>) } else if (res.statusCode === 401) { // 401错误 -> 清理用户信息,跳转到登录页 const memberStore = useMemberStore() memberStore.clearProfile() uni.navigateTo({ url: '/pages/login/login' }) reject(res) } else { // 其他错误 -> 根据后端错误信息轻提示 uni.showToast({ // 指定不显示图标 icon: 'none', // 如果请求数据中有msg则显示 否则标题为请求错误 title: (res.data as Data<T>).msg || '请求错误', }) reject(res) } }, // 响应失败 fail(err) { uni.showToast({ icon: 'none', title: '网络错误,换个网络试试', }) reject(err) }, }) }) }
//my.vue 的测试请i去更新 // 测试请求 const getDate = async () => { const res = await http({ method: 'GET', url: '/home/banner', header: {}, }) console.log('请求成功', res) }
我无法看懂这个函数!
- 大部分东西已经解析的很清晰了 如果你没有看懂 请查看自己是否学过 vue 或者是否做过Vue小兔鲜项目,没有做过也不急 看我的另一篇笔记 Vue 3 黑马程序员小兔鲜开发-CSDN博客
…options
小结
自定义导航栏
<!-- src/pages/index/componets/CustomNavbar.vue --> <script setup lang="ts"> const { safeAreaInsets } = uni.getSystemInfoSync() </script> <template> <view class="navbar" :style="{ paddingTop: safeAreaInsets?.top + 'px' }"> <!-- logo文字 --> <view class="logo"> <image class="logo-image" src="@/static/images/logo.png"></image> <text class="logo-text">新鲜 · 亲民 · 快捷</text> </view> <!-- 搜索条 --> <view class="search"> <text class="icon-search">搜索商品</text> <text class="icon-scan"></text> </view> </view> </template> <style lang="scss"> /* 自定义导航条 */ .navbar { background-image: url(@/static/images/navigator_bg.png); background-size: cover; position: relative; display: flex; flex-direction: column; padding-top: 20px; .logo { display: flex; align-items: center; height: 64rpx; padding-left: 30rpx; padding-top: 20rpx; .logo-image { width: 166rpx; height: 39rpx; } .logo-text { flex: 1; line-height: 28rpx; color: #fff; margin: 2rpx 0 0 20rpx; padding-left: 20rpx; border-left: 1rpx solid #fff; font-size: 26rpx; } } .search { display: flex; align-items: center; justify-content: space-between; padding: 0 10rpx 0 26rpx; height: 64rpx; margin: 16rpx 20rpx; color: #fff; font-size: 28rpx; border-radius: 32rpx; background-color: rgba(255, 255, 255, 0.5); } .icon-search { &::before { margin-right: 10rpx; } } .icon-scan { font-size: 30rpx; padding: 15rpx; } } </style>
// src/pages.json { "path": "pages/index/index", "style": { "navigationStyle": "custom", // 隐藏默认导航 "navigationBarTextStyle": "white", "navigationBarTitleText": "首页" } }
报错 Module ‘“d:/Code/erabbitShop-uni-app-Vue3-TS/src/pages/index/components/CustomNavbar.vue”’ has no default export.
- 因为你装的插件问题 Vetur
- 把这个插件删掉/禁用 使用 vola(不在维护)
没有显示导航栏目
- 查看项目有没有跑起来
Day2
轮播图
- 导入组件没有什么值得讲的,后面的是配置自动导入,项目约定都以Xtx开头
- 使用CTRL + i 实现自动导入
轮播图一图理清
Ts
- 打错字了 应该是对返回数据做了一个预测
为什么要把子组件使用的数据在父组件发请求获取 而不是在子组件中获取?明明父组件不需要这些数据
这是一个常见的设计模式,称为"智能组件和哑组件"模式。
在这种模式中,父组件负责获取数据和处理业务逻辑,而子组件负责接收数据并展示。这种方式有几个优点:
- 数据流的可控性: 父组件可以更好地控制数据的获取和传递,从而使整个应用程序的数据流更加清晰可见。
- 复用性: 子组件可以更加通用,因为它们不需要关心数据的获取方式和业务逻辑,只需要关注数据的展示。
- 单一职责原则: 父组件负责数据获取和处理,符合单一职责原则,使得组件更加容易理解和维护。
另外,这种设计模式也有助于提高代码的可测试性和可维护性,因为业务逻辑更加集中,更容易进行单元测试和重构。
我甚至看不懂vue代码!要怎么办
- 去写个小兔鲜 vue3 项目
- 如果你是个后端开发人员 那么可以找下vue语法网站 花个几小时快速入门 吴悠讲编程的个人空间-吴悠讲编程个人主页-哔哩哔哩视频 (bilibili.com)
- 推荐看看他的视频
首页 前台分类组件
// 修改小程序的颜色 类似html5中为body指定颜色 page { background-color: #f7f7f7; }
获取前台数据
TS类型声明使用及编写
存储理念
- 一个组件是通用的存放在components中,不是则存放在page里的components中
热门推荐
猜你喜欢
滚动实现
获取数据
为什么要在子组件中获取数据而不是父组件中获取数据?
在子组件中获取数据而不在父组件中获取数据通常被称为"自包含组件"或"自管理组件"模式。在这种模式下,子组件负责管理自己的状态和数据获取逻辑,而不依赖于外部组件传递数据。
这种模式的优点包括:
- 组件独立性:子组件独立于父组件,可以更轻松地复用和维护。
- 封装性:子组件封装了自己的数据获取逻辑,使得代码更加清晰和易于理解。
- 可测试性:子组件的行为和状态更容易进行单元测试,因为其逻辑更加集中和独立。
然而,这种模式也存在一些局限性:
- 数据传递复杂性:如果子组件需要从多个父组件获取数据,可能需要在多个地方重复编写数据获取逻辑,增加了复杂性。
- 性能影响:如果同一个数据在多个子组件中重复获取,可能会导致冗余的数据请求,影响性能。
猜你喜欢分页准备
首页下拉刷新
调优
这样就能实现全部一起开始请求
黑马程序员uni-app 小兔鲜儿 项目及bug记录(上)(2):https://developer.aliyun.com/article/1548510