<!-- file: /pages/index/index.vue -->
<template>
<view class="container">
<!-- 1. 自定义导航栏组件使用,title是这里透传给子组件的变量,@counterOver是子组件暴露出来的函数事件,ref可以作为选择器,当父组件通过ref选择到子组件时,可以调用子组件里通过defineExpose暴露出的方法 -->
<navbar :title="pageTitle" @navBarAttached="onNavBarAttached" @counterOver="onCounterOver" ref="nav-bar"/>
<scroll-view scroll-y class="content">
<!-- 2-1. 两种事件传参方式,绑定到data属性传参,移动端推荐将@click事件都替换为@tap,因为click会有300ms的延迟 -->
<view class="card" @tap="showShareModal" :data-id="currentItem.id">
<text>点我分享 (使用data属性传参)</text>
</view>
<!-- 2-2. 两种事件传参方式,大家常用的直接传参 -->
<view class="card" @tap="showShareModal(currentItem.id)">
<text>点我分享 (直接传参)</text>
</view>
<!-- 3. 组件市场安装的组件使用 -->
<!--通过组件市场安装的组件和普通组件引用方式差不多,只是选择器可能不一样,比如这个弹窗组件的选择器就是ref,通过ref绑定事件对象-->
<uni-popup ref="QRCodePopup">
</uni-popup>
<!-- 4. 页面跳转示例 -->
<view class="card" @tap="navigateToSearch">
<text>跳转到搜索页面</text>
</view>
<!-- 5. Tab切换示例 -->
<view class="card" @tap="switchToOrderTab">
<text>切换到订单Tab页</text>
</view>
</scroll-view>
</view>
</template>
<script setup>
import { ref, reactive, onLoad, watch } from 'vue';
// 1. 响应式数据定义
const pageTitle = ref('首页');
const currentItem = reactive({ id: 123, name: '测试项目' });
// 2. 获取应用实例和组件引用
const app = getApp();
const QRCodePopup = ref(); //uni_modules插件市场组件引用
const nav-bar = ref(''); //子组件引用
// 3. 页面生命周期 - 加载
onLoad((params) => {
console.log('页面加载,参数:', params);
// 使用全局方法示例,这里是对uni.request的一个封装
app.globalData.utils.request({
url: '/app/user_info',
success: (res) => {
const { token } = res.data;//这里是vue3的结构语法,可以从data中提取对应的元素
//存储登录数据到storage,获取时把set改成get就行
uni.setStorageSync('token', token);
}
});
// 根据URL参数设置通用组件的页面标题
if (params.act === 'search') {
uni.setNavigationBarTitle({ title: '搜索页' });
} else {
uni.setNavigationBarTitle({ title: '列表页' });
}
});
// 4. 常用弹窗提醒演示
const showShareModal = (id) => {
console.log('分享项目ID:', id || currentItem.id);
uni.showToast({
title: `分享项目 ${id || currentItem.id}`,
icon: 'none'
});
};
//5 对子组件暴露出来的事件做处理
const onCounterOver = (e) => {
//这里可以接收子组件在事件上的传值
console.log(e.detail.value)
//5.1这里可以调用子组件里通过defineExpose暴露出的方法
nav-bar.value.resetCount();//重置当前组件的计数
};
// 6. 组件市场下载的插件里会有对应的使用方法,按照文档使用即可
const showQRCodePopup = () => {
QRCodePopup.value.open();
};
// 7. 常用的页面导航
const navigateToSearch = () => {
uni.navigateTo({
url: '/pages/search/index'
});
};
//8. 常用的tab切换
const switchToOrderTab = () => {
uni.switchTab({
url: '/pages/order/index'
});
};
// 9. 监听数据变化示例,watch函数可以监听响应式变量的变化,发生变化时可以在里面触发自定义事件
watch(() => order.payStatus, (newValue, oldValue) => {
console.log(`支付状态从 ${oldValue} 变为 ${newValue}`);
});
// 10. 全局事件监听,当业务需要跨页面流转时,比如跳转到另一个页面去付款,可以在另一个页面暴露事件,跳转回来时事件会被成功监听。
uni.$on('orderStatusChange', (data) => {
console.log('收到订单状态变更:', data);
order.payStatus = data.payStatus;
});
</script>
<!-- file: /components/demo-component.vue -->
<template>
<view class="demo-component">
<!-- 1. 组件属性接收 -->
<text class="title">{{ title }}</text>
<!-- 2. 组件事件发射 -->
<button @tap="handleButtonClick">点击我</button>
<!-- 3. 使用插槽 -->
<slot name="content"></slot>
<!-- 4. 条件渲染 -->
<view v-if="isVisible" class="message">
这是条件渲染的内容
</view>
<!-- 5. 列表渲染 -->
<view v-for="(item, index) in items" :key="item.id" class="list-item">
<text>{{ index + 1 }}. {{ item.name }}</text>
</view>
<!-- 6. 样式绑定 -->
<view :class="['status', statusClass]" :style="customStyle">
状态: {{ statusText }}
</view>
</view>
</template>
<script setup>
import { ref, reactive, computed, watch, onMounted, defineProps, defineEmits, toRaw } from 'vue';
// 1. 定义组件属性
const props = defineProps({
title: {
type: String,
default: '默认标题'
}
});
// 2. 定义组件事件
const emit = defineEmits(['counterOver']);
// 3. 响应式数据
const isVisible = ref(true);
const items = reactive([
{ id: 1, name: '项目一' },
{ id: 2, name: '项目二' },
{ id: 3, name: '项目三' }
]);
// 4.1 计算属性,使用计算属性可以动态处理响应式数据,比如常用的格式化和一些单位换算
const statusText = computed(() => {
switch (props.status) {
case 'normal': return '正常';
case 'warning': return '警告';
case 'error': return '错误';
default: return '未知';
}
});
//4.2这样可以从父组件给子组件传递class属性,方便基于类的样式管理
const statusClass = computed(() => `status-${props.status}`);
//4.3也可以直接传style行内样式
const customStyle = computed(() => ({
color: props.status === 'error' ? 'red' : 'green',
fontWeight: 'bold'
}));
// 5. 调用暴露到父组件的方法并传参
const handleButtonClick = () => {
emit('counterOver', {
detail: {
value: count.value
}
});
};
// 6. 监听属性变化
watch(count, (newValue, oldValue) => {
console.log(`计数从 ${oldValue} 变为 ${newValue}`);
});
// 7. 组件挂载生命周期
onMounted(() => {
console.log('组件挂载完成');
});
onBeforeMount(() => {
console.log('组件挂载前');
});
// 8. 暴露方法给父组件,父组件可直接调用
defineExpose({
resetCount: () => { count.value = 0; },
});
</script>
#################坑##############################
## UniApp/Vue 开发实用小技巧
1. **侦听器使用**:`watch()` 用于监听数据变化并做出处理。每当被监听的数据变更时会自动触发更新。
2. **模板引用选择DOM**:
```vue
<input ref='input'/>
```
```javascript
const input = ref(null);
onMounted(() => {
input.value.focus();
});
```
3. **Tab菜单切换**:可以使用 `:is` 动态组件在多个组件之间进行切换。
4. **ES6对象解构** - 在 Vue 和 React 中极其常见:
```javascript
const person = { name: 'Alice', age: 30 };
const { name, age } = person; // name = 'Alice', age = 30
// 函数参数解构
function greet({ name, age }) {
console.log(`Hello, ${name}. You are ${age}.`);
}
```
5. **默认参数**:`function (a, b = 10) {...}`
6. **剩余/扩展运算符**:`...` (用于函数参数或数组/对象操作)
7. **增强的对象字面量**:可以直接在对象中写变量和函数
8. **v-slot简写**:`v-slot` 可以简写为 `#`
9. **EasyCom组件免导入**:符合 easycom 语法的组件可以免导入直接使用。
10. **array.reduce 使用**:对数组内容逐个处理,通过回调函数接收累加器和当前值,累加器会在每次累加后更新。
11. **微信胶囊按钮**:微信右上角的胶囊块称为胶囊按钮。
12. **Flex布局技巧**:`style flex=1` 可以让元素自动填充可用空间。
13. **uni.request 数据封装**:`uni.request` 会在外层多包一层 data,如果封装请求,记得返回响应对象时加上 `.data`。
14. **插件安装后重启**:安装 UniApp 插件后需要重启模拟器,否则插件可能加载不成功。
15. **npm 包安装前提**:如果项目需要通过 npm 安装包,需要先在根目录新建 `package.json` 并输入 `{}` 默认值。
16. **动态属性绑定**:Vue 中如果有动态属性,属性名前面记得加 `:`,如 `:class`、`:data` 等。
17. **v-if 和 v-for 避免共用**:`v-if` 和 `v-for` 不要放在同一个元素上,会影响性能。
18. **样式作用域**:使用 `scoped` 属性可以限制样式只作用于当前组件。
19. **响应式数据更新**:直接修改数组或对象时,需要使用特定方法或重新赋值来触发视图更新。
20. **生命周期钩子**:合理使用 `onMounted`、`onUpdated`、`onUnmounted` 等生命周期钩子函数。