阶段四:UI优化与项目发布
1. 教学目标
- 掌握高级UI交互:学习使用
uni-popup实现弹窗,并通过props和emit实现父子组件间的交互。 - 提升用户体验(UX):掌握在
<scroll-view>中实现“下拉刷新”功能,为用户提供主动更新数据的能力。 - 理解资源优化:(可选) 了解如何封装一个
SvgIcon组件,以更灵活地管理和使用矢量图标。 - 掌握项目交付:学习配置
manifest.json,执行打包命令,并了解将项目上传到微信开发者工具进行发布的完整流程。 - 培养产品意识:理解代码功能完成不等于项目结束,UI/UX的打磨和最终的发布是交付高质量产品的必要环节。
2. 优化与发布流程图 (Mermaid)
graph TD
subgraph "UI 优化"
A[用户点击生活指数] --> B[LifeIndex.vue 发出 show-detail 事件];
B --> C[index.vue 监听到事件
更新 visible 和 currentIndex 状态]; C --> D[IndexDetailPopup.vue
根据 props 弹出显示详情]; end subgraph "UX 优化" E[用户在首页下拉] --> F[scroll-view 触发 refresherrefresh 事件]; F --> G[index.vue 调用 onRefresh 方法]; G --> H[设置 refreshing=true, 重新请求数据]; H --> I[数据返回后设置 refreshing=false
刷新动画收起]; end subgraph "打包发布" J[配置 manifest.json
填入AppID] --> K[执行打包命令
npm run build:mp-weixin]; K --> L[在微信开发者工具中
导入 dist/build/mp-weixin 目录]; L --> M[预览、测试后点击上传]; end
更新 visible 和 currentIndex 状态]; C --> D[IndexDetailPopup.vue
根据 props 弹出显示详情]; end subgraph "UX 优化" E[用户在首页下拉] --> F[scroll-view 触发 refresherrefresh 事件]; F --> G[index.vue 调用 onRefresh 方法]; G --> H[设置 refreshing=true, 重新请求数据]; H --> I[数据返回后设置 refreshing=false
刷新动画收起]; end subgraph "打包发布" J[配置 manifest.json
填入AppID] --> K[执行打包命令
npm run build:mp-weixin]; K --> L[在微信开发者工具中
导入 dist/build/mp-weixin 目录]; L --> M[预览、测试后点击上传]; end
3. 核心步骤详解
步骤一:实现生活指数详情弹窗
当用户点击首页的某个生活指数时,我们弹出一个浮层来展示更详细的说明。
1. 创建详情弹窗组件 IndexDetailPopup.vue
- 职责: 接收要展示的指数数据和控制其显示的
visible状态,并能通知父组件关闭自己。 - 实现逻辑:
- 内部使用
uni-popup作为弹窗的基础。 - 定义
visible和indexData两个props。 - 使用
watch监听visibleprop 的变化,当它变为true时调用popup.open(),变为false时调用popup.close()。 - 当用户点击弹窗内的“关闭”按钮时,通过
emit('close')通知父组件。
- 内部使用
- 知识点:
uni-popup的使用,watch监听props变化,emit自定义事件。
点击展开/折叠 IndexDetailPopup.vue 源代码
<template>
<uni-popup ref="popup" type="center" :is-mask-click="false">
<view class="popup-content">
<text class="popup-title">{{ indexData.name }}</text>
<view class="popup-body">
<uni-icons :type="indexData.icon" size="64" :color="indexData.color"></uni-icons>
<text class="popup-value">{{ indexData.value }}</text>
<text class="popup-desc">{{ indexData.desc }}</text>
</view>
<button @click="onClose">关闭</button>
</view>
</uni-popup>
</template>
<script setup>
import { defineProps, defineEmits, ref, watch } from 'vue';
const props = defineProps({
visible: { type: Boolean, default: false },
indexData: { type: Object, default: () => ({}) }
});
const emit = defineEmits(['close']);
const popup = ref(null);
// 监听父组件传来的 visible 状态,控制弹窗的打开和关闭
watch(() => props.visible, (newVal) => {
if (!popup.value) return;
if (newVal) {
popup.value.open();
} else {
popup.value.close();
}
});
// 当用户点击关闭按钮时,通知父组件
const onClose = () => {
emit('close');
};
</script>
2. 在 index.vue 中使用弹窗
- 实现逻辑:
- 在
<script setup>中定义detailVisible和currentIndex两个ref变量,用于控制弹窗的显隐和内容。 - 定义
showIndexDetail和closePopup两个方法,分别用于打开和关闭弹窗。 - 在模板中,监听
LifeIndex组件派发的@show-detail事件,并调用showIndexDetail方法。 - 将
detailVisible和currentIndex作为props传递给IndexDetailPopup组件,并监听其@close事件。
- 在
点击展开/折叠 index.vue 与弹窗交互的相关代码
<template>
<!-- ...其他组件... -->
<!-- 生活指数,监听子组件的 show-detail 事件 -->
<LifeIndex :index-data="lifeIndices" @show-detail="showIndexDetail"></LifeIndex>
<!-- 指数详情弹窗,传递 visible 状态并监听 close 事件 -->
<IndexDetailPopup :visible="detailVisible" :index-data="currentIndex" @close="closePopup"></IndexDetailPopup>
</template>
<script setup>
import { ref } from 'vue';
// ... 其他导入 ...
// 控制弹窗显隐的状态
const detailVisible = ref(false);
// 存储当前要展示的指数详情
const currentIndex = ref({});
// 显示指数详情(由 LifeIndex 组件触发)
const showIndexDetail = (index) => {
currentIndex.value = index;
detailVisible.value = true;
};
// 关闭弹窗(由 IndexDetailPopup 组件触发)
const closePopup = () => {
detailVisible.value = false;
};
</script>
步骤二:实现下拉刷新功能
本项目未使用页面级的下拉刷新,而是在 index.vue 内部使用了 <scroll-view> 组件提供的下拉刷新,这种方式更灵活。
- 实现逻辑:
- 在
index.vue的模板中,将页面主体内容包裹在<scroll-view>中。 - 为
<scroll-view>添加关键属性:refresher-enabled="true": 开启下拉刷新。@refresherrefresh="onRefresh": 绑定下拉被触发的事件。:refresher-triggered="refreshing": 动态绑定一个ref变量,用于手动控制刷新动画的显示与隐藏。
- 在
<script setup>中,定义refreshing = ref(false)。 - 创建
onRefresh方法。当用户下拉时,该方法被调用:- 将
refreshing.value设为true,显示刷新动画。 - 调用
initData()或updateWeather()重新获取数据。 - 在数据获取完成后(
await之后),将refreshing.value设为false,收起刷新动画。
- 将
- 在
- 知识点:
<scroll-view>组件的下拉刷新功能。
点击展开/折叠 index.vue 下拉刷新相关代码
<template>
<view class="weather-container">
<scroll-view
scroll-y
refresher-enabled="true"
@refresherrefresh="onRefresh"
:refresher-triggered="refreshing"
style="height: 100vh;"
>
<!-- 所有页面内容... -->
</scroll-view>
</view>
</template>
<script setup>
import { ref } from 'vue';
// ...
// 控制刷新动画的状态
const refreshing = ref(false);
const initData = async () => { /* ... */ };
// 下拉刷新事件处理
const onRefresh = async () => {
// 1. 显示刷新动画
refreshing.value = true;
// 2. 重新获取所有数据
await initData();
// 3. 数据获取完毕,隐藏刷新动画
refreshing.value = false;
};
</script>
步骤三:自定义SVG图标组件 (SvgIcon.vue) (可选)
- 职责: 提供一个统一的、可复用的 SVG 图标组件,方便地控制图标的名称、大小和颜色。
- 实现逻辑: 这个组件本质上是一个
<image>标签。它接收name,size,color三个props,然后根据name动态地拼接出src/static/SvgIcon/目录下的对应.svg文件的路径。 - 知识点:
props的高级用法,动态src绑定。
点击展开/折叠 SvgIcon.vue 源代码
<template>
<image
class="weather-icon"
:src="`/src/static/SvgIcon/${name}.svg`"
mode="widthFix"
:style="{ width: size + 'px', height: size + 'px', color: color }"
/>
</template>
<script setup>
import { defineProps } from 'vue'
defineProps({
name: { type: String, required: true },
size: { type: [Number, String], default: 24 },
color: { type: String, default: '' }
})
</script>
步骤四:项目打包与发布
这是将我们的劳动成果交付给用户的最后一步。
- 配置
manifest.json:- 打开项目根目录的
manifest.json文件。 - 在左侧菜单中选择 “微信小程序配置”。
- 将从微信公众平台申请到的 微信小程序AppID 填入对应的输入框中。这是小程序身份的唯一标识,必须填写。
- (可选)在此处还可以配置小程序名称、图标等。
- 打开项目根目录的
- 执行打包命令:
- 打开
package.json文件,可以看到scripts中定义了各种平台的打包命令。 - 我们需要的是
build:mp-weixin。 - 打开终端(HBuilderX内置终端或系统终端),在项目根目录下执行:
npm run build:mp-weixin- 等待命令执行完毕。成功后,会在项目根目录生成一个
dist文件夹,其中dist/build/mp-weixin就是我们打包好的小程序代码。
- 打开
- 在微信开发者工具中发布:
- 打开你的 微信开发者工具。
- 点击 “导入”,项目目录选择上一步生成的
dist/build/mp-weixin文件夹。 - AppID 会自动填入,确认无误后点击导入。
- 在开发者工具中,可以进行最后的预览和调试。
- 确认无误后,点击右上角的 “上传” 按钮,填写版本号和项目备注。
- 上传成功后,登录 微信公众平台,在 “版本管理” 中可以看到你刚上传的版本。你可以将其设为“体验版”供内部测试,或直接“提交审核”。审核通过后,即可发布上线。
4. 课程总结与拓展任务
本次课程我们从零开始,完整地经历了一个真实项目的全部生命周期。我们不仅学习了 uni-app 和 Vue3 的具体技术,更重要的是,我们建立了一套结构化、组件化、服务化的工程思想。这套思想可以被应用到未来的任何项目中。
拓展任务:
- 任务: 目前应用在首次加载数据时,页面会短暂地显示默认值或空白。请实现一个“骨架屏(Skeleton Screen)”加载效果来优化这个过程。在
index.vue中增加一个isLoading状态,在initData开始时设为true,结束后设为false。当isLoading为true时,用一些灰色的占位块来模拟CurrentEnvironment等组件的轮廓,为用户提供一个更平滑的加载预期。 - 目的: 学习和实践“骨架屏”这一高级UX优化技巧,让应用感觉上“更快”。
教师提示: 最后一阶段,重点在于培养学生的“产品完结”意识。一个项目不仅要功能跑通,还要好用、好看、稳定。让学生理解,代码的终点是服务于最终用户。发布流程是理论知识,能让学生对项目开发的“最后一公里”有一个完整的认知。