本文链接:https://blog.csdn.net/u014105655/article/details/131851414
视频教程 - 黑马程序员 - 小兔鲜uniapp项目
├─pages 业务页面文件存放的目录
│ └─index
│ └─index.vue index页面
├─static 存放应用引用的本地静态资源的目录(注意:静态资源只能存放于此)
├─unpackage 非工程代码,一般存放运行或发行的编译结果
├─index.html H5端页面
├─main.js Vue初始化入口文件
├─App.vue 配置App全局样式、监听应用生命周期
├─pages.json **配置页面路由、导航栏、tabBar等页面类信息**
├─manifest.json **配置appid**、应用名称、logo、版本等打包信息
└─uni.scss uni-app内置的常用样式变量
npx degit dcloudio/uni-preset-vue#vite-ts my-vue3-ts-project-xxxxxx
或直接gitee下载 https://gitee.com/dcloud/uni-preset-vue/repository/archive/vite-ts.zip
`npx @dcloudio/uvm@latest` 更新,
更新日志详见:https://download1.dcloud.net.cn/hbuilderx/changelog/4.15.2024050802.html
pnpm i --registry=https://registry.npmmirror.com
pnpm dev:mp-weixin
微信开发者工具 左上角--项目--导入项目--项目文件夹--
..... \dist\dev\mp-weixin
// .vscode/extensions.json
{
// 推荐的扩展插件
"recommendations": [
"mrmaoddxxaa.create-uniapp-view", // 创建 uni-app 页面
"uni-helper.uni-helper-vscode", // uni-app 代码提示
"evils.uniapp-vscode", // uni-app 文档
"vue.volar", // vue3 语法支持
"vue.vscode-typescript-vue-plugin", // vue3 ts 插件
"editorconfig.editorconfig", // editorconfig
"dbaeumer.vscode-eslint", // eslint
"esbenp.prettier-vscode" // prettier
]
}
// 微信小程序类型申明文件 原生微信小程序类型 2选1
pnpm i -D @types/wechat-miniprogram // 或 miniprogram-api-typings
// uni-app 类型声明文件
pnpm i -D @uni-helper/uni-app-types
// uni-ui 类型声明文件
pnpm i -D @uni-helper/uni-ui-types
// 不使用 TS 可能会报 uni 找不到的 错
pnpm i @types/uni-app -save-dev
// 找不到模块“vue”或其相应的类型声明 tsconfig.json 中types:['node']
pnpm i --save-dev @types/node
// tsconfig.json
{
"extends": "@vue/tsconfig/tsconfig.json",
"compilerOptions": {
"ignoreDeprecations": "5.0", // typeScript 5.0报错
"sourceMap": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
},
"lib": ["esnext", "dom"],
"types": [
"@dcloudio/types", // uni-app API 类型
"miniprogram-api-typings", // 原生微信小程序类型 2选1
// "@types/wechat-miniprogram", // 原生微信小程序类型 2选1
"@uni-helper/uni-app-types", // uni-app 组件类型
]
},
// vue 编译器类型,校验标签类型
"vueCompilerOptions": {
// "experimentalRuntimeMode": "runtime-uni-app", // 原配置 `experimentalRuntimeMode` 现调整为 `nativeTags`
// vue-official插件版本v2.0.12 太高会不识别 不允许属性nativeTags。其配置无效,组件识别失效不变色
"nativeTags": ["block", "component", "template", "slot"],
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"]
}
vscode 设置 搜 文件关联 添加项 manifest.json 值 jsonc 和 pages.json 值 jsonc
注意:其他文件不允许添加注释
// js版本
jsconfig.json
{
"compilerOptions": {
"target": "es5",
"module": "esnext",
"baseUrl": "./",
"moduleResolution": "node",
"paths": {
"@/*": ["./src/*"]
},
"lib": ["esnext", "dom", "dom.iterable", "scripthost"],
"jsx": "preserve" // template 模版 警告
}
}
// 配置 路径 src-->@ 自动提示路径
pnpm i sass -D
pnpm i sass-loader -D // 可以不安装
// https://uniapp.dcloud.net.cn/component/uniui/quickstart.html#npm%E5%AE%89%E8%A3%85
pnpm i @dcloudio/uni-ui
pnpm i -D @uni-helper/uni-ui-types
// 需要在tsconfig.json types类型中注册 "@uni-helper/uni-ui-types"
// pages.json
{
// 组件自动导入
"easycom": {
// 开启自动扫描
"autoscan": true,
// 以正则方式自定义组件匹配规则
"custom": {
// uni-ui 规则如下配置
"^uni-(.*)": "@dcloudio/uni-ui/lib/uni-$1/uni-$1.vue"
}
},
// 默认配置
"pages": [
// …省略
]
}
// vite.config.ts
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import uni from '@dcloudio/vite-plugin-uni'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [uni()],
resolve: {
extensions: ['.ts', '.js', '.json', '.vue', '.less', '.scss', '.css'], // 省略后缀 以及 index.*
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
},
},
})
# Editor configuration, see http://editorconfig.org
# 表示是最顶层的 EditorConfig 配置文件
root = true
[*] # 表示所有文件适用
charset = utf-8 # 设置文件字符集为 utf-8
indent_style = space # 缩进风格(tab | space)
indent_size = 2 # 缩进大小
end_of_line = lf # 控制换行类型(lf | cr | crlf), lf 适配 mac,linix ; cr 适配window;
trim_trailing_whitespace = true # 去除行首的任意空白字符
insert_final_newline = true # 始终在文件末尾插入一个新行
[*.md] # 表示仅 md 文件适用以下规则
max_line_length = off
trim_trailing_whitespace = false
pnpm i -D eslint prettier eslint-plugin-vue @vue/eslint-config-prettier @vue/eslint-config-typescript @rushstack/eslint-patch @vue/tsconfig
/* eslint-env node */
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,
UniHelper: true,
},
parserOptions: {
ecmaVersion: "latest",
},
rules: {
"prettier/prettier": [
"warn",
{
$schema: 'https://json.schemastore.org/prettierrc',
printWidth: 100,
tabWidth: 2,
useTabs: false,
// 结尾是否添加分号 [0]-关闭 1-warn 2-error
semi: false,
singleQuote: true,
trailingComma: 'all',
bracketSpacing: true,
jsxBracketSameLine: true,
arrowParens: 'avoid',
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"],
},
};
module.exports = {
env: {
browser: true,
es2021: true,
node: true,
jest: true,
},
/* 指定如何解析语法 */
parser: 'vue-eslint-parser',
/** 优先级低于 parse 的语法解析配置 */
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
parser: '@typescript-eslint/parser',
jsxPragma: 'React',
ecmaFeatures: {
jsx: true,
},
},
/* 继承已有的规则 */
extends: [
'eslint:recommended',
'plugin:vue/vue3-essential',
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended',
],
plugins: ['vue', '@typescript-eslint'],
/*
* "off" 或 0 ==> 关闭规则
* "warn" 或 1 ==> 打开的规则作为警告(不影响代码执行)
* "error" 或 2 ==> 规则作为一个错误(代码不能执行,界面报错)
*/
rules: {
// eslint(https://eslint.bootcss.com/docs/rules/)
'no-var': 'error', // 要求使用 let 或 const 而不是 var
'no-multiple-empty-lines': ['warn', { max: 1 }], // 不允许多个空行
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'no-unexpected-multiline': 'error', // 禁止空余的多行
'no-useless-escape': 'off', // 禁止不必要的转义字符
// typeScript (https://typescript-eslint.io/rules)
'@typescript-eslint/no-unused-vars': 'error', // 禁止定义未使用的变量
'@typescript-eslint/prefer-ts-expect-error': 'error', // 禁止使用 @ts-ignore
'@typescript-eslint/no-explicit-any': 'off', // 禁止使用 any 类型
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/no-namespace': 'off', // 禁止使用自定义 TypeScript 模块和命名空间。
'@typescript-eslint/semi': 'off',
// eslint-plugin-vue (https://eslint.vuejs.org/rules/)
'vue/multi-word-component-names': 'off', // 要求组件名称始终为 “-” 链接的单词
'vue/script-setup-uses-vars': 'error', // 防止<script setup>使用的变量<template>被标记为未使用
'vue/no-mutating-props': 'off', // 不允许组件 prop的改变
'vue/attribute-hyphenation': 'off', // 对模板中的自定义组件强制执行属性命名样式
'@typescript-eslint/ban-ts-comment': 'off',
},
}
新建 .eslintignore 忽略校验的文件
dist
node_modules
{
"script": {
// ... 省略 ...
"lint": "eslint . --ext .vue,.js,.ts --fix --ignore-path .gitignore"
}
}
// 运行
pnpm lint
// script 新增 代码检查 和修复指令
"lint": "eslint src",
"fix": "eslint src --fix"
{
"$schema": "https://json.schemastore.org/prettierrc",
"printWidth": 100,
"tabWidth": 2,
"useTabs": false,
// 结尾是否添加分号 0-关闭 1-warn 2-error
"semi": false,
"singleQuote": true,
"trailingComma": "all",
"bracketSpacing": true,
"jsxBracketSameLine": true,
"arrowParens": "avoid",
"endOfLine": "auto"
}
.prettierignore
/dist/*
/html/*
.local
/node_modules/**
**/*.svg
**/*.sh
/public/*
pnpm dlx husky-init
pnpm i lint-staged -D
// package.json
{
"script": {
// ... 省略 ...
"lint-staged": "lint-staged"
},
"lint-staged": {
"*.{vue,ts,js}": ["eslint --fix"]
}
}
修改项目 左上角 .husky/pre-commit 文件
- npm test
+ pnpm lint-staged
注意:其他项目导入的非规范的代码可以使用,在当前文件首行增加下行代码暂停eslint校验
/* eslint-disable */
"files.associations": { "*.json": "jsonc" }
文章知识点与官方知识档案匹配,可进一步学习相关知识
来自: 项目初始化--uniapp--vscode--vue3--ts_vscode vue3 unii-CSDN博客
pnpm i pinia pinia-plugin-persistedstate
1
插件默认使用 localStorage 实现持久化,小程序端不兼容,需要替换持久化 API。
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,
},
)
tsstores/index.ts
import { createPinia } from 'pinia'
import persist from 'pinia-plugin-persistedstate'
// 创建 pinia 实例
const pinia = createPinia()
// 使用持久化存储插件
pinia.use(persist)
// 默认导出,给 main.ts 使用
export default pinia
// 模块统一导出
export * from './modules/member'
main.ts
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
ts
// 网页端API
localStorage.setItem()
localStorage.getItem()
多端持久化 API
ts
// 兼容多端API
uni.setStorageSync()
uni.getStorageSync()
参考代码
ts
// stores/modules/member.ts
export const useMemberStore = defineStore(
'member',
() => {
//…省略
},
{
// 配置持久化
persist: {
// 调整为兼容多端的API
storage: {
setItem(key, value) {
uni.setStorageSync(key, value)
},
getItem(key) {
return uni.getStorageSync(key)
},
},
},
},
)
uniapp 拦截器: uni.addInterceptor
接口说明:接口文档
实现需求
参考代码
ts
// src/utils/http.ts
import { useMemberStore } from '@/stores/modules/member'
// 请求基地址
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)
常见问题
问: 为什么用手机预览没有数据?
答: 微信小程序端,需登录 微信公众平台 配置以下地址为合法域名 👇
https://pcapi-xiaotuxian-front-devtest.itheima.net
实现需求
返回 Promise 对象,用于处理返回值类型
成功 resolve
失败 reject
// src/utils/http.ts 增加以下内容
/**
* 请求函数
* @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 对象
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
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',
title: (res.data as Data<T>).msg || '请求错误',
})
reject(res)
}
},
// 响应失败
fail(err) {
uni.showToast({
icon: 'none',
title: '网络错误,换个网络试试',
})
reject(err)
},
})
})
}
// 测试接口请求
interface IBanner {
hrefUrl: string
id: string
imgUrl: string
type: string
}
const getData = async () => {
// 传递返回数据定义result泛型的具体类型的类型
const res = await http<IBanner[]>({
method: 'GET',
url: '/home/banner',
header: {},
})
console.log(res.result[0].imgUrl)
}
// 存放路径: src/services/home.ts
import type { PageParams, PageResult } from '@/types/global'
// 类型定义文件global.d.ts
import type { BannerItem, CategoryItem, HotItem, GuessItem } from '@/types/home'
import { http } from '@/utils/http'
/**
* 首页-广告区域-小程序
* @param distributionSite 广告区域展示位置(投放位置 投放位置,1为首页,2为分类商品页) 默认是1
*/
export const getHomeBannerAPI = (distributionSite = 1) => {
return http<BannerItem[]>({
method: 'GET',
url: '/home/banner',
data: {
distributionSite,
},
})
}
/**
* 首页-前台分类-小程序
*/
export const getHomeCategoryAPI = () => {
return http<CategoryItem[]>({
method: 'GET',
url: '/home/category/mutli',
})
}
/**
* 首页-热门推荐数据类型
*/
export const getHomeHotAPI = () => {
return http<HotItem[]>({
method: 'GET',
url: '/home/hot/mutli',
})
}
/**
* 猜你喜欢-小程序
*/
export const getHomeGoodsGuessLikeAPI = (data?: PageParams) => {
return http<PageResult<GuessItem>>({
method: 'GET',
url: '/home/goods/guessLike',
data,
})
}
载压缩包 sku插件
导入的非规范的代码可以使用,在当前文件首行增加下行代码暂停eslint校验
/* eslint-disable */
不同手机的安全区域不同,适配安全区域能防止页面重要内容被遮挡。
可通过 uni.getSystemInfoSync() 获取屏幕边界到安全区的距离。
// src/pages.json
{
"path": "pages/index/index",
"style": {
"navigationStyle": "custom", // 隐藏默认导航
"navigationBarTextStyle": "white",
"navigationBarTitleText": "首页"
}
}
<!-- src/pages/index/componets/CustomNavbar.vue -->
<script>
// 获取屏幕边界到安全区域距离
const { safeAreaInsets } = uni.getSystemInfoSync()
</script>
<template>
<!-- 顶部占位 -->
<view class="navbar" :style="{ paddingTop: safeAreaInsets?.top + 'px' }">
<!-- ...省略 -->
</view>
</template>
小程序 底部安全区域css环境变量
env(safe-area-inset-bottom)
export default defineConfig({
server: {
proxy: {
'/front': {
// target: import.meta.env.VITE_API_URL,
target: loadEnv('', process.cwd()).VITE_API_URL, // 获取环境变量 读取env文件 VITE_API_URL的值
changeOrigin: true
// rewrite: (path) => path.replace(/^\/api/, '')
},
'/boss': {
target: loadEnv('', process.cwd()).VITE_API_URL, // 报错 直接输入.env文件读取参数值 import.meta.env.VITE_API_URL
changeOrigin: true
}
}
}
})
如果调用被拦截的话,请检查微信小程序里的项目设置,然后选中 不检验合法域名、web-view(业务域名)、TLS版本以及HTTPS证书
uni.scss 中尾部 添加
@import "@/common/style/base-style.scss"; // 引入自定义变量 文件,注意结尾加分号
App.vue中
<style lang="scss">
/*每个页面公共css */
@import "common/style/common-style.scss";
/* 字体图标 */
@import "@/static/fonts/iconfont.css"
</style>
功能:用于子页面通知父页面更新数据列表
uni.$on(eventName,callback) 定义全局自定义事件 list.vue
uni.$emit(eventName,Object) 触发全局的自定义事件,传递参数 edit.vue
pnpm install -D vite-plugin-mock mockjs
export default defineConfig(({ command }) => {
return {
plugins: [
vue(),
// Mock配置
viteMockServe({
localEnabled: command === 'serve',
}),
file:///Users/Desktop/myapp/node_modules/vite-plugin-mock/dist/index.mjs:128
if (!require.cache) {
^
在node_modules/vite-plugin-mock/dist/index.mjs这个文件中做如下配置:
import { createRequire } from 'module';
const require = createRequire(import.meta.url);
模拟数据
//用户信息数据
function createUserList() {
return [
{
userId: 1,
avatar:
'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
username: 'admin',
password: '111111',
desc: '平台管理员',
roles: ['平台管理员'],
buttons: ['cuser.detail'],
routes: ['home'],
token: 'Admin Token',
},
{
userId: 2,
avatar:
'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
username: 'system',
password: '111111',
desc: '系统管理员',
roles: ['系统管理员'],
buttons: ['cuser.detail', 'cuser.user'],
routes: ['home'],
token: 'System Token',
},
]
}
//对外暴露一个数组,数组里包含两个接口
//登录假的接口
//获取用户信息的假的接口
export default [
// 用户登录接口
{
url: '/api/user/login', //请求地址
method: 'post', //请求方式
response: ({ body }) => {
//获取请求体携带过来的用户名与密码
const { username, password } = body
//调用获取用户信息函数,用于判断是否有此用户
const checkUser = createUserList().find(
(item) => item.username === username && item.password === password,
)
//没有用户返回失败信息
if (!checkUser) {
return { code: 201, data: { message: '账号或者密码不正确' } }
}
//如果有返回成功信息
const { token } = checkUser
return { code: 200, data: { token } }
},
},
// 获取用户信息
{
url: '/api/user/info',
method: 'get',
response: (request) => {
//获取请求头携带token
const token = request.headers.token
//查看用户信息是否包含有次token用户
const checkUser = createUserList().find((item) => item.token === token)
//没有返回失败的信息
if (!checkUser) {
return { code: 201, data: { message: '获取用户信息失败' } }
}
//如果有返回成功信息
return { code: 200, data: { checkUser } }
},
},
]
import Mock from "mockjs"
// 内存模拟数据
const arr = []
for (let i = 0; i < 10; i++) {
arr.push({
id: Mock.mock("@id"),
name: Mock.mock("@cname"),
place: Mock.mock("@county(true)"),
})
}
export default [
{
url: "/list",
method: "get",
response: () => {
return arr
},
},
{
url: "/del/:id",
method: "delete",
response: (req) => {
const index = arr.findIndex((item) => item.id === req.query.id)
if (index > -1) {
arr.splice(index, 1)
return { success: true }
} else {
return { success: false }
}
},
},
{
url: "/edit/:id",
method: "patch",
response: ({ query, body }) => {
const item = arr.find((item) => item.id === query.id)
if (item) {
item.name = body.name
item.place = body.place
return { success: true }
} else {
return { success: false }
}
},
},
]
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。