阶段一:项目基础与组件化
1. 教学目标
- 项目创建:掌握使用 HBuilderX 创建基于 Vite + Vue3 的
uni-app项目。 - 结构认知:理解
uni-app的核心目录结构及pages.json的路由配置功能。 - 组件化思维:学习将一个复杂的页面(首页)自上而下地分解为多个高内聚、低耦合的独立组件。
- 静态实现:能够利用
props传参机制,将静态数据渲染到各个组件中,最终拼装出一个完整的、可视化的静态首页。
2. 阶段流程图 (Mermaid)
此图展示了本阶段从项目创建到最终静态页面完成的完整工作流。
graph TD
A[创建 uni-app 项目] --> B{分析首页UI};
B --> C[规划组件拆分
CurrentEnvironment
HourlyForecast
DailyForecast
LifeIndex]; C --> D[在 `src/components` 目录下
创建对应的 .vue 文件]; subgraph "逐一实现静态组件" D1[编写 CurrentEnvironment.vue] D2[编写 HourlyForecast.vue] D3[编写 DailyForecast.vue] D4[编写 LifeIndex.vue] end D --> D1 & D2 & D3 & D4; subgraph "组装首页" E[在 `pages/index/index.vue` 中
导入所有组件] --> F[定义临时的静态数据] F --> G[在模板中使用组件
并通过 Props 传递数据] end D1 & D2 & D3 & D4 --> E; G --> H[完成首页静态布局]
CurrentEnvironment
HourlyForecast
DailyForecast
LifeIndex]; C --> D[在 `src/components` 目录下
创建对应的 .vue 文件]; subgraph "逐一实现静态组件" D1[编写 CurrentEnvironment.vue] D2[编写 HourlyForecast.vue] D3[编写 DailyForecast.vue] D4[编写 LifeIndex.vue] end D --> D1 & D2 & D3 & D4; subgraph "组装首页" E[在 `pages/index/index.vue` 中
导入所有组件] --> F[定义临时的静态数据] F --> G[在模板中使用组件
并通过 Props 传递数据] end D1 & D2 & D3 & D4 --> E; G --> H[完成首页静态布局]
3. 核心步骤详解
步骤一:项目初始化与目录认知
- 创建项目: 在 HBuilderX 中,通过
文件 -> 新建 -> 项目创建一个新的uni-app项目。- 技术选型: 请务必选择
Vite和Vue3版本,这是目前主流的组合。
- 技术选型: 请务必选择
- 目录概览: 创建后,花几分钟时间向学生介绍核心目录:
src: 我们所有代码的家。pages: 存放所有页面,每个页面一个文件夹。components: 存放可复用的UI组件。static: 存放图片、字体等静态资源。utils: 存放工具函数、API请求等。pages.json: 项目的“导航地图”,配置页面路径、窗口样式、tabBar等。
步骤二:首页组件化拆分与创建
在动手编码前,先进行“设计”。引导学生打开任何一个天气App,会发现其首页都是由多个信息块组成的。我们将我们的首页也拆分成类似的组件:
CurrentEnvironment.vue: 显示当前的核心天气信息和空气质量。HourlyForecast.vue: 横向滚动的24小时天气预报。DailyForecast.vue: 纵向列表的7天天气预报。LifeIndex.vue: 网格布局的生活指数。
接下来,在 src/components/ 目录下,创建以上四个 .vue 文件,并填入从我们项目中提取的源代码。
1. 核心环境组件: CurrentEnvironment.vue
- 功能: 显示温度、天气、风力、湿度以及详细的空气质量指数。
- 知识点:
props定义、uni-card和uni-icons组件的使用、Flexbox布局。
点击展开/折叠 CurrentEnvironment.vue 源代码
<template>
<view class="current-environment">
<!-- 实时天气信息 -->
<uni-card class="current-weather" :shadow="'none'">
<view class="weather-info">
<view class="left-section">
<uni-icons :type="weatherData.icon" size="120" color="#FFD700"></uni-icons>
<text class="weather-desc">{{ weatherData.weather }}</text>
</view>
<view class="right-section">
<text class="temperature">{{ weatherData.temp }}°</text>
<view class="weather-detail">
<view class="detail-item">
<uni-icons type="water" size="20" color="#4A90E2"></uni-icons>
<text class="detail-text">{{ weatherData.humidity }}</text>
</view>
<view class="detail-item">
<uni-icons type="wind" size="20" color="#90A4AE"></uni-icons>
<text class="detail-text">{{ weatherData.wind }}</text>
</view>
<view class="detail-item">
<uni-icons type="location" size="20" color="#FF5722"></uni-icons>
<text class="detail-text">{{ weatherData.visibility }}</text>
</view>
</view>
</view>
</view>
</uni-card>
<!-- 空气质量 -->
<uni-card class="air-quality" :shadow="'none'">
<text class="section-title">空气质量</text>
<view class="air-info">
<view class="air-main">
<text class="aqi-value">{{ airData.aqi }}</text>
<text class="aqi-level">{{ airData.level }}</text>
</view>
<uni-progress :percent="airData.aqi" :show-info="false" :activeColor="airData.color" stroke-width="20"></uni-progress>
<view class="air-detail">
<view class="air-item">
<text class="air-label">PM2.5</text>
<text class="air-data">{{ airData.pm25 }}</text>
</view>
<view class="air-item">
<text class="air-label">PM10</text>
<text class="air-data">{{ airData.pm10 }}</text>
</view>
<view class="air-item">
<text class="air-label">NO2</text>
<text class="air-data">{{ airData.no2 }}</text>
</view>
<view class="air-item">
<text class="air-label">O3</text>
<text class="air-data">{{ airData.o3 }}</text>
</view>
</view>
</view>
</uni-card>
</view>
</template>
<script setup>
import { defineProps } from 'vue';
// 定义组件的props
defineProps({
// 实时天气数据
weatherData: {
type: Object,
default: () => ({
temp: '25',
weather: '晴',
humidity: '45%',
wind: '东北风 3级',
visibility: '10 km',
icon: 'sunny'
})
},
// 空气质量数据
airData: {
type: Object,
default: () => ({
aqi: 75,
level: '良',
pm25: 35,
pm10: 60,
no2: 25,
o3: 80,
color: '#FFFF00'
})
}
});
</script>
2. 24小时预报组件: HourlyForecast.vue
- 功能: 以横向滚动的方式展示未来24小时的天气趋势。
- 知识点:
scroll-view组件的scroll-x属性,v-for循环渲染列表。
点击展开/折叠 HourlyForecast.vue 源代码
<template>
<uni-card class="hourly-forecast" :shadow="'none'">
<text class="section-title">24小时预报</text>
<scroll-view scroll-x class="hourly-scroll">
<view class="hourly-list">
<view class="hourly-item" v-for="(item, index) in hourlyData" :key="index">
<text class="hour-time">{{ item.time }}</text>
<uni-icons :type="item.icon" size="40" color="#90A4AE"></uni-icons>
<text class="hour-temp">{{ item.temp }}°</text>
</view>
</view>
</scroll-view>
</uni-card>
</template>
<script setup>
import { defineProps } from 'vue';
// 定义组件的props
defineProps({
// 24小时预报数据
hourlyData: {
type: Array,
default: () => []
}
});
</script>
3. 7天预报组件: DailyForecast.vue
- 功能: 用列表形式清晰展示未来一周的天气、日期和温度范围。
- 知识点:
uni-list和uni-list-item组件,v-for渲染,computed属性处理默认数据。
点击展开/折叠 DailyForecast.vue 源代码
<template>
<uni-card class="daily-forecast">
<text class="section-title">7天预报</text>
<uni-list>
<uni-list-item v-for="(item, index) in displayData" :key="index" :show-arrow="false">
<template #header>
<view class="daily-left">
<text class="daily-date">{{ item.date }}</text>
<text class="daily-day">{{ item.day }}</text>
</view>
</template>
<template #body>
<view class="daily-right">
<uni-icons :type="item.icon" size="30" color="#90A4AE" class="daily-icon"></uni-icons>
<text class="daily-weather">{{ item.weather }}</text>
<view class="daily-temp">
<text class="temp-max">{{ item.tempMax }}°</text>
<text class="temp-min">{{ item.tempMin }}°</text>
</view>
</view>
</template>
</uni-list-item>
</uni-list>
</uni-card>
</template>
<script setup>
import { defineProps, computed } from 'vue';
const props = defineProps({
dailyData: { type: Array, default: () => [] }
});
const defaultDailyData = [
// ... 默认数据
];
const displayData = computed(() => {
return props.dailyData.length > 0 ? props.dailyData : defaultDailyData;
});
</script>
4. 生活指数组件: LifeIndex.vue
- 功能: 以网格布局展示多个生活指数,并能响应点击事件。
- 知识点:
uni-grid组件,defineEmits用于定义子组件向父组件的事件通信。
点击展开/折叠 LifeIndex.vue 源代码
<template>
<uni-card class="life-index" :shadow="'none'">
<text class="section-title">生活指数</text>
<uni-grid :column="3" :show-border="false" :square="false">
<uni-grid-item v-for="indexItem in indexData" :key="indexItem.type" @click="onShowDetail(indexItem)">
<view class="index-item">
<uni-icons :type="indexItem.icon" size="48" :color="indexItem.color"></uni-icons>
<text class="index-name">{{ indexItem.name }}</text>
<text class="index-value">{{ indexItem.value }}</text>
</view>
</uni-grid-item>
</uni-grid>
</uni-card>
</template>
<script setup>
import { defineProps, defineEmits } from 'vue';
defineProps({
indexData: { type: Array, default: () => [] }
});
const emit = defineEmits(['show-detail']);
const onShowDetail = (indexItem) => {
emit('show-detail', indexItem);
};
</script>
步骤三:在首页 index.vue 中组装静态组件
所有“零件”都已备好,现在我们回到“总装车间”——pages/index/index.vue。
- 功能: 作为页面的容器,负责导入、注册并使用所有子组件,同时为它们提供所需的数据。
- 知识点: 组件的导入 (
import)、注册 (SFC中自动) 和使用,通过:propName="data"的形式传递数据。
点击展开/折叠 pages/index/index.vue 组装后源代码
<template>
<view class="weather-container">
<scroll-view
scroll-y
style="height: 100vh;"
>
<!-- 城市选择栏 (暂时静态) -->
<view class="city-bar">
<text class="city-name">{{ currentCity }}</text>
<uni-icons type="arrow-down" size="20"></uni-icons>
</view>
<!-- 3. 在模板中使用所有组件,并通过 props 传入数据 -->
<CurrentEnvironment :weather-data="currentWeather" :air-data="airQuality"></CurrentEnvironment>
<HourlyForecast :hourly-data="hourlyForecast"></HourlyForecast>
<DailyForecast :daily-data="dailyForecast"></DailyForecast>
<LifeIndex :index-data="lifeIndices" @show-detail="showIndexDetail"></LifeIndex>
<!-- 指数详情弹窗 (暂时隐藏) -->
<IndexDetailPopup :visible="detailVisible" :index-data="currentIndex" @close="closePopup"></IndexDetailPopup>
</scroll-view>
</view>
</template>
<script setup>
import { ref } from 'vue';
// 1. 导入所有需要的UI组件
import CurrentEnvironment from '../../components/CurrentEnvironment.vue';
import HourlyForecast from '../../components/HourlyForecast.vue';
import DailyForecast from '../../components/DailyForecast.vue';
import LifeIndex from '../../components/LifeIndex.vue';
import IndexDetailPopup from '../../components/IndexDetailPopup.vue';
// 2. 定义临时的、用于静态展示的本地数据
// 在真实开发中,这些数据将从API获取
const currentCity = ref('北京市');
const currentWeather = ref({
temp: '25', weather: '晴', humidity: '45%', wind: '东北风 3级', visibility: '10 km', icon: 'sunny'
});
const hourlyForecast = ref([
{ time: '14:00', icon: 'cloudy', temp: '25' }, { time: '15:00', icon: 'cloudy', temp: '24' },
// ...更多数据
]);
const dailyForecast = ref([]); // DailyForecast 组件内部有默认数据
const airQuality = ref({
aqi: 75, level: '良', pm25: 35, pm10: 60, no2: 25, o3: 80, color: '#FFFF00'
});
const lifeIndices = ref([
{ type: '1', name: '穿衣', value: '舒适', icon: 'person', color: '#4A90E2' },
{ type: '2', name: '运动', value: '适宜', icon: 'flag', color: '#F5A623' },
// ...更多数据
]);
// 弹窗相关状态 (暂时用不到)
const currentIndex = ref({});
const detailVisible = ref(false);
const showIndexDetail = (index) => { /* 暂时为空 */ };
const closePopup = () => { /* 暂时为空 */ };
</script>
4. 课后任务 (进阶)
- 任务:
CurrentEnvironment.vue组件目前同时负责“实时天气”和“空气质量”两块内容,显得有些臃肿。请创建一个新的组件AirQuality.vue,将“空气质量”相关的模板、脚本和样式从CurrentEnvironment.vue中完全剥离出去。然后,在index.vue中像使用其他组件一样使用它。 - 目的: 锻炼学生的代码“重构”能力,进一步理解单一职责原则(一个组件只做一件事),并巩固组件拆分与组装的全流程。
教师提示: 此阶段是培养学生工程化思想的关键。务必强调:父组件通过 Props 向子组件传递数据,子组件通过 Events (emit) 向父组件报告事件。这是 Vue (乃至所有现代前端框架) 数据流的核心。鼓励学生在每个组件内部使用默认 props 数据,这样每个组件都可以独立预览和调试,极大地提升开发效率。