一、项目介绍
本项目是一个适合新手练手的轻量型 uni-app 工具类小程序,集成三大核心功能:
- 天气查询:调用和风天气 API 实现实时天气、未来预报查询
- 备忘录:基于 uni-app 本地存储实现文本记录的增删改查
- 待办清单:通过本地存储管理待办事项,支持状态标记与删除
通过本项目,新手可掌握:
- uni-app 基础语法与项目结构
- 网络 API 调用与数据处理
- 本地存储(
uni.setStorageSync/uni.getStorageSync)的使用 - 多页面路由与组件化开发
- 小程序 UI 布局与交互逻辑
二、准备工作
1. 开发环境搭建
2. 项目初始化
(1)打开 HBuilderX,点击「文件 > 新建 > 项目」
(2)选择「uni-app」,模板选「默认模板」,输入项目名称(如 tool-mini-program)
(3)项目结构说明:
tool-mini-program/
├─ pages/ # 页面目录(天气、备忘录、待办清单页面)
├─ static/ # 静态资源(图片等)
├─ common/ # 公共工具类(API 封装、工具函数)
├─ App.vue # 应用入口
├─ main.js # 入口文件
├─ pages.json # 页面路由与配置
└─ manifest.json # 项目配置(小程序 AppID 等)
三、核心功能实现
模块 1:天气查询(调用和风天气 API)
1.1 配置 API 密钥
在 common/config.js 中存储 API 配置:
// common/config.js
export default {
weatherKey: '你的和风天气KEY', // 替换为自己的 KEY
baseUrl: 'https://devapi.qweather.com/v7' // 和风天气 API 基础地址
}
1.2 封装网络请求工具
创建 common/request.js 封装 uni-app 网络请求:
// common/request.js
import config from './config'
export default function request(url, method = 'GET', data = {}) {
return new Promise((resolve, reject) => {
uni.request({
url: config.baseUrl + url,
method,
data: { ...data, key: config.weatherKey }, // 自动携带 KEY
success: (res) => {
if (res.data.code === '200') {
resolve(res.data)
} else {
reject('请求失败:' + res.data.msg)
}
},
fail: (err) => {
reject('网络错误:' + err.errMsg)
}
})
})
}
1.3 天气页面开发(pages/weather/weather.vue)
页面结构(template):
<template>
<view class="weather-container">
<!-- 加载中提示 -->
<view v-if="loading" class="loading">加载中...</view>
<!-- 天气信息展示 -->
<view v-else class="weather-info">
<view class="city">{{ city }}</view>
<view class="temp">{{ now.temp }}°C</view>
<view class="cond">{{ now.text }}</view>
<view class="detail">
<text>湿度:{{ now.humidity }}%</text>
<text>风速:{{ now.windDir }} {{ now.windSpeed }}km/h</text>
</view>
<!-- 未来预报 -->
<view class="forecast">
<view class="forecast-item" v-for="(item, index) in forecast" :key="index">
<text>{{ item.fxDate.slice(5) }}</text>
<text>{{ item.textDay }}</text>
<text>{{ item.tempMin }}°~{{ item.tempMax }}°</text>
</view>
</view>
</view>
</view>
</template>
逻辑处理(script):
<script>
import request from '../../common/request'
export default {
data() {
return {
loading: true,
city: '',
now: {}, // 实时天气
forecast: [] // 未来预报
}
},
onLoad() {
this.getLocation() // 先获取定位
},
methods: {
// 获取用户定位(经纬度)
getLocation() {
uni.getLocation({
type: 'gcj02',
success: (res) => {
this.getWeather(res.longitude, res.latitude)
this.getCity(res.longitude, res.latitude)
},
fail: () => {
uni.showToast({ title: '请开启定位权限', icon: 'none' })
this.loading = false
}
})
},
// 获取实时天气
async getWeather(lng, lat) {
try {
const nowRes = await request(`/weather/now?location=${lng},${lat}`)
this.now = nowRes.now
const forecastRes = await request(`/weather/3d?location=${lng},${lat}`)
this.forecast = forecastRes.daily
this.loading = false
} catch (err) {
uni.showToast({ title: err, icon: 'none' })
this.loading = false
}
},
// 获取城市名称
async getCity(lng, lat) {
const res = await request(`/geo/reverse?location=${lng},${lat}`)
this.city = res.address.city
}
}
}
</script>
样式(style):
<style scoped>
.weather-container {
padding: 20rpx;
}
.loading {
text-align: center;
padding: 50rpx;
}
.city {
font-size: 36rpx;
text-align: center;
margin: 30rpx 0;
}
.temp {
font-size: 80rpx;
text-align: center;
margin: 50rpx 0;
}
.cond {
font-size: 32rpx;
text-align: center;
color: #666;
}
.detail {
display: flex;
justify-content: space-around;
margin: 40rpx 0;
color: #666;
}
.forecast {
margin-top: 50rpx;
}
.forecast-item {
display: flex;
justify-content: space-between;
padding: 20rpx 0;
border-bottom: 1px solid #eee;
}
</style>
模块 2:备忘录(本地存储)
2.1 备忘录数据结构设计
每条备忘录包含:
{
id: '唯一标识(时间戳)',
title: '标题',
content: '内容',
createTime: '创建时间'
}
2.2 备忘录列表页(pages/memo/memo.vue)
页面结构:
<template>
<view class="memo-container">
<!-- 添加按钮 -->
<button class="add-btn" @click="toEdit">+ 添加备忘录</button>
<!-- 备忘录列表 -->
<view class="memo-list">
<view
class="memo-item"
v-for="(item, index) in memoList"
:key="item.id"
@click="toEdit(item.id)"
>
<view class="memo-title">{{ item.title || '无标题' }}</view>
<view class="memo-time">{{ item.createTime }}</view>
<button
class="delete-btn"
@click.stop="deleteMemo(item.id)"
>删除</button>
</view>
</view>
</view>
</template>
逻辑处理:
<script>
export default {
data() {
return {
memoList: []
}
},
onShow() {
// 页面显示时读取本地存储
const memos = uni.getStorageSync('memos') || []
this.memoList = memos.reverse() // 倒序显示(最新的在前)
},
methods: {
// 跳转到编辑页(新增/修改)
toEdit(id) {
uni.navigateTo({
url: `/pages/memo-edit/memo-edit?id=${id || ''}`
})
},
// 删除备忘录
deleteMemo(id) {
uni.showModal({
title: '提示',
content: '确定删除吗?',
success: (res) => {
if (res.confirm) {
let memos = uni.getStorageSync('memos') || []
memos = memos.filter(item => item.id !== id)
uni.setStorageSync('memos', memos)
this.memoList = memos.reverse()
}
}
})
}
}
}
</script>
2.3 备忘录编辑页(pages/memo-edit/memo-edit.vue)
<template>
<view class="memo-edit">
<input
type="text"
v-model="title"
placeholder="请输入标题"
class="title-input"
/>
<textarea
v-model="content"
placeholder="请输入内容"
class="content-input"
></textarea>
<button class="save-btn" @click="saveMemo">保存</button>
</view>
</template>
<script>
export default {
data() {
return {
id: '',
title: '',
content: ''
}
},
onLoad(options) {
this.id = options.id // 从路由获取 id(空则为新增)
if (this.id) {
// 编辑已有备忘录:读取数据
const memos = uni.getStorageSync('memos') || []
const memo = memos.find(item => item.id === this.id)
if (memo) {
this.title = memo.title
this.content = memo.content
}
}
},
methods: {
saveMemo() {
if (!this.title && !this.content) {
uni.showToast({ title: '内容不能为空', icon: 'none' })
return
}
const memos = uni.getStorageSync('memos') || []
const now = new Date().toLocaleString() // 当前时间
if (this.id) {
// 修改:替换原数据
const index = memos.findIndex(item => item.id === this.id)
memos[index] = { ...memos[index], title: this.title, content: this.content }
} else {
// 新增:添加新数据
memos.push({
id: Date.now().toString(), // 用时间戳作为唯一 id
title: this.title,
content: this.content,
createTime: now
})
}
// 保存到本地存储
uni.setStorageSync('memos', memos)
uni.navigateBack() // 返回列表页
}
}
}
</script>
模块 3:待办清单(本地存储)
3.1 待办数据结构设计
每条待办包含:
{
id: '唯一标识',
content: '待办内容',
completed: false, // 是否完成
createTime: '创建时间'
}
3.2 待办清单页面(pages/todo/todo.vue)
页面结构:
<template>
<view class="todo-container">
<!-- 添加待办 -->
<view class="add-todo">
<input
v-model="newTodo"
placeholder="请输入待办事项"
@confirm="addTodo"
/>
<button @click="addTodo">添加</button>
</view>
<!-- 待办列表 -->
<view class="todo-list">
<view
class="todo-item"
v-for="(item, index) in todoList"
:key="item.id"
>
<checkbox
:checked="item.completed"
@change="toggleStatus(item.id)"
class="todo-check"
/>
<text :class="{ completed: item.completed }">{{ item.content }}</text>
<button
class="delete-btn"
@click="deleteTodo(item.id)"
>删除</button>
</view>
</view>
</view>
</template>
逻辑处理:
<script>
export default {
data() {
return {
newTodo: '',
todoList: []
}
},
onShow() {
// 读取本地存储的待办
this.todoList = uni.getStorageSync('todos') || []
},
methods: {
// 添加待办
addTodo() {
if (!this.newTodo.trim()) return
const newItem = {
id: Date.now().toString(),
content: this.newTodo.trim(),
completed: false,
createTime: new Date().toLocaleString()
}
this.todoList.unshift(newItem) // 添加到开头
uni.setStorageSync('todos', this.todoList)
this.newTodo = '' // 清空输入框
},
// 切换完成状态
toggleStatus(id) {
const todo = this.todoList.find(item => item.id === id)
if (todo) todo.completed = !todo.completed
uni.setStorageSync('todos', this.todoList)
},
// 删除待办
deleteTodo(id) {
this.todoList = this.todoList.filter(item => item.id !== id)
uni.setStorageSync('todos', this.todoList)
}
}
}
</script>
样式:
<style scoped>
.add-todo {
display: flex;
padding: 20rpx;
gap: 10rpx;
}
.add-todo input {
flex: 1;
border: 1px solid #eee;
padding: 15rpx;
border-radius: 8rpx;
}
.todo-list {
padding: 0 20rpx;
}
.todo-item {
display: flex;
align-items: center;
padding: 20rpx 0;
border-bottom: 1px solid #eee;
gap: 15rpx;
}
.todo-item text {
flex: 1;
}
.completed {
text-decoration: line-through;
color: #999;
}
.delete-btn {
color: #f00;
background: transparent;
}
</style>
四、全局配置(pages.json)
配置底部导航栏(tabBar)实现三个功能页切换:
{
"pages": [
{
"path": "pages/weather/weather",
"style": { "navigationBarTitleText": "天气查询" }
},
{
"path": "pages/memo/memo",
"style": { "navigationBarTitleText": "备忘录" }
},
{
"path": "pages/todo/todo",
"style": { "navigationBarTitleText": "待办清单" }
},
{
"path": "pages/memo-edit/memo-edit",
"style": { "navigationBarTitleText": "编辑备忘录" }
}
],
"tabBar": {
"color": "#666",
"selectedColor": "#007aff",
"list": [
{
"pagePath": "pages/weather/weather",
"text": "天气",
"iconPath": "static/weather.png",
"selectedIconPath": "static/weather-selected.png"
},
{
"pagePath": "pages/memo/memo",
"text": "备忘录",
"iconPath": "static/memo.png",
"selectedIconPath": "static/memo-selected.png"
},
{
"pagePath": "pages/todo/todo",
"text": "待办",
"iconPath": "static/todo.png",
"selectedIconPath": "static/todo-selected.png"
}
]
}
}
五、注意事项
1、和风天气 API 限制:免费版有调用次数限制(每日 1000 次),建议开发时注意控制请求频率
2、本地存储容量:uni-app 本地存储受小程序限制(单 key 不超过 1MB,总容量不超过 10MB),大量数据需考虑云存储
3、定位权限:部分设备可能需要手动开启定位权限,需做好异常处理
4、跨端兼容:若需发布到多端(H5、App),需注意 API 兼容性(如 uni.getLocation 在 H5 端需 HTTPS 环境)
六、扩展建议
1、天气模块:添加空气质量、生活指数(紫外线、洗车指数等)
2、备忘录:支持富文本、图片上传、分类标签
3、待办清单:添加截止时间、优先级、统计功能
4、全局:添加主题切换、数据同步到云端
通过本项目,新手可快速掌握 uni-app 核心开发能力,理解小程序的网络请求、本地存储、页面交互等核心逻辑。建议按模块分步实现,每完成一个功能就进行调试,逐步积累经验~
uni-app小程序实战三合一
1028

被折叠的 条评论
为什么被折叠?



