uniapp学习
Uniapp
手机上运行的测试服一直弹出热更提示,并且更新完重进还是会有热更提示
运行包的应用版本不对,将mainifest的应用版本名称和版本号改为线上最新版本即可

为什么.gitignore 忽略了 unpackage,SourceTree 还显示该目录的未暂存文件
1. 项目初始化时,你还没写
.gitignore文件,就直接执行了「第一次 git commit」 → 此时 Git 会把项目里所有文件 / 文件夹都默认加入追踪,其中就包含了unpackage,之后你再补写/unpackage/到.gitignore,已经晚了;2. 开发中误操作:曾经手动在 SourceTree 里勾选了
unpackage文件夹,执行了「暂存」或「提交」操作,导致它被 Git 记录为「需要追踪的目录」。
解决办法:
打开项目根目录的终端(vscode 终端 / 系统 cmd 都可以),依次执行下面 2 行命令,无需任何修改,直接复制粘贴(确保在执行命令前没有暂存的文件,小心被一起提交)
# 第1行:核心命令,移除Git对unpackage文件夹的「追踪标记」,本地文件保留不变!
git rm -r --cached unpackage
# 第2行:把「移除追踪」这个操作,提交到Git,让忽略规则永久生效
git commit -m "移除unpackage的git追踪,启用.gitignore忽略规则"
预防方法
1:【项目初始化第一优先级】新建项目后,第一件事就是创建并编写
.gitignore文件,写完后再执行任何git add / git commit操作!这是黄金法则,99% 的忽略失效问题,都是因为「先提交,后写.gitignore」导致的。Git 的逻辑是「先到先得」,先提交的文件会被追踪,后加的规则无效。
2:永远不要把「自动生成的产物目录、依赖目录」提交到 Git 仓库
比如 uni-app 的
unpackage、所有前端项目的node_modules、打包后的dist、缓存文件、日志文件等,这些都是「可重新生成」的,提交了只会添乱,还会让仓库体积变大。3:
.gitignore写好后,一定要「提交到 Git 仓库」
.gitignore本身是一个普通的文本文件,只有把它提交到仓库,团队协作时,其他成员拉取项目后,才能复用你的忽略规则,避免团队其他人也踩同样的坑。
uni-app 项目 .gitignore 标准完整版
# 第三方依赖包
/node_modules
# uni-app 编译构建产物目录
/unpackage/
# 编译缓存文件
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
# 本地配置文件(比如自己的环境变量、隐私配置)
/local_config.js
/.env.local
/.env.development.local
/.env.production.local
# npm 日志文件
npm-debug.log*
yarn-debug.log*
yarn-error.log*
tabBar跳转
uni.navigateTo和uni.redirectTo只能跳转应用内非 tabBar 的页面的路径 ,所以一个页面如果绑定了tabBar,页面上的navigator 的opentype为navigate或redirect,则跳转相应的页面就无效了
v-bind在绑定style
v-bind在绑定style的时候,一直用双引号包裹单引号很麻烦并且不方便扩充,所以可以把属性值当作对象来处理
<view class="box1" :style="`background-color: ${bgcolor}`"></view>
<!-- 效果与上面一样,注意命名格式-要替换成大写字母 -->
<view class="box1" :style="{backgroundColor: bgcolor}"></view>
data() {
return {
bgcolor:"yellow"
}
}
v-bind绑定纯字符串记得带单引号,不然会被当成变量
<view :title="'普通字符串'"></view>
运行到android和ios,制作自定义底座
如果想运行到手机,需要提前创建自定义调试基座

基底打包完成后就可以运行到手机,选择运行到对应基座
nvue与vue的区别
nvue与vue有区别,例如nvue中文字内容必须用text组件,nvue 页面只能使用flex布局,不支持其他布局方式等,详情见页面 | uni-app官网
uniapp真机调试(安卓)
uniapp真机调试(安卓),点击运行--运行到手机或模拟器--显示webview调试控制台
点击运行--运行到手机或模拟器-运行到基座,控制台会出现webview调试模块,点击箭头,就会出现控制台
width超出border
width: 100%;
padding: 10rpx;
// 当width为100%并且有padding的时候,可以看到大小会往右移,
// 这时候加box-sizing可以把padding变为内填充
box-sizing: border-box;
//ps:nvue盒模型的 box-sizing 默认为 border-box,
//即盒子的宽高包含内容、内边距和边框的宽度,不包含外边距的宽度
![]()
computed使用
computed的每一个计算属性都会被缓存起来,只要计算属性所依赖的属性发生变化,计算属性就会重新执行,视图也会更新。下面代码中,计算属性demo,它依赖了text和title这两个属性,只要它们其中一个属性变化,demo就会重新执行。
computed计算属性会被缓存,在下面代码中使用了两次demo,但在控制台只输出了一次 “this.title + this.text”。
<input type="text" v-model="title" />
<text>{{title}}</text>
<view>---------------------</view>
<text>{{demo}}</text>
<text>{{demo}}</text>
...
data(){
return{
title: "sada",
text: "一一一",
}
},
...
computed: {
demo() {
console.log(this.title + this.text)
return this.title + this.text
}
},
自定义事件
1.创建自定义组件以及接收自定义属性
在这个目录中新建组件


组件pubTitle.vue内容
<template>
<view class="pubTitle">
<view class="time">
{{time}}
</view>
<view class="big">
{{title}}
</view>
<view class="small">
{{subtitle}}
</view>
<view class="small">
{{list}}
</view>
<view class="small">
{{user}}
</view>
<view class="line">
</view>
</view>
</template>
<script>
export default {
name: "pubTitle",
//props:类似data(),调用的时候给title赋值即可
//接受自定义属性,不带默认值
// props: ["title", "subtitle"],
// 或者这样可以给属性赋值一个默认值,
//其他页面调用的时候如果没给属性赋值,则会显示默认值
props: {
title: {
type: String,
default: "默认标题"
},
subtitle: {
type: String,
default: "默认副标题"
},
time: {
type: Number,
default: Date.now()
},
list: {
type: Array,
default () {
return [1, 2, 3]
}
},
user: {
type: Object,
default () {
return {
name: "匿名",
gender: "保密"
}
}
}
},
data() {
return {
};
}
}
</script>
<style lang="scss">
.pubTitle {
padding: 60rpx 30rpx;
text-align: center;
background: #95E1D3;
.big {
font-size: 50rpx;
font-weight: 600;
color: #333;
}
.small {
font-size: 28rpx;
color: #888;
}
.line {
display: inline-block;
width: 80rpx;
height: 8rpx;
background: #1AA034;
border-radius: 10rpx;
}
}
</style>
其他页面调用组件只要用组件名标签并且给组件的props中的属性赋值即可
//index.vue
<template>
<view>
//标题文本内容为“首页”
<pubTitle title="首页" subtitle="index page"></pubTitle>
//标题内容为“forTest”
<pubTitle :title="title" subtitle="for page"></pubTitle>
//不传值则组件文本内容是默认标题和默认副标题
<pubTitle></pubTitle>
<pubTitle :time="time" subtitle="for page"></pubTitle>
<pubTitle :time='987' subtitle="for page"></pubTitle>
<pubTitle :list="[4,5,6]" subtitle="for page"></pubTitle>
<pubTitle :list="list" subtitle="for page"></pubTitle>
<pubTitle :user="user" subtitle="for page"></pubTitle>
<pubTitle :user="{user:'李四',sex:'女'}" subtitle="for page"></pubTitle>
</view>
</view>
</template>
...
data(){
return{
title: "forTest",
time: 123,
list: [9, 9, 9],
user: {
name: "张三",
gender: "男",
},
}
}
2.emit子向父传值
<template>
<view>
<!-- 我是子组件myevent -->
{{title}}
<view class="">
<input type="text" placeholder="请输入.." @input="oninput" />
</view>
{{array}}
</view>
</template>
<script>
export default {
name: "myevent",
props: {
title: {
type: String,
default: "111",
},
array: {
type: Object,
default () {
return {
name: "张三",
age: "12",
}
}
}
},
data() {
return {
};
},
methods: {
oninput(e) {
let a = "子组件传入" + e.detail.value
this.array.name = a
//创建自定义事件(用于引用该组件的父组件调用),事件名myenv,后面是返回
this.$emit("myenv", this.array)
}
}
}
</script>
<style lang="scss">
</style>
<!-- 我是父组件 -->
<!-- 使用自定义方法@myenv 类似@click-->
<myevent title="组件间的传值" @myenv="onmyenv"></myevent>
...
methods: {
// 绑定子组件的自定义方法,接收其返回值
onmyenv(e) {
console.log(e);
}
}
3.组件中使用原生方法
<!-- 我是父组件 -->
//在组件中使用@click等原生方法需要带.native,不然会以为是子组件自定义方法,导致无法实现功能
<myevent title="组件间的传值" @myenv="onmyenv" @click.native="onClick()"></myevent>
...
onClick() {
console.log(12)
}
4.sync关键字
当一个子组件改变了一个 prop 的值时,这个变化也会同步到父组件中所绑定。 .sync 它会被扩展为一个自动更新父组件属性的 v-on 监听器。
//父组件
<mypop :state.sync="state"></mypop>
...
data() {
return {
state: false
};
},
//子组件
...
<button @click="onClose()">关闭</button>
...
methods: {
onClose() {
//改变父组件的state值,
// 添加update:父组件就可以用.sync关键字直接获取state的值,
//就不需要在父组件中重新写个方法来接收state的值了
this.$emit("update:state", false)
}
},
props: {
state: {
type: Boolean,
default: false
},
}
for循环中btn点击变色
使用this.isChangecolor[e - 1] = e ,用这种方式给isChangecolor添加元素无效 的原因是
Vue无法检测到直接通过索引设置数组项的变化(如:this.items[index] = newValue)。Vue的响应式系统需要特殊的方法来触发视图更新。
//for循环内的btn,通过判断isChangecolor的index位置是否有值来更改第index个btn的颜色
<button class="btn"
:class="isChangecolor[index-1]===index?'changeBg':''"
@click="goBuy(index)">
¥28.00</button>
//script
data() {
return {
isChangecolor: [],
}
},
goBuy(e) {
// this.isChangecolor[e - 1] = e ,用这种方式给isChangecolor添加元素(无效,改为用$(set))
this.$set(this.isChangecolor, e - 1, e)
console.log(this.isChangecolor, this.isChangecolor[0]);
}
//css
.btn {
width: 150rpx;
height: 50rpx;
font-size: 25rpx;
line-height: 50rpx;
border-radius: 47rpx;
background: linear-gradient(90deg, #FCC334 0%, #FBEF9B 100%);
&.changeBg {
background: #fff;
}
}
web-view
在云服务器上部署了cocos小游戏之后,想要显示在uniapp项目中,并且实现与uniapp的信息传输,就需要web-view组件
cocos部分
下载uni.webview.1.5.6.js
uni-app/dist/uni.webview.1.5.4.js · DCloud/uni-app - AtomGit | GitCode
创建构建模板,找到构建模板文件夹(通常是 build-templates/web-mobile/index.html,如果没有就从 build/web-mobile/index.html 复制一份或者直接修改构建后的文件)

在index.html的body中引入,构建后记得把 uni.webview.1.5.5.js 文件放到构建目录里,和 index.html 同级
<!-- 引入 uni-app 的 SDK -->
<script type="text/javascript" src="./uni.webview.1.5.5.js"></script>
<script>
document.addEventListener('UniAppJSBridgeReady', function() {
console.log('UniApp JS SDK Ready');
// 这里可以通知游戏 SDK 加载完毕,但通常不需要
});
</script>
cocos在游戏结束面板弹出时(init 方法中),向 UniApp 发送消息。
init(score: number, bestScore: number) {
...
// -----------------------------------------------------------
// 将分数发送给 UniApp
// -----------------------------------------------------------
this.sendScoreToUniApp(score, bestScore);
}
/**
* 发送数据给 UniApp
*/
sendScoreToUniApp(score: number, bestScore: number) {
// 检查环境,避免在编辑器或普通浏览器报错
// @ts-ignore: 忽略 ts 对 window.uni 的类型检查
if (window.uni && window.uni.postMessage) {
console.log("Cocos: 向 UniApp 发送分数");
// uni.postMessage 的格式必须是 { data: {} }
// @ts-ignore
window.uni.postMessage({
data: {
action: 'updateScore', // 消息类型标记
currentScore: score,
bestScore: bestScore
}
});
} else {
console.log("Cocos: 当前非 UniApp 环境,无法发送消息");
}
}
Cocos 构建与部署
在 Cocos Creator 中点击 构建 (Build)。
平台选择 Web Mobile。
构建完成后,将构建生成的文件夹(假设叫 web-mobile)重命名为 2048game(名字随意)。
关键步骤: 将整个 2048game 文件夹复制到 UniApp 项目的 /hybrid/html/ 目录下。
UniApp 规定本地 HTML 必须放在 /hybrid/html/ 才能被 <web-view> 加载。
路径结构应为:你的UniApp项目/hybrid/html/2048game/index.html。
或者把2048game部署到云服务器上,这样在h5上也能看到画面,使用本地路径在浏览器打开会报错
uniapp部分
<template>
<view class="container">
<!-- 顶部展示区:显示从Cocos传回来的分数 -->
<view class="info-bar">
<text>UniApp本地最高分: {{ savedBestScore }}</text>
</view>
<!-- 嵌入 Cocos 游戏 -->
<!-- src 指向本地的 index.html -->
<!-- @message 用于监听 Cocos 发来的 uni.postMessage -->
<web-view
class="game-view"
src="/hybrid/html/2048game/index.html"
@message="handleMessage":webview-styles="webviewStyles" :update-title="false"
></web-view>
</view>
</template>
......
data() {
return {
savedBestScore: 0,
webviewStyles: {
width: '430px',
height: '650px',
} // 别忘了这个样式
};
},
......
onLoad() {
// 1. 页面加载时,先从本地缓存读取旧的最高分
const score = uni.getStorageSync('MY_2048_BEST_SCORE');
if (score) {
this.savedBestScore = score;
}
// 如果是 H5 环境,手动监听 window message
// #ifdef H5
window.addEventListener('message', this.handleH5Message);
// #endif
},
//
onUnload() {
// #ifdef H5
window.removeEventListener('message', this.handleH5Message);
// #endif
},
methods: {
/**
* 处理来自 Cocos 的消息
*/
handleMessage(evt) {
console.log('【App端】收到消息:', evt.detail);
// 注意:evt.detail.data 是一个数组!
const dataList = evt.detail.data;
if (dataList && dataList.length > 0) {
const msg = dataList[dataList.length - 1]; // 取最新一条
this.processLogic(msg);
}
// 现在运行到app上会报错,解决办法是把游戏部署到服务器上使用http/https协议
// 本地方案(必须修改代码): 手动注入一段代码,用 XMLHttpRequest (XHR) 来“欺骗”浏览器,替换掉不兼容的 fetch。
},
/**
* 情况2:H5 端通过 window.message 触发
*/
handleH5Message(evt) {
console.log('【H5端】收到消息:', evt.data);
// H5传过来的直接就是数据对象,可能包裹在 data 里
const msg = evt.data && evt.data.data ? evt.data.data : evt.data;
console.log("msg1", msg);
this.processLogic(msg.arg);
},
/**
* 统一处理逻辑
*/
processLogic(msg) {
if (msg && msg.action === 'updateScore') {
console.log("成功提取最高分:", msg.bestScore);
this.savedBestScore = msg.bestScore;
uni.setStorageSync('MY_2048_BEST_SCORE', msg.bestScore);
// 提示一下用户
uni.showToast({
title: '分数已同步',
icon: 'success'
});
}
}
}
注意点:
1.uni.postMessage 在 H5 浏览器环境 下(即用浏览器预览 UniApp)通常是不生效的,或者行为与 App 不一致。强烈建议使用真机调试(连接手机运行基座) 或者使用 App 模拟器。h5环境访问会有跨域问题,想在h5上看可以通过 window.message 触发
2.app的vue中 web-view的属性只能用webview-styles控制,不受class属性的影响,并且webview-styles只有宽高和loading三个属性,并且会自动铺满除去原生导航栏之外的所有区域,并且是最高层级,不好调整样式,
解决办法:app的web-view放在nvue中
.vue 页面里的 <web-view> 是原生图层,不受 CSS 控制。
.nvue 页面(Weex 引擎)里的 <web-view> 是一个普通组件,它完全支持 flex 布局!你可以像排列两个 div 一样排列 info-bar 和 web-view。
3.web-view的src用本地的src如果想用浏览器查看会报错
css
hover-class与position
父元素使用“hover-class”属性修改scale的时候的时候,子元素如果设置了定位(position),可能会导致按压事件区域可能异常,使用时需要注意
一个元素添加不同的class的样式覆盖
<view class=" a b">实际宽度是200px,因为在style中b比a后写,所以b会覆盖掉a</view>
<style>
.a{
width:100px;
}
.b{
width:200px
}
</style>
nvue中view使用了hover属性,导致点击按钮之后按钮位置偏移
一、偏移的核心原因
1. hover 样式修改了盒模型属性
如果 hover 时改变了
padding/margin/width/height等盒模型属性,nvue 原生渲染引擎会实时重排布局 —— 比如 hover 时给按钮加了padding: 10rpx,点击后样式未及时恢复,或原生布局计算延迟,导致按钮位置偏移。2. transform: scale 导致的视觉 / 布局偏移
若 hover 用了
transform: scale()做缩放效果:
nvue 中
transform的锚点默认是元素中心,缩放后可能因父容器的flex/absolute布局,导致元素视觉位置 “错位”;若缩放后未及时重置(比如
touchend事件延迟),会残留缩放状态,表现为位置偏移。3. nvue 对 hover-class 的兼容性问题
nvue 原生渲染对
hover-class的支持不完善,尤其是结合position: absolute/fixed时,hover 触发的样式可能导致原生视图的布局计算误差,出现偏移。4. 事件触发顺序与样式恢复延迟
手动用
@touchstart(加 hover 样式)和@touchend(恢复样式)模拟 hover 时,若touchend触发延迟(比如有事件冒泡 / 阻止默认行为),会导致样式恢复不及时,看起来像位置偏移。
层级调试
在ios的nvue中调试层级的时候碰到了一个问题:同一个父容器,先写子容器的代码z-index=3,后面添加的组件的z-index=9,按道理来说组件的层级要比子容器的层级高,但是实际情况是组件层级在子容器之下
原因:子容器的position是:fixed,组件的position是absolute,
解决方法:组件的position修改为fixed即可显示在子容器上
ai分析:
你前面的
<view>结构所在的父容器设置了position: fixed,而组件<positionFourBtn>是position: absolute。在 nvue 原生渲染逻辑中:
position: fixed的容器及其子元素,原生层级优先级高于同一父容器下的absolute元素(即使组件z-index更高);- 且 DOM 中先出现的
fixed相关元素,会被原生引擎优先渲染为 “更高层级视图”,后出现的absolute组件自然被覆盖
background-image要记得添加属性
使用background-image的时候,开发时能点看图片,说明路径是对的,但运行后没显示,可能是
样式属性缺失导致图片被隐藏,图片尺寸远大于元素尺寸,且未设置 background-size,导致只显示空白,缺少 background-repeat 或 background-position,图片重复或偏移出可视区域
.cItem {
width: 100rpx;
height: 100rpx;
background-image: url("@/static/wenzibj.png");
background-size: 100% 100%; // 让图片铺满元素
background-repeat: no-repeat; // 禁止重复
background-position: center; // 居中显示
}
JavaScript
JavaScript 数组引用赋值问题
要copy数组的时候可以用...展开运算符 ,.slice(),Array.from() 这些浅拷贝或者深拷贝
let a = [1, 2, 3];
let b = a; // 这里不是创建新数组,而是将b指向a的引用
b.push(4); // 通过b修改了a引用的数组
console.log(a); // [1, 2, 3, 4] ← a也被改变了
-----------------------------------------------
let a = [1, 2, 3];
let b = a.slice(); // 方法1:使用slice创建新数组
// 或
let b = [...a]; // 方法2:使用展开运算符
// 或
let b = Array.from(a); // 方法3
b.push(4);
console.log(a); // [1, 2, 3] ← 保持不变
console.log(b); // [1, 2, 3, 4]
---------------------------------------
const a = [[1], [2], [3]];
const b = JSON.parse(JSON.stringify(a)); // 深拷贝
浅拷贝与深拷贝
| 特性 | 浅拷贝 | 深拷贝 |
|---|---|---|
| 复制深度 | 只复制第一层属性/元素 | 递归复制所有嵌套层级 |
| 内存引用 | 嵌套对象/数组保持原引用 | 所有层级都是全新的独立副本 |
| 修改影响 | 修改嵌套内容会影响原对象 | 修改任何内容都不会影响原对象 |
| 性能 | 快(只复制一层) | 慢(需要递归复制所有层级) |
| 内存占用 | 较少(共享嵌套对象) | 较多(完全独立副本) |
原对象:
{
name: "张三",
age: 25,
address: {
city: "北京",
street: "长安街"
}
}
浅拷贝后:
{
name: "张三", ← 新值
age: 25, ← 新值
address: { ← 仍然是原对象的引用!
city: "北京",
street: "长安街"
}
}
深拷贝后:
{
name: "张三", ← 新值
age: 25, ← 新值
address: { ← 全新的对象!
city: "北京",
street: "长安街"
}
}
.map(Number)
let digits = [1, 2, 3]
let str = ""
for (var a = 0; a < digits.length; a++) {
str = str + digits[a]
}
//map() 会对数组的每个元素调用提供的函数,并返回一个新数组。
//Number 是一个内置函数,用于将值转换为数字。
//所以 map(Number) 相当于:
//const numberArray = stringArray.map(item => Number(item));
return (Number(str) + 1).toString().split("").map(Number)
两种防抖函数
//在工具类中添加方法
//第一个防抖函数,通过setTimeout计时器防止玩家频繁点击按钮
let debounceTimeOut = null
function debounceFunc(func, wait = 2500, notShowTip) {
if (debounceTimeOut !== null) {
if (!notShowTip) {
uni.showToast({
title: '请稍候进行操作',
icon: 'none',
duration: 2000
});
}
return
}
if (typeof func === 'function') {
debounceTimeOut = setTimeout(() => {
clearTimeout(debounceTimeOut);
debounceTimeOut = null;
}, wait);
func();
}
}
//第二个防抖函数,根据点击时间差来判断是否点击多次
let oldTime = 0
let newTime = 0
function debounceFunc2(func, wait = 1500, notShowTip) {
newTime = Date.now();
if (newTime - oldTime < wait) {
oldTime = newTime;
if (!notShowTip) {
uni.showToast({
title: '请稍候操作',
icon: 'none',
duration: 2000
});
}
return
}
oldTime = newTime;
if (typeof func === "function") {
func();
}
}
...
//记得导出函数
export {
debounceFunc,
debounceFunc2,
}
v-for的key
最好不要直接用:key =index,建议直接使用:key = ”‘字符串’+item.id(唯一标识符)“防止在冗长的代码中在同层级下出现两个index还不知道在哪找起
异步函数Promise和async和await和.then
console.log("A: 正常排队");
new Promise((resolve) => {
console.log("B: Promise 内部其实是 VIP,立刻执行!");
// 假设这里去发网络请求
resolve(); // 拿到结果了,通知 .then
}).then(() => {
console.log("D: 收到 resolve,我最后才执行");
});
console.log("C: 正常排队");
//实际输出顺序: A -> B -> C -> D。
//Promise 内部的代码立刻执行,但遇到 resolve() 时,它不会立刻执行 .then,而是
//把 .then 里的任务塞进“微任务队列”里,等外面的同步代码(C)全跑完,
//再回头执行 .then(D)。
Promise(承诺机制):
new Promise内部的代码是同步、立即执行的。它的作用是发起一个耗时操作(如网络请求),并承诺在未来通过调用resolve()或reject()来交出结果。在它等待结果的期间,外部的同步代码会继续不受影响地往下跑。.then()(监听与回调): 相当于一个结果接收器。它必须等待 Promise 内部调用
resolve()或reject()之后,才会被推入任务队列,随后再执行.then()里面的代码。async(异步标记与包装): 用于标识一个函数是异步操作。除了作为标记,它在底层还会自动将该函数的最终返回值包装成一个 Promise 对象,方便外部继续对它使用
.then()或await。await(异步串行化神器): 它只能在
async函数体内使用。它的核心作用是只阻塞当前async函数内部的执行——必须死等当前的 Promise 拿到resolve或reject结果后,才会放行执行下一行代码。它的终极意义是:用看起来像同步代码的直观写法,把原本非阻塞、满天飞的异步操作,变成了可控的串行执行。
标准异步请求 (Try-Catch-Finally)
这是最稳健的工业级写法。
const handleRequest = async () => {
loading.value = true; // 1. 开始转圈
try {
// 2. 阻塞等待:等结果回来
const res = await uni.request({ url: '...' });
// 3. 成功后的逻辑
data.value = res.data;
} catch (error) {
// 4. 容错:网络断了或接口崩了
uni.showToast({ title: '加载失败', icon: 'none' });
} finally {
// 5. 闭环:无论成败,都要关掉转圈
loading.value = false;
}
};
性能优化 (Promise.all)
当页面一打开需要调好几个接口时,用这个。
*数组里的每个成员必须是 Promise 对象
onMounted(async () => {
try {
// 三个请求并发,总耗时 = 最慢的那一个
const [list, user, time] = await Promise.all([
getListApi(),
getUserApi(),
getSystemTimeApi()
]);
// 结构赋值,一次性拿到所有数据
giftList.value = list;
userInfo.value = user;
serverTime.value = time;
} catch (err) {
console.error("初始化页面失败", err);
}
});
Promise.all的工作逻辑是:
检查数组里的每一个元素。
如果是 Promise,就等它状态变为
resolved。如果数组里混进了一个不是 Promise 的东西(比如一个普通函数或数字),
Promise.all会认为它已经“瞬间完成了”,不会产生任何等待效果。
没有 await 的 async 是虚假的:如果你不写 await,代码会一溜烟跑完,不会等异步结果。
await只挡住函数内部:async函数内部在死等,但它不会阻塞外面的同辈方法执行(保证了 UI 不卡死)。
Promise.all是一票否决制:数组里只要有一个请求 Reject(失败),整个Promise.all就会直接跳进catch。
new Promise 的本质:一份“延期交付”的合同
你可以把它想象成你和 JavaScript 签的一份合同:“我现在开始干一件费时间的事,干完了我拨动 resolve 开关,干砸了我拨动 reject 开关。”
const myTask = new Promise((resolve, reject) => {
// 这里写“费时间”的代码(比如定时器、网络请求、读文件)
const success = true;
if (success) {
resolve("想要传给后面的数据"); // 👈 拨动“成功”开关
} else {
reject("报错信息"); // 👈 拨动“失败”开关
}
});
必须要拨动开关:如果你在
new Promise内部既没写resolve()也没写reject(),那么外面的await就会永久死等下去,直到浏览器崩溃。只能改一次状态:一旦执行了
resolve,这个 Promise 的状态就锁死了。你后面再写reject也是没用的。立刻执行:当你
new Promise的那一刻,内部的代码是立即开始运行的,它不会等你调用await才开始。
使用场景:
① 包装“老旧”的回调函数(Promisify)
② 模拟延迟 / 强制等待
③ 预加载资源(如图片)
vue3+ts
pinia
引入uniapp
# 如果使用 npm
npm install pinia
# 如果使用 yarn
yarn add pinia
第二步:在 main.js (或 main.ts) 中挂载
import { createSSRApp } from 'vue'
import * as Pinia from 'pinia' // 1. 导入 Pinia
import App from './App.vue'
export function createApp() {
const app = createSSRApp(App)
// 2. 创建 Pinia 实例并挂载
app.use(Pinia.createPinia())
return {
app,
Pinia // uniapp 环境下建议导出一份,以便某些插件使用
}
}
第三步:定义你的 Store
在项目根目录创建一个 store 文件夹,然后新建一个文件(例如 counter.js):
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
// 你可以使用 'Setup' 风格,这和 Composition API 一模一样
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
return { count, doubleCount, increment }
})
第四步:在页面中使用
<template>
<view>
<text>当前计数:{{ counter.count }}</text>
<text>双倍计数:{{ counter.doubleCount }}</text>
<button @click="counter.increment">点我加1</button>
</view>
</template>
<script setup>
import { useCounterStore } from '@/store/counter.js'
const counter = useCounterStore()
</script>
总结
Pinia 并不是为了“替代”接口请求,而是作为 API 数据的暂存区
作用: 解决跨页面、跨组件的数据共享。
心法: 服务器是“真理源头”,Pinia 是“本地投影”。页面只管看 Pinia,Pinia 负责跟服务器同步。
注意事项
响应式解构 (
storeToRefs): 直接解构const { money } = store会丢失响应性。
必须使用
storeToRefs(store)才能让页面随着数据的变化而自动刷新。核心思维: 模块化。让专门的 Store 管专门的事,互相之间通过 Action 通信。
Bug
报错
报错了不要只看web 可以运行小程序看看,page.json的错会在小程序编译的时候告诉位置,web不会

list.push()
...
list1: [1, 2],
...
// 错误:以为l1是新数组,实际上l1是数字(数组长度)
let l1 = this.list1.push(1);
// 数组引用赋值问题,实际上l2和list1指向的是同一个数组
let l2 = this.list1
// this.list1也会改变
l2.push(1)
// 使用展开运算符copy数组,再push就不会改变list1了
let l3 = [...this.list1]
l3.push(1)
props 应该是单向向下传递的
报错:Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "state"
这个错误是 Vue 的警告信息,表明您正在直接修改一个 prop 的值,这违反了 Vue 的单向数据流原则。
-
state是作为 prop 从父组件传递下来的 -
子组件中直接修改了
this.state -
这违反了 Vue 的设计原则:props 应该是单向向下传递的
//父组件
<button @click.native="onClick()">开启</button>
<mypop :state="state"></mypop>
...
methods: {
// 绑定子组件的自定义方法,接收其返回值
onmyenv(e) {
console.log(e);
},
onClick() {
this.state = true
}
},
//子组件
methods: {
click() {
this.state = false // 直接修改了prop,导致报错
}
},
props: {
state: { // 从父组件接收的prop
type: Boolean,
default: false
}
}
大数精度丢失
大数精度丢失是指 JavaScript 在处理非常大或非常小的数字时,无法精确表示这些数字,导致计算结果不准确的现象。
JavaScript 使用 IEEE 754 双精度浮点数标准来表示数字,这意味着:
-
最大安全整数:
Number.MAX_SAFE_INTEGER = 9007199254740991(2⁵³ - 1) -
最小安全整数:
Number.MIN_SAFE_INTEGER = -9007199254740991
超过这个范围的整数会出现精度问题。
let digits = [6,1,4,5,3,9,0,1,9,5,1,8,6,7,0,5,5,4,3];
let str = digits.join(''); // "6145390195186705543"
// 这个数字超过了 MAX_SAFE_INTEGER (9007199254740991)
console.log(Number(str)); // 6145390195186705000 ← 精度丢失!
// 最后三位 543 变成了 500
解决方法
使用 BigInt(ES2020)
// 在数字后面加 n 表示 BigInt
const bigNum1 = 9007199254740992n;
const bigNum2 = 9007199254740993n;
console.log(bigNum1 + bigNum2); // 18014398509481985n ← 精确!
//字符转bigint并且做加法
let str="6145390195186705543"
BigInt(str) + 1n //6145390195186705544n
服务器
云服务器安装宝塔
购买完服务器可以先重置密码,后启动实例的远程连接
搜索宝塔面板,复制宝塔linux安装脚本
if [ -f /usr/bin/curl ];then curl -sSO https://download.bt.cn/install/install_panel.sh;else wget -O install_panel.sh https://download.bt.cn/install/install_panel.sh;fi;bash install_panel.sh ssl251104
按y同意后会开始安装,等待5分钟左右会出现宝塔外网ipv4面板地址,内网面板地址,账号,密码(要记住),并且提示请安全组放行“宝塔”端口
在安全组的入方向添加对应的宝塔端口,重启实例,再复制外网地址到浏览器上即可看到宝塔面板

把cocos文件放入云服务器
在php项目中添加一个站点,文件放在宝塔的对应目录下,云服务器和宝塔都放行对应的端口号,重启云服务器后浏览器输入http://外网地址:端口号即可运行cocos游戏





更多推荐

所有评论(0)