✅ 超详细拆解+通俗案例+完整代码,零基础可直接复刻
✅ 技术适配:基于微信小程序最新开发规范,覆盖核心基础能力
✅ 条理清晰:从“认知→环境→语法→实战→调试”全流程,无冗余内容
✅ 核心目标:掌握小程序开发全流程,能独立制作带基础交互的完整小程序
一、前置准备:彻底搞懂小程序
1. 小程序的核心特征
- 无需安装:不用像App那样下载安装,微信内搜索/扫码就能打开,比如打开“微信读书”小程序,用完直接退出,不占手机内存;
- 轻量高效:体积限制(主包≤2MB),加载速度比网页快,体验接近原生App;
- 功能闭环:能实现大部分App的核心功能(购物、支付、聊天、打卡等),但不能后台运行;
- 多端兼容:一套代码可适配微信、支付宝、抖音等平台(新手先聚焦微信小程序)。
2. 环境搭建(一步一步教你做)
(1)注册小程序账号(获取AppID)
- 打开「微信公众平台」(https://mp.weixin.qq.com/),点击“立即注册”→选择“小程序”;
- 按提示填写邮箱、验证码、密码,完成账号激活;
- 登录后,在“开发→开发管理→开发设置”中找到「AppID(小程序ID)」,复制保存(开发必需)。
(2)安装微信开发者工具
- 官网下载对应系统版本(https://developers.weixin.qq.com/miniprogram/dev/devtools/download.html);
- 安装完成后打开,选择“小程序→公众号网页→小游戏”分类,点击“微信开发者工具”;
- 首次打开需用微信扫码登录,绑定你的小程序账号。
(3)创建第一个项目
- 登录后点击“+新小程序项目”,弹出配置框:
- 项目名称:填“我的第一个小程序”(自定义);
- 项目目录:点击“选择”,新建一个空文件夹(如桌面的“miniprogram-demo”);
- AppID:粘贴刚才复制的个人AppID(若无,选“测试号”,但测试号部分功能受限);
- 勾选“不使用云服务”(新手先不学云开发);
- 模板选择“基础模板”;
- 点击“创建”,等待工具加载完成,自动生成默认项目结构。
(4)开发者工具界面说明
- 左侧模拟器:实时预览小程序效果,可切换手机型号(如iPhone 15、华为P60);
- 中间编辑器:编写代码的区域,支持语法高亮、自动补全;
- 右侧调试器:调试代码(Console看报错、Network看网络请求、AppData看数据);
- 顶部菜单栏:
- 编译:保存代码并刷新模拟器;
- 预览:生成二维码,用微信扫码看真机效果;
- 上传:将代码提交到微信公众平台(发布用)。
二、核心基础:小程序项目结构与语法(超详细)
1. 完整项目结构解析
默认生成的项目结构如下,用“开奶茶店”的例子类比:
miniprogram-demo/ # 项目根目录
├─ pages/ # 所有页面存放目录(奶茶店的各个包间)
│ ├─ index/ # 首页(大堂)
│ │ ├─ index.js # 首页逻辑(大堂的服务规则)
│ │ ├─ index.json # 首页配置(大堂的装修规则)
│ │ ├─ index.wxml # 首页结构(大堂的桌椅摆放)
│ │ └─ index.wxss # 首页样式(大堂的装修风格)
│ └─ logs/ # 日志页(仓库)
│ ├─ logs.js
│ ├─ logs.json
│ ├─ logs.wxml
│ └─ logs.wxss
├─ app.js # 全局逻辑(奶茶店的整体规则)
├─ app.json # 全局配置(奶茶店的整体布局)
├─ app.wxss # 全局样式(奶茶店的统一装修风格)
└─ project.config.json # 项目配置(开发者工具的设置,不用改)
(1)全局文件
| 文件 | 核心作用(通俗解释) | 关键配置举例 |
|---|---|---|
| app.json | 小程序的“总章程” | 1. pages:声明所有页面路径(必须把首页放第一个); 2. window:设置顶部导航栏样式; 3. tabBar:底部导航栏(如首页/我的) |
| app.wxss | 全局样式(所有页面都能用) | 定义全局字体、颜色,比如 page { background-color: #f5f5f5; }(所有页面背景灰色) |
| app.js | 全局逻辑(小程序启动/销毁触发) | 1. onLaunch:小程序启动时执行(如初始化用户信息); 2. globalData:存储全局数据(如用户ID) |
app.json完整示例:
{
"pages": [
"pages/index/index", // 首页(启动页)
"pages/logs/logs", // 日志页
"pages/detail/detail" // 新增的详情页(后续实战用)
],
"window": {
"navigationBarBackgroundColor": "#ffffff", // 顶部导航栏背景色(白色)
"navigationBarTextStyle": "black", // 导航栏文字颜色(黑色)
"navigationBarTitleText": "我的奶茶店", // 导航栏标题
"backgroundColor": "#f5f5f5", // 页面下拉后的背景色
"enablePullDownRefresh": false // 是否允许下拉刷新(关闭)
},
"tabBar": { // 底部导航栏(可选,比如首页/订单/我的)
"color": "#666666", // 未选中文字颜色
"selectedColor": "#07C160", // 选中文字颜色(微信绿)
"backgroundColor": "#ffffff",// 底部背景色
"list": [
{
"pagePath": "pages/index/index", // 对应页面路径
"text": "首页", // 文字
"iconPath": "images/home.png", // 未选中图标
"selectedIconPath": "images/home-active.png" // 选中图标
},
{
"pagePath": "pages/logs/logs",
"text": "日志",
"iconPath": "images/log.png",
"selectedIconPath": "images/log-active.png"
}
]
},
"sitemapLocation": "sitemap.json" // 搜索引擎收录配置(新手不用改)
}
(2)页面文件
每个页面必须有.wxml(结构)和.js(逻辑),.json(页面配置)和.wxss(页面样式)可选。
页面配置(如index.json)会覆盖app.json的全局配置,比如:
{
"navigationBarTitleText": "奶茶店首页", // 仅首页的导航栏标题改为这个
"enablePullDownRefresh": true // 仅首页允许下拉刷新
}
2. 核心语法
(1)WXML:页面结构(类似HTML,但有专属标签)
WXML是小程序的“结构语言”,负责描述页面有什么,核心规则:
- 标签名更简洁:用
view代替HTML的div,text代替p/span,image代替img; - 属性名用连字符:如
bindtap(绑定点击事件)、wx:for(循环); - 数据绑定用
{{ }}:把JS里的数据显示到页面。
常用WXML标签:
| 标签 | 作用(通俗解释) | 示例代码 |
|---|---|---|
| view | 容器(包裹其他元素) | <view class="box">我是一个容器</view> |
| text | 文字(仅能放文字,可复制) | <text selectable="true">可复制的文字</text>(selectable允许复制) |
| image | 图片(支持本地/网络图片) | <image src="/images/milk-tea.png" mode="widthFix"></image>(widthFix:宽度自适应,高度等比) |
| button | 按钮(支持点击事件) | <button bindtap="buyMilkTea" type="primary">购买奶茶</button>(type=primary:绿色主按钮) |
| input | 输入框(用户输入内容) | <input placeholder="请输入奶茶甜度" bindinput="inputChange"></input> |
| form | 表单(包裹输入框/按钮) | <form bindsubmit="formSubmit"><button form-type="submit">提交</button></form> |
| scroll-view | 滚动视图(内容超出可滚动) | <scroll-view scroll-y="true" style="height: 300rpx;">长列表内容</scroll-view> |
WXML核心语法:数据绑定
<!-- index.wxml -->
<view>
<!-- 1. 绑定普通数据 -->
<text>奶茶名称:{{milkTeaName}}</text>
<!-- 2. 绑定属性(注意:属性也要用{{}}) -->
<image src="{{teaImage}}" mode="widthFix"></image>
<!-- 3. 绑定布尔值(控制显示/隐藏) -->
<view wx:if="{{isHot}}">热销款</view>
<!-- 4. 绑定样式(两种方式) -->
<view style="color: {{fontColor}};">价格:{{price}}元</view>
<view class="{{isDiscount ? 'discount' : 'normal'}}">限时折扣</view>
</view>
// index.js
Page({
data: {
milkTeaName: "珍珠奶茶",
teaImage: "/images/pearl-tea.png",
isHot: true,
fontColor: "#ff0000",
price: 15,
isDiscount: true
}
})
/* index.wxss */
.discount { color: #ff0000; font-weight: bold; }
.normal { color: #666666; }
- 效果:页面显示“奶茶名称:珍珠奶茶”,图片显示珍珠奶茶,有“热销款”标签,价格红色15元,“限时折扣”红色加粗。
WXML核心语法:列表渲染(wx:for)
用于批量展示多条数据(如奶茶列表),必须加wx:key(唯一标识,避免渲染错误):
<!-- index.wxml -->
<view class="tea-list">
<view wx:for="{{teaList}}" wx:key="id" class="tea-item">
<image src="{{item.image}}" mode="widthFix"></image>
<view class="info">
<text class="name">{{item.name}}</text>
<text class="price">¥{{item.price}}</text>
<text wx:if="{{item.stock > 0}}">有货</text>
<text wx:else>售罄</text>
</view>
<button bindtap="addToCart" data-id="{{item.id}}">加入购物车</button>
</view>
</view>
// index.js
Page({
data: {
teaList: [
{ id: 1, name: "珍珠奶茶", price: 15, image: "/images/pearl-tea.png", stock: 100 },
{ id: 2, name: "芋泥奶茶", price: 18, image: "/images/taro-tea.png", stock: 50 },
{ id: 3, name: "抹茶奶茶", price: 20, image: "/images/matcha-tea.png", stock: 0 }
]
},
// 加入购物车事件(获取点击的奶茶ID)
addToCart(e) {
const teaId = e.currentTarget.dataset.id; // 获取data-id的值
wx.showToast({
title: `已加入购物车(ID:${teaId})`,
icon: "success"
})
}
})
/* index.wxss */
.tea-list { padding: 20rpx; }
.tea-item {
display: flex;
align-items: center;
padding: 20rpx;
border-bottom: 1rpx solid #eee;
}
.tea-item image { width: 100rpx; height: 100rpx; margin-right: 20rpx; }
.tea-item .info { flex: 1; }
.tea-item .name { font-size: 32rpx; display: block; margin-bottom: 10rpx; }
.tea-item .price { color: #ff0000; margin-right: 20rpx; }
- 效果:页面显示3款奶茶,每款包含图片、名称、价格、库存状态,点击“加入购物车”弹出对应ID的提示。
WXML核心语法:条件渲染(wx:if/wx:else)
控制元素是否显示,比如根据库存显示“有货/售罄”,上面的例子已包含,补充多条件:
<view wx:if="{{score >= 90}}">爆款</view>
<view wx:elif="{{score >= 80}}">热销</view>
<view wx:elif="{{score >= 70}}">推荐</view>
<view wx:else>普通</view>
(2)WXSS:页面样式(类似CSS,新增特性)
WXSS是小程序的“样式语言”,兼容大部分CSS语法,新增2个核心特性:
- rpx单位:自适应单位,1rpx = 屏幕宽度/750,比如750rpx就是满屏宽度,不用手动计算适配不同手机;
- 例子:
width: 375rpx;在iPhone 15(宽度390px)上是195px,在华为P60(宽度360px)上是180px,自动适配;
- 例子:
- 样式导入:用
@import导入外部样式文件,比如:@import "/styles/common.wxss"; // 导入全局通用样式
WXSS常用技巧:
- 布局用Flex:小程序推荐用Flex布局,比如让元素居中:
.container { display: flex; justify-content: center; /* 水平居中 */ align-items: center; /* 垂直居中 */ height: 100vh; /* 占满屏幕高度 */ } - 样式优先级:页面样式(.wxss)> 全局样式(app.wxss),标签内
style属性优先级最高; - 隐藏元素:
display: none;(不占空间)或visibility: hidden;(占空间)。
(3)JS:页面逻辑(小程序核心)
页面JS的核心是Page()函数,里面包含页面生命周期、数据、事件处理函数,完整示例:
// index.js
Page({
// 1. 页面初始数据(必须用data包裹)
data: {
count: 0,
teaList: []
},
// 2. 页面生命周期函数(自动触发)
// 页面加载时执行(只执行1次)
onLoad(options) {
console.log("页面加载了");
// 初始化数据
this.setData({
teaList: [
{ id: 1, name: "珍珠奶茶", price: 15 }
]
});
// 获取跳转传参(比如从详情页带过来的id)
console.log("跳转传参:", options.id);
},
// 页面显示时执行(每次切回页面都执行)
onShow() {
console.log("页面显示了");
},
// 页面初次渲染完成(可以操作DOM,新手少用)
onReady() {
console.log("页面渲染完成了");
},
// 页面隐藏时执行(跳转到其他页面)
onHide() {
console.log("页面隐藏了");
},
// 页面卸载时执行(返回上一页且无法返回)
onUnload() {
console.log("页面卸载了");
},
// 下拉刷新时执行(需在json中开启enablePullDownRefresh)
onPullDownRefresh() {
console.log("下拉刷新了");
// 刷新完成后停止加载动画
wx.stopPullDownRefresh();
},
// 上拉触底时执行(加载更多)
onReachBottom() {
console.log("上拉触底了");
// 加载更多数据
this.loadMoreData();
},
// 3. 自定义事件处理函数
// 点击按钮增加计数
addCount() {
// 注意:修改data必须用setData,不能直接this.data.count++
this.setData({
count: this.data.count + 1
});
},
// 加载更多数据
loadMoreData() {
const newData = [
{ id: 4, name: "草莓奶茶", price: 22 }
];
// 合并原有数据和新数据
this.setData({
teaList: [...this.data.teaList, ...newData]
});
},
// 跳转页面
goToDetail(e) {
const teaId = e.currentTarget.dataset.id;
// 方式1:保留当前页面,可返回(最多跳10层)
wx.navigateTo({
url: `/pages/detail/detail?id=${teaId}` // 传参:id=1
});
// 方式2:关闭当前页面,不可返回(适合tabBar页面)
// wx.redirectTo({
// url: `/pages/detail/detail?id=${teaId}`
// });
// 方式3:跳转到tabBar页面(只能跳tabBar配置的页面)
// wx.switchTab({
// url: "/pages/index/index"
// });
}
});
JS核心知识点:
- 修改数据:必须用
this.setData({ key: value }),因为小程序的data是响应式的,直接修改不会触发页面更新; - 事件绑定:
- 绑定点击事件:
bindtap="函数名"(冒泡)或catchtap="函数名"(不冒泡); - 传参:通过
data-*属性传参,比如data-id="{{item.id}}",在事件中用e.currentTarget.dataset.id获取;
- 绑定点击事件:
- 小程序API:用
wx.xxx()调用,比如wx.showToast()(提示框)、wx.navigateTo()(跳转)、wx.request()(网络请求),后续实战会详细讲。
三、基础实战:完整制作一个奶茶店小程序
实战目标:制作一个包含“首页(奶茶列表)→详情页(奶茶信息)”的小程序
步骤1:准备素材
在项目根目录新建images文件夹,放入3张图片:
- pearl-tea.png(珍珠奶茶图片)
- taro-tea.png(芋泥奶茶图片)
- matcha-tea.png(抹茶奶茶图片)
步骤2:配置页面路由
在app.json的pages数组中添加详情页:
"pages": [
"pages/index/index",
"pages/logs/logs",
"pages/detail/detail"
]
步骤3:开发首页(index)
index.wxml(结构):
<view class="container">
<!-- 顶部搜索 -->
<view class="search-bar">
<input placeholder="搜索奶茶" class="search-input"></input>
<button type="default" size="mini">搜索</button>
</view>
<!-- 奶茶列表 -->
<view class="tea-list">
<view wx:for="{{teaList}}" wx:key="id" class="tea-item" bindtap="goToDetail" data-id="{{item.id}}">
<image src="{{item.image}}" mode="widthFix" class="tea-img"></image>
<view class="tea-info">
<text class="tea-name">{{item.name}}</text>
<text class="tea-price">¥{{item.price}}</text>
<text class="tea-stock" wx:if="{{item.stock > 0}}">有货</text>
<text class="tea-stock out" wx:else>售罄</text>
</view>
</view>
</view>
</view>
index.wxss(样式):
.container {
padding: 20rpx;
background-color: #f5f5f5;
min-height: 100vh;
}
.search-bar {
display: flex;
align-items: center;
background-color: #fff;
padding: 10rpx;
border-radius: 10rpx;
margin-bottom: 20rpx;
}
.search-input {
flex: 1;
height: 60rpx;
padding: 0 10rpx;
font-size: 28rpx;
}
.tea-list {
background-color: #fff;
border-radius: 10rpx;
padding: 10rpx;
}
.tea-item {
display: flex;
align-items: center;
padding: 20rpx 10rpx;
border-bottom: 1rpx solid #eee;
}
.tea-item:last-child {
border-bottom: none;
}
.tea-img {
width: 120rpx;
height: 120rpx;
border-radius: 10rpx;
margin-right: 20rpx;
}
.tea-info {
flex: 1;
}
.tea-name {
font-size: 32rpx;
display: block;
margin-bottom: 10rpx;
}
.tea-price {
color: #ff4400;
font-size: 30rpx;
margin-right: 20rpx;
}
.tea-stock {
font-size: 26rpx;
color: #07C160;
}
.tea-stock.out {
color: #999;
}
index.js(逻辑):
Page({
data: {
teaList: [
{ id: 1, name: "珍珠奶茶", price: 15, image: "/images/pearl-tea.png", stock: 100 },
{ id: 2, name: "芋泥奶茶", price: 18, image: "/images/taro-tea.png", stock: 50 },
{ id: 3, name: "抹茶奶茶", price: 20, image: "/images/matcha-tea.png", stock: 0 }
]
},
// 跳转到详情页
goToDetail(e) {
const teaId = e.currentTarget.dataset.id;
// 传参跳转
wx.navigateTo({
url: `/pages/detail/detail?id=${teaId}`
});
}
});
步骤4:开发详情页(detail)
detail.wxml(结构):
<view class="detail-container">
<!-- 奶茶图片 -->
<image src="{{teaInfo.image}}" mode="widthFix" class="detail-img"></image>
<!-- 奶茶信息 -->
<view class="detail-info">
<text class="detail-name">{{teaInfo.name}}</text>
<text class="detail-price">¥{{teaInfo.price}}</text>
<text class="detail-desc">{{teaInfo.desc}}</text>
<view class="detail-stock">库存:{{teaInfo.stock}}杯</view>
<button bindtap="addToCart" class="buy-btn" wx:if="{{teaInfo.stock > 0}}">加入购物车</button>
<button class="buy-btn disabled" wx:else disabled>已售罄</button>
<button bindtap="goBack" class="back-btn">返回首页</button>
</view>
</view>
detail.wxss(样式):
.detail-container {
padding: 20rpx;
background-color: #f5f5f5;
min-height: 100vh;
}
.detail-img {
width: 100%;
height: auto;
border-radius: 10rpx;
margin-bottom: 20rpx;
}
.detail-info {
background-color: #fff;
padding: 20rpx;
border-radius: 10rpx;
}
.detail-name {
font-size: 36rpx;
font-weight: bold;
display: block;
margin-bottom: 10rpx;
}
.detail-price {
color: #ff4400;
font-size: 32rpx;
display: block;
margin-bottom: 20rpx;
}
.detail-desc {
font-size: 28rpx;
color: #666;
line-height: 1.5;
margin-bottom: 20rpx;
display: block;
}
.detail-stock {
font-size: 28rpx;
margin-bottom: 30rpx;
}
.buy-btn {
width: 100%;
height: 80rpx;
line-height: 80rpx;
background-color: #07C160;
color: #fff;
border-radius: 10rpx;
font-size: 32rpx;
margin-bottom: 20rpx;
}
.buy-btn.disabled {
background-color: #999;
}
.back-btn {
width: 100%;
height: 80rpx;
line-height: 80rpx;
background-color: #fff;
color: #333;
border: 1rpx solid #eee;
border-radius: 10rpx;
font-size: 32rpx;
}
detail.js(逻辑):
Page({
data: {
teaInfo: {}
},
onLoad(options) {
// 获取首页传过来的id
const teaId = options.id;
// 模拟根据id获取详情数据
const teaList = [
{ id: 1, name: "珍珠奶茶", price: 15, image: "/images/pearl-tea.png", stock: 100, desc: "经典珍珠奶茶,Q弹珍珠,奶香浓郁" },
{ id: 2, name: "芋泥奶茶", price: 18, image: "/images/taro-tea.png", stock: 50, desc: "手工芋泥,绵密香甜,口感丰富" },
{ id: 3, name: "抹茶奶茶", price: 20, image: "/images/matcha-tea.png", stock: 0, desc: "日式抹茶,微苦回甘,茶香醇厚" }
];
// 找到对应奶茶
const teaInfo = teaList.find(item => item.id == teaId);
// 更新数据
this.setData({ teaInfo });
},
// 加入购物车
addToCart() {
wx.showToast({
title: "加入购物车成功",
icon: "success"
});
},
// 返回首页
goBack() {
wx.navigateBack();
}
});
步骤5:测试运行
- 点击开发者工具顶部的“编译”按钮,模拟器会显示首页;
- 点击某款奶茶,跳转到详情页,查看是否传参成功;
- 点击“加入购物车”,弹出提示;
- 点击“返回首页”,回到列表页。
四、核心避坑清单(高频错误)
- ❌ 页面路径写错:跳转路径必须以
/开头(如/pages/detail/detail),且必须在app.json的pages数组中; - ❌ 直接修改data:比如
this.data.count++,页面不会更新,必须用this.setData({ count: this.data.count + 1 }); - ❌ 传参获取错误:事件中传参用
e.currentTarget.dataset.xxx,不是e.target.dataset.xxx(target是触发元素,currentTarget是绑定元素); - ❌ 图片路径错误:本地图片路径必须以
/开头(如/images/pearl-tea.png),不能写相对路径(../images/pearl-tea.png); - ❌ rpx/px混用:优先用rpx,避免用px导致适配问题;
- ❌ 事件名写错:
bindtap写成bindTap(小程序是小写),函数名和绑定的不一致; - ❌ wx:key缺失:列表渲染必须加
wx:key,推荐用数据的唯一id(如wx:key="id"),不要用index(性能差); - ❌ 忘记开启下拉刷新:在json中设置
enablePullDownRefresh: true,否则onPullDownRefresh不触发。
五、调试技巧
- Console面板:打印日志找问题,用
console.log()输出数据,比如在onLoad中打印options,看是否拿到传参; - AppData面板:查看当前页面的data数据,确认数据是否更新成功;
- Network面板:后续学网络请求时,查看请求是否成功、返回数据是否正确;
- 真机调试:点击“预览”,生成二维码,用微信扫码,看真机效果(模拟器和真机可能有差异);
- 错误提示:编辑器底部会显示语法错误,红色提示是致命错误,黄色是警告,按提示修改。
六、总结
- 小程序开发核心是「结构(WXML)+ 样式(WXSS)+ 逻辑(JS)+ 配置(JSON)」,四者配合完成页面开发;
- 基础语法重点掌握:数据绑定
{{ }}、列表渲染wx:for、事件绑定bindtap、页面跳转wx.navigateTo; - 实战关键:先搭结构,再写样式,最后加逻辑,一步一步调试,避免一次性写大量代码;
- 新手避坑:重点检查路径、数据修改方式、事件绑定,这三类错误占新手问题的80%;
- 进阶方向:后续可学习网络请求(wx.request)、本地存储(wx.setStorage)、支付功能、云开发等。
1万+

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



