MyLesson 小程序前台前端开发(一)
MyLesson 微信小程序前台前端的开发流程。内容包括基础环境搭建(微信开发者工具、IDEA)、通用组件封装(工具类、常量、API 请求)、底部导航栏自定义实现。核心功能模块涵盖首页展示(通知、轮播、秒杀)、用户登录注册(账号、手机验证码)、课程列表(搜索、分页)及课程详情(视频播放、摘要、目录、智能客服)。基于 VantWeapp 组件库,采用 SCSS 预处理,实现了完整的前端交互逻辑。

MyLesson 微信小程序前台前端的开发流程。内容包括基础环境搭建(微信开发者工具、IDEA)、通用组件封装(工具类、常量、API 请求)、底部导航栏自定义实现。核心功能模块涵盖首页展示(通知、轮播、秒杀)、用户登录注册(账号、手机验证码)、课程列表(搜索、分页)及课程详情(视频播放、摘要、目录、智能客服)。基于 VantWeapp 组件库,采用 SCSS 预处理,实现了完整的前端交互逻辑。

微信小程序页面组成:微信小程序的每个页面都由四个同名文件组成,比如创建一个页面为 Apple,则需要同时创建以下四个文件:
| 页面 | 描述 |
|---|---|
| Apple.json | 用于配置当前页面的窗口表现,例如页面标题、导航栏颜色、使用哪些组件等 |
| Apple.wxml | 即微信小程序的页面结构模板文件,采用类似 HTML 的标签语法 |
| Apple.wxss | 用于定义当前 Apple 页面的样式,语法与 CSS 基本一致 |
| Apple.js | 是当前页面的逻辑处理文件,负责页面的生命周期管理、数据定义、事件处理以及与用户交互相关的逻辑实现 |
微信小程序页面创建:当 IDEA 正确添加了微信小程序相关插件后,可直接创建同时包含四个文件的页面或组件:
New -> Wechat Mini program Page 并输入名称即可,页面创建成功后,自动在 app.json 文件的 pages 块中添加对应的路径。New -> Wechat Mini program Component 并能输入名称即可,和页面不同,组件创建成功后,不会在 app.json 文件的 pages 块中添加对应的路径。SCSS 语法教程:参考相关 CSS 文章。
在小程序项目中添加 SCSS 支持:
"useCompilerPlugins": ["sass"] 代码以添加 SCSS 支持,若已存在一个相同项,则需要将其删除:{"setting":{"useCompilerPlugins":["sass"],...},}
/* 自定义颜色变量 */
$bg-gray: #252a34; $black: #000000; $yellow: #f9ed69; $orange: #f08a5d; $white: #eaeaea; $gray: #757171; $subBlack: #232121;
/* 全局样式 */
page{
background-color: $bg-gray; // 背景色
color: $white; // 前景色
font-size: 35rpx; // 字体大小
text-align: center; // 文字居中
font-family: 思源黑体,微软雅黑,Consolas, sans-serif; // 字体
}
/* 取消滚动条 */
::-webkit-scrollbar{
display: none; // 隐藏滚动条
width: 0; // 宽度
height: 0; // 高度
color: transparent; // 颜色
}
/** vant 分割线 */
.van-divider{padding: 0 20rpx; // 内边距 }
/** 底线 */
.baseline{height: 200rpx; // 高度 padding-bottom: 50rpx; // 下边距 }
心法:Vant 是一个轻量、可靠的移动端组件库,于 2017 年开源,目前 Vant 官方提供了 Vue 2 版本、Vue 3 版本和微信小程序版本。
在小程序项目中添加 VantWeapp 支持:
npm install @vant/[email protected] --save
"style": "v2" 去除,规避部分组件样式混乱。"packNpmManually": true 代码开启 NPM 支持。"packNpmRelationList" -> "packageJsonPath": "" 指定 NPM 依赖文件的位置。"packNpmRelationList" -> "miniprogramNpmDistDir": "" 指定 NPM 在哪个目录中打包。{"setting":{..."packNpmManually":true,"packNpmRelationList":[{"packageJsonPath":"./package.json","miniprogramNpmDistDir":"./"}],}}
工具 -> 构建 npm,构建完成后,即可引入组件。开发通用工具 utils/util.js 文件:
/**
* 判断非空值
*
* @param value 被判断的值
* @return boolean 返回 true 表示不为 null 也不为 undefined
*/
function isNotNull(value){
return value !==null&& value !==undefined;
}
/**
* 判断空值
*
* @param value 被判断的值
* @return boolean 返回 true 表示为 null 或 undefined
*/
function isNull(value){
return!isNotNull(value);
}
/**
* 判断是否存在空值
*
* @param values 被判断的值,不定长列表
* @return boolean 返回 true 表示包含 null 或 undefined
*/
function hasNull(...values){
for(let i in values){
if(isNull(values[i])){
return true;
}
}
return false;
}
/**
* 判断空字符串
*
* @param value 被判断的值
* @return boolean 返回 true 表示不为 null 或 undefined 或空字符串
*/
function isNotEmpty(value){
return value !==null&& value !==undefined&& value !=='';
}
/**
* 判断非空字符串
*
* @param value 被判断的值
* @return boolean 返回 true 表示为 null 或 undefined 或空字符串
*/
function isEmpty(value){
return!isNotEmpty(value);
}
(){
( i values){
((values[i])){
;
}
}
;
}
(){
((dateStr)) ;
date = (dateStr);
=e=> e <?+ e : e;
yy =(date.());
mm =(date.()+);
dd =(date.());
hh =(date.());
mi =(date.());
;
}
(){
((dateStr)) ;
date = (dateStr);
=e=> e <?+ e : e;
yy =(date.());
mm =(date.()+);
dd =(date.());
;
}
(){
result = .().().(,);
len = .(len,); len = .(len,);
result.(-len);
}
(){
rgb =[]
( i =; i <;++i){
color = .(.()*).()
color = color. ===?+ color : color
rgb.(color)
}
+ rgb.()
}
(){
wx.({
:,: content,
:(){
(sm. && onConfirm)();
(sm. && onCancel)();
}});
}
(){
wx.({
: url,
:{
(reload){
page =().();
(page) page.();
}
});
}
(){
wx.({
: url,
:{
(reload){
page =().();
(page) page.();
}
});
}
(){
wx.({
: title,
:,
:});
}
(){
wx.({
: title,
:,
:});
}
(){
wx.({
: title,
:,
:});
}
(){
wx.({
: title,: content,
:(){
(res.){
(success)();
}{
(cancel)();
}
}});
}
(){
(!wx.()){
();
({(,);},);
;
}
;
}
. ={ isNotNull, isNull, hasNull, isNotEmpty, isEmpty, hasEmpty, dateFormat, datetimeFormat, randomStr, randomColor, confirm, tab, page, success, error, tip, modal, isLogin }
开发常量工具 utils/const.js 文件:
// 环境 IP 地址
const HOST='localhost';
const LINUX_HOST='192.168.40.77';
const GATEWAY_HOST=`http://${HOST}:24101`;
const SOCKET_SERVER=`ws://${HOST}:24107`;
const MINIO_HOST=`http://${LINUX_HOST}:9001/mylesson/`;
const MINIO_AVATAR=MINIO_HOST+'/avatar/';
const MINIO_BANNER=MINIO_HOST+'/banner/';
const MINIO_COURSE_COVER=MINIO_HOST+'/course-cover/';
const MINIO_COURSE_SUMMARY=MINIO_HOST+'/course-summary/';
const MINIO_EPISODE_VIDEO=MINIO_HOST+'/episode-video/';
const MINIO_EPISODE_VIDEO_COVER=MINIO_HOST+'/episode-video-cover/';
const UPLOAD_AVATAR_URL=GATEWAY_HOST+'/user-server/api/v1/user/uploadAvatar/';
// 常用请求状态码
const STATUS={SUCCESS:1000}
// 常用表单规则
={
:[{: ,:}],
:[{: ,:}],
:[{: ,:}],
:[{: ,:}],
:[{: ,:}],
:[{: ,:}],
:[{: ,:}],
:[{: ,:}],
:[{: ,:}],
:[{: ,:}],
:[{: ,:}],
:[{: ,:}],
:[{: ,:}],
:[{: ,:}],
:[{: ,:}],
:[{: ,:}],
:[{: ,:}],
}
=;
=;
=[
{:,:,:[{:,:},{:,:},{:,:},{:,:},{:,:}]},
{:,:,:[{:,:},{:,:},{:,:}]},
{:,:,:[{:,:},{:,:},{:,:},{:,:},{:,:},{:,:},{:,:}]},
{:,:,:[{:,:},{:,:},{:,:},{:,:},{:,:},{:,:}]},
{:,:,:[{:,:},{:,:},{:,:},{:,:},{:,:}]},
{:,:,:[{:,:},{:,:},{:,:},{:,:},{:,:}]},
{:,:,:[{:,:},{:,:},{:,:}]}
]
=[,,,,,,,,,,,];
. ={,,,,,,,,,,,,,,}
开发常量工具 utils/api.js 文件:
import util from'./util.js';
import constant from'./const.js';
/**
* (私有)API 前缀处理:根据模块名称返回 API 前缀
*
* @param module 模块名称,如 user, course 等
* @return 对应的 API 前缀,如 /user-server/api/v1/user 等,末尾无 / 符号
*/
function apiPrefixFormat(module){
// 微服务与模块的映射关系:key 为微服务名称,value 为模块名称数组
const serviceModuleMap ={'user-server':['menu','role','user'],'course-server':['category','comment','course','episode','report','season'],'sale-server':['article','banner','coupons','notice','seckill','seckillDetail'],'order-server':['cart','order','orderDetail']};
// 查找匹配的微服务名称:遍历微服务与模块的映射关系,找到包含当前模块 module 的微服务名称
const microServiceName = Object.keys(serviceModuleMap).find(key=> serviceModuleMap[key].includes(module));
// 返回拼接后的 API 前缀
return`/${microServiceName}/api/v1/${module}`;
}
(){
= config[];
url = config[];
(util.(, url));
method = config[];
params = config[];
header = config[];
(util.(method)) method =;
(util.(header)){
token = wx.()||;
header ={:,: token};
}
url = constant.+()+ url;
({
wx.({
: url,
: method,
: params,
: header,
(){
(util.(res)) util.();
res =!== res. &&!== res.[]? res. : res;
(res[]=== constant..){
}
{ util.(res[]); .(res[]);
}},
(){(+ err);
}});
});
}
(){
({, url, params});
}
(){
({, url, params,:});
}
(){
({, url, params,:});
}
(){
({, url, params,:});
}
. ={get, post, put, del};
微信小程序的底部支持自定义导航栏。
开发小程序的底部导航栏:
通过右键 New -> Wechat Mini program Page 的方式创建'首页 + 课程 + 购物车 + 我的'四个选项卡相关的页面(其中首页 index 是项目本身就存在的)。
/pages/index/index 页面(json + wxml + scss + js)。/pages/course/course 页面(json + wxml + scss + js)。/pages/cart/cart 页面(json + wxml + scss + js)。/pages/user/user 页面(json + wxml + scss + js)。微信小程序自定义的底部导航栏需要自行创建,且注意目录名称,组件名称和位置均固定,不可随意更改。
在项目根目录开发底部导航栏组件(自定义):
New -> Wechat Mini program Component 创建 index 组件:|_ ml-miniapp |_ custom-tab-bar |_ index.js |_ index.json |_ index.scss |_ index.wxml ...
custom-tab-bar/index.json:
{"component":true,"usingComponents":{"van-tabbar":"@vant/weapp/tabbar/index","van-tabbar-item":"@vant/weapp/tabbar-item/index"}}
custom-tab-bar/index.wxml:
<view custom-class="tab-bar"><van-tabbar active="{{activeTab}}" bind:change="changeTab"><van-tabbar-item wx:for="{{ tabs }}" wx:key="index" icon="{{item['icon']}}"> {{ item['text'] }} </van-tabbar-item></van-tabbar></view>
custom-tab-bar/index.scss:
/**暂无*/
custom-tab-bar/index.js:
import util from"../utils/util.js";
Component({
data:{
// 底部导航栏 - 当前标签(索引)
activeTab:0,
// 底部导航栏 - 全部标签
tabs:[
{pagePath:"/pages/index/index",text:"首页",icon:'home-o'},
{pagePath:"/pages/course/course",text:"课程",icon:'shop-o'},
{pagePath:"/pages/cart/cart",text:"购物车",icon:'cart-o'},
{pagePath:"/pages/user/user",text:"我的",icon:'user-o'}
]},
methods:{
// 点击标签按钮时,切换标签页
changeTab(ev){// 获取索引:首页 0,课程 1,购物车 2,我的 3
let tabIndex = ev.detail;
// 获取对应的标签页
let tab =this.data.tabs[tabIndex];
// 获取对应的跳转路径
let pagePath = tab['pagePath'];
// 切换标签页
util.tab(pagePath);
}}});
{...
"tabBar":{"custom":true,"list":[
{"pagePath":"pages/index/index"},
{"pagePath":"pages/course/course"},
{"pagePath":"pages/user/user"},
{"pagePath":"pages/cart/cart"}
]}}
分别在四个标签页面的 onLoad 函数中修改 activeTab 变量,此时点击四个选项卡按钮的时候,会有切换页面的效果。
pages/index/index.js:
Page({
data:{},
onLoad:function(options){
this.getTabBar().setData({"activeTab":0});
}})
pages/course/course.js:
Page({
data:{},
onLoad:function(options){
this.getTabBar().setData({"activeTab":1});
}});
pages/cart/cart.js:
Page({
data:{},
onLoad:function(options){
this.getTabBar().setData({"activeTab":2});
}});
pages/user/user.js:
Page({
data:{},
onLoad:function(options){
this.getTabBar().setData({"activeTab":3});
}});
本模块包含项目首页展示,用户登录页面(包括账号登录和手机 + 验证码登录两种)和用户注册页面。
项目首页主要用于展示通知,标题,横幅,公告新闻和秒杀活动等营销类型的内容,此界面无需用户登录即可查看。
页面要素:位置自上而下:
| 页面要素 | 描述 |
|---|---|
| 一条通知 | 从后台取出第 1 条通知内容并滚动播放 |
| 登录提示 | 若用户未登录时,提供提示和登录按钮 |
| 大小标题 | 展示项目大标题和小标题 |
| 轮播图片 | 从后台取出前 5 条横幅内容并滚动播放 |
| 公告列表 | 从后台取出前 5 条新闻内容并使用折叠组件进行展示 |
| 整点秒杀 | 从后台取出今日的秒杀活动并分别展示每场的秒杀物品详情 |
开发项目首页:
index.json:
{"usingComponents":{"van-sticky":"@vant/weapp/sticky/index","van-notice-bar":"@vant/weapp/notice-bar/index","van-image":"@vant/weapp/image/index","van-divider":"@vant/weapp/divider/index","van-collapse":"@vant/weapp/collapse/index","van-collapse-item":"@vant/weapp/collapse-item/index","van-tab":"@vant/weapp/tab/index","van-tabs":"@vant/weapp/tabs/index","van-card":"@vant/weapp/card/index","van-button":"@vant/weapp/button/index","van-empty":"@vant/weapp/empty/index"}}
index.wxml:
<van-sticky class="notice-bar"><van-notice-bar text="{{currentNotice}}" left-icon="volume-o"/></van-sticky><view class="login-tips" wx:if="{{isLogin}}"> 游客部分功能受限,点我 <text bind:tap="toLogin" class="link">登录系统</text>! </view><view class="project-title"><view class="title">{{PROJECT_TITLE}}</view><view class="sub-title">{{PROJECT_SUB_TITLE}}</view></view><view class="banner"><swiper wx:if="{{banners}}" autoplay="3000"><swiper-item wx:for="{{banners}}" wx:key="id">< = = = =/>公告列表 {{item['content']}}整点秒杀未开始秒杀中已结束底线
index.scss:
@import"app";
.login-tips{
color: $gray; // 前景色
margin-top: 20rpx;// 外边距
.link{
color: $orange; // 前景色
}}
.project-title{
letter-spacing: 5rpx; // 字间距
line-height: 80rpx; // 行高
margin: 40rpx 60rpx;// 外边距
.title{
font-size: 45rpx; // 字体大小
}
.sub-title{
font-size: 40rpx; // 字体大小
color: $orange; // 字体颜色
}}
.banner{
height: 400rpx; // 高度
margin: 0 20rpx;// 外边距
swiper, image{
height: 400rpx; // 高度
}
.no-banner-tip{
height: 400rpx;// 高度
.van-empty__image{
height: ;
: rpx;
}}}
{
-align: left;
{
: ;
: ;
: bold;
: rpx;
}
{
: rpx;
}
{
: ;
: rpx;
}}
{
: rpx;
{
: ;
: ;
}
{
: ;
{
: ;
}}}
{
: ;
: ;
: rpx solid ;
: rpx ;
}
{
: rpx;
-align: left;
}
{
: rpx;
: large;
: rpx;
: ;
}
{
: ;
}
{
: rpx;
: rpx;
}
{
: rpx;
: rpx;
-: rpx;
-align: center;
: large;
: -;
: right;
: -;
: ;
: ;
}
, {
: ;
: ;
: ;
}
{
: ;
: ;
}}
index.js:
import api from'../../utils/api.js';
import constant from'../../utils/const.js';
import util from"../../utils/util.js";
Page({
data:{
isLogin:false,// 是否已登录
currentNotice:null,// 当前通知对象
PROJECT_TITLE: constant.PROJECT_TITLE,// 项目主标题
PROJECT_SUB_TITLE: constant.PROJECT_SUB_TITLE,// 项目副标题
MINIO_BANNER: constant.MINIO_BANNER,// 横幅轮播图片 MINIO 地址
banners:null,// 横幅列表对象
currentArticleIdx:1,// 当前公告的 value 值,用于切换公告
articles:null,// 公告列表对象
seckills:null,// 秒杀活动列表对象
activeSeckillIdx:0,// 当前秒杀活动的 value 值,用于切换秒杀活动
MINIO: constant.MINIO_COURSE_COVER// 课程封面图片 MINIO 地址
},
// 跳入登录页面
toLogin:function(){ util.page('/pages/index/login-by-account/login-by-account',true);},
// 查询 1 条通知记录
topNotice1:(){
that =;
api.(,).( that.({: res[][]})).( .(err));
},
:(){
that =;
api.(,).( that.({: res})).( .(err));
},
:(){
that =;
api.(,).( that.({: res})).( .(err));
},
:(){
that =;
api.(,).( that.({: res})).( .(err));
},
:(){
.({: ev.});
},
:(){
.({: ev.});
},
:(){
.({:!wx.()});
.();
.();
.();
.();
.().({:});
},
});
账号登录页面用于通过账号和密码进行系统登录,登录成功在前端保存用户信息和 Token 令牌,并会跳回首页。
页面要素:位置自上而下:
| 页面要素 | 描述 |
|---|---|
| 账号输入框 | 采集登录账号,测试环境下固定为 admin,开发环境下置空 |
| 密码输入框 | 采集登录密码,测试环境下固定为 123456789,开发环境下置空 |
| 登录按钮 | 向后台请求登录,提交账号和密码,登录成功后在前端保存用户信息和 Token 令牌 |
| 注册新账号按钮 | 跳转到注册页面 |
| 手机号码登录按钮 | 跳转到通过手机号码登录的页面 |
| 返回首页按钮 | 跳转到首页选项卡 |
| 加载函数 | 在前端清空用户信息和 Token 令牌 |
开发账号登录页面:
login-by-account.json:
{"usingComponents":{"van-field":"@vant/weapp/field/index","van-button":"@vant/weapp/button/index","van-row":"@vant/weapp/row/index","van-col":"@vant/weapp/col/index"}}
login-by-account.wxml:
<view class="login-by-account-board"><van-field label="登录账号" model:value="{{username}}" custom-class="field" right-icon="user-o" border="{{false}}" clearable="{{true}}" placeholder="请输入账号"/><van-field label="登录密码" model:value="{{password}}" custom-class="field" type="password" right-icon="eye-o" border="{{false}}" clearable="{{true}}" placeholder="请输入密码"/><van-button custom-class="login-btn" type="info" block bind:tap="loginByAccount">登录</van-button><van-row custom-class="link"><van-col span="8" bind:tap="toRegister">注册新账号</>手机号码登录返回首页
login-by-account.scss:
@import"app";
.login-by-account-board{
padding: 20rpx;// 内边距
.field{
margin: 20rpx auto; // 外边距
border-radius: 15rpx; // 圆角
background-color: $bg-gray; // 背景颜色
border: 3rpx solid $black; // 边框
color: $white; // 字体颜色
text-align: left; // 左对齐
}
.van-field__label{
color: $yellow; // 字体颜色
}
.van-field__control{
color: $white; // 字体颜色
}
.login-btn{
font-size: large; // 字体大小
border-radius: 15rpx; // 圆角
}
.link{
font-size: 30rpx; // 字体大小
margin: 20rpx; // 外边距
color: $gray; // 字体颜色
}}
login-by-account.js:
import util from"../../../utils/util.js";
import api from"../../../utils/api.js";
import constant from"../../../utils/const.js";
Page({
data:{
username:'admin',// 登录账号
password:'123456789',// 登录密码
},
// 通过账号密码登录系统
loginByAccount:function(){
let username =this.data.username;
let password =this.data.password;
// 空值校验
if(util.hasEmpty(username, password)){ util.tip('账号或密码不能为空');return;}
// 校验登录账号规则
if(!constant.RULE.USERNAME[0]['pattern'].test(username)){ util.tip(constant.RULE.USERNAME[0]['message']);return;}
// 校验登录密码规则
if(!constant.RULE.PASSWORD[0]['pattern'].test(password)){ util.(constant..[][]);;}
api.(,,{username, password}).({
wx.(, res[]);
wx.(, res[]);
util.();
( util.(),);
}).( .(err));
},
:(){ util.(,);},
:(){ util.(,);},
:(){ util.();},
:(){
wx.();
wx.();
}});
手机登录页面用于通过手机号码和验证码进行系统登录:用户输入手机号码,然后点击'发送验证码'按钮,获取到一个验证码(测试环境下直接返回给前端并自动填充,生产环境下给对应手机号码发送短信)。用户输入验证码,点击登录按钮,向后台请求登录,提交手机号码和验证码。登录成功后在前端保存用户信息和 Token 令牌,并跳回首页。
页面要素:位置自上而下:
| 页面要素 | 描述 |
|---|---|
| 手机号码输入框 | 采集手机号码,测试环境下固定为 17766541438,开发环境下置空 |
| 短信验证码输入框 | 采集验证码 |
| 发送验证码按钮 | 向后台请求一个验证码,并自动填充到短信验证码输入框中 |
| 登录按钮 | 向后台请求登录,提交手机号码和验证码,登录成功后在前端保存用户信息和 Token 令牌 |
| 注册新账号按钮 | 跳转到注册页面 |
| 账号密码登录按钮 | 跳转到通过账号密码登录的页面 |
| 返回首页按钮 | 跳转到首页选项卡 |
| 加载函数 | 在前端清空用户信息和 Token 令牌 |
开发手机登录页面:
login-by-phone.json:
{"usingComponents":{"van-field":"@vant/weapp/field/index","van-button":"@vant/weapp/button/index","van-row":"@vant/weapp/row/index","van-col":"@vant/weapp/col/index"}}
login-by-phone.wxml:
<view class="login-by-phone-board"><van-field label="手机号码" model:value="{{phone}}" custom-class="field" right-icon="user-o" border="{{false}}" clearable="{{true}}" placeholder="请输入手机号码"/><van-field label="短信验证码" model:value="{{vcode}}" custom-class="field" center use-button-slot border="{{false}}" clearable="{{true}}" placeholder="请输入短信验证码"><van-button bind:tap="getVcode" slot="button" size="small" type="primary">发送验证码</van-button></van-field><van-button custom-class="login-btn" bind:tap="loginByPhone" type="info" block>登录</>注册新账号账号密码登录返回首页
login-by-phone.scss:
@import"app";
.login-by-phone-board{
padding: 20rpx;// 内边距
.field{
margin: 20rpx auto; // 外边距
border-radius: 15rpx; // 圆角
background-color: $bg-gray; // 背景颜色
border: 3rpx solid $black; // 边框
color: $white; // 字体颜色
text-align: left; // 左对齐
}
.van-field__label{
color: $yellow; // 字体颜色
}
.van-field__control{
color: $white; // 字体颜色
}
.login-btn{
font-size: large; // 字体大小
border-radius: 15rpx; // 圆角
}
.link{
font-size: 30rpx; // 字体大小
margin: 20rpx; // 外边距
color: $gray; // 字体颜色
}}
login-by-phone.js:
import api from"../../../utils/api.js";
import util from"../../../utils/util.js";
import constant from"../../../utils/const.js";
Page({
data:{
phone:'17766541438',// 手机号码
vcode:'',// 短信验证码
},
// 获取短信验证码
getVcode:function(){
let that =this;
let phone =this.data.phone;
// 空值校验
if(util.isEmpty(phone)){ util.tip('手机号码不能为空');return;}
// 校验手机号码规则
if(!constant.RULE.PHONE[0]['pattern'].test(phone)){ util.tip(constant.RULE.PHONE[0]['message']);return;}
// 发送请求:根据手机号码获取短信验证码
api.get('user','/getVcode/'+ phone).then(res=>{
util.success('验证码获取成功');
that.setData({: res});
}).( .(err));
},
:(){
phone =..;
vcode =..;
(util.(phone, vcode)){ util.();;}
(!constant..[][].(phone)){ util.(constant..[][]);;}
api.(,,{phone, vcode}).({
wx.(, res[]);
wx.(, res[]);
util.();
( util.(),);
}).( .(err));
},
:(){ util.(,);},
:(){ util.(,);},
:(){ util.();},
:(){
wx.();
wx.();
}});
注册页面用于注册新账号,注册成功后会跳入账号密码登录页面。
页面要素:位置自上而下:
| 页面要素 | 描述 |
|---|---|
| 登录账号输入框 | 采集登录账号 |
| 登录密码输入框 | 采集登录密码 |
| 确认密码输入框 | 采集确认密码,仅用于规避误输入 |
| 真实姓名输入框 | 采集真实姓名 |
| 手机号码输入框 | 采集手机号码 |
| 身份证号输入框 | 采集身份证号 |
| 电子邮箱输入框 | 采集电子邮箱 |
| 注册按钮 | 向后台请求注册,提交全部输入框数据 |
| 账号密码登录按钮 | 跳转到通过账号密码登录的页面 |
| 手机号码登录按钮 | 跳转到通过手机号码登录的页面 |
| 返回首页按钮 | 跳转到首页选项卡 |
开发注册页面:
register.json:
{"usingComponents":{"van-field":"@vant/weapp/field/index","van-button":"@vant/weapp/button/index","van-row":"@vant/weapp/row/index","van-col":"@vant/weapp/col/index"}}
register.wxml:
<view class="register-board"><van-field label="登录账号" model:value="{{ username }}" custom-class="field" right-icon="user-o" placeholder="请输入登录账号" clearable="{{true}}" border="{{ false }}"/><van-field label="登录密码" model:value="{{ password }}" type="password" custom-class="field" right-icon="eye-o" placeholder="请输入登录密码" clearable="{{true}}" border="{{ false }}"/><van-field label="确认密码" model:value="{{ rePassword }}" type="password" custom-class="field" right-icon="eye-o" placeholder="请确认登录密码" clearable="{{true}}" border="{{ false }}"/><van-field label= = = = = = =/>注册账号密码登录手机号码登录返回首页
register.scss:
@import"app";
.register-board{
padding: 20rpx;// 内边距
.field{
margin: 20rpx auto; // 外边距
border-radius: 15rpx; // 圆角
background-color: $bg-gray; // 背景颜色
border: 3rpx solid $black; // 边框
color: $white; // 字体颜色
text-align: left; // 左对齐
}
.van-field__label{
color: $yellow; // 字体颜色
}
.van-field__control{
color: $white; // 字体颜色
}
.register-btn{
font-size: large; // 字体大小
border-radius: 15rpx; // 圆角
}
.link{
font-size: 30rpx; // 字体大小
margin: 20rpx; // 外边距
color: $gray; // 字体颜色
}}
register.js:
import util from"../../../utils/util.js";
import api from"../../../utils/api.js";
import constant from"../../../utils/const.js";
Page({
data:{
username:'',
password:'',
rePassword:'',
realname:'',
phone:'',
idcard:'',
email:'',
},
// 注册账号
register:function(){
let username =this.data.username;
let password =this.data.password;
let rePassword =this.data.rePassword;
let realname =this.data.realname;
let phone =this.data.phone;
let idcard =this.data.idcard;
let email =this.data.email;
(password !== rePassword){ util.();;}
(!constant..[][].(username)){ util.(constant..[][]);;}
(!constant..[][].(password)){ util.(constant..[][]);;}
(!constant..[][].(realname)){ util.(constant..[][]);;}
(!constant..[][].(phone)){ util.(constant..[][]);;}
(!constant..[][].(idcard)){ util.(constant..[][]);;}
(!constant..[][].(email)){ util.(constant..[][]);;}
param ={username, password, realname, phone, idcard, email};
api.(,, param).({
util.();
({ util.(,);},);
}).( .(err));
},
:(){ util.(,);},
:(){ util.(,);},
:(){ util.();},
:(){
}});
本模块包含课程列表页面和课程详情页面,这两个页面均不需要用户登录即可访问。
课程列表:课程列表是导航页面,当用户点击底部导航栏'课程'按钮时进行分页展示全部课程列表。
课程详情:当点击课程列表中的某个具体课程封面时,跳转到对应该课程的详情页面。
当用户点击底部选项卡'课程'的时候,跳转到课程列表页面,页面加载后,向后台请求课程列表(ElasticSearch 数据库)并渲染到页面,出于性能考虑,页面默认显示 12 条课程数据,当滚动条触底时,进行下一次分页查询(再查 12 条)。
页面要素:位置自上而下:
| 页面要素 | 描述 |
|---|---|
| 搜索框 | 用户可通过课程名称或者作者名称进行分词搜索 |
| 课程列表 | 向后台请求课程列表(ElasticSearch 数据库)并渲染到页面 |
开发课程列表相关页面:
course.json:
{"usingComponents":{"van-sticky":"@vant/weapp/sticky/index","van-search":"@vant/weapp/search/index","van-divider":"@vant/weapp/divider/index","van-grid":"@vant/weapp/grid/index","van-grid-item":"@vant/weapp/grid-item/index","van-image":"@vant/weapp/image/index","van-icon":"@vant/weapp/icon/index","van-rate":"@vant/weapp/rate/index"}}
course.wxml:
<van-sticky><van-search class="search-ipt" value="{{keyword}}" bind:search="searchByKeyword" bind:clear="cancelSearch" background="#252a34" shape="round" clear-trigger="always" input-align="center" placeholder="请输入课程标题进行搜索"/></van-sticky><scroll-view class="course-list" bindscrolltolower="onListEnd" scroll-y="true"><van-divider contentPosition="center" dashed>课程列表</van-divider><van-grid class="course-list-grid" column-num="2" gutter="20rpx"><van-grid-item content-class="course-item" wx:for="{{courses}}" wx:key="id" data-course-id="{{item['id']}}" bind:tap="showDetail" >{{ item['title'] }} 作者:{{item['author']}} 价格:{{item['price']} }元 没有更多课程了底线
course.scss:
@import"app";
.course-list{
height: 90vh;// 高度
.course-item{
background-color: $black;// 背景色
.title{
font-weight: bold; // 加粗
white-space: nowrap; // 不换行
overflow: hidden; // 超出隐藏
text-overflow: ellipsis; // 超出部分显示...
color: $yellow; // 字体颜色
font-size: 30rpx; // 字体大小
margin-top: 10rpx; // 上边距
margin-left: 20rpx; // 左边距
margin-bottom: 20rpx; // 下边距
text-align: left; // 文字左对齐
width: 100%; // 宽度
}
.info{
font-size: small; // 字体大小
text-align: left; // 文字左对齐
line-height: 40rpx; // 行高
width: 100%; // 宽度
margin-left: rpx;
}}}
course.js:
import util from'../../utils/util.js';
import api from"../../utils/api.js";
import constant from"../../utils/const.js";
Page({
data:{
MINIO_COURSE_COVER: constant.MINIO_COURSE_COVER,// 课程封面路径
pageInfo:{pageNum:1,pageSize:12,totalPage:0,totalRow:0},// 分页信息
courses:null,// 课程列表对象
keyword:'',// 课程标题
},
// 分页搜索课程记录
page:function(){
let that =this;
let keyword =this.data.keyword;
let pageNum =this.data.pageInfo['pageNum'];
let pageSize =this.data.pageInfo['pageSize'];
if(util.isNull(pageNum)) pageNum =1;
if(util.isNull(pageSize)) pageSize =5;
(keyword. >){ util.();;}
params ={pageNum, pageSize,: keyword.()};
api.(,, params).({
that.({: pageNum ===? res[]: that...(res[]),: res[],: res[],: res[],: res[],});
}).( .(err));
},
:(){
that =;
pageNum = that..[];
totalPage = that..[];
(pageNum < totalPage){
.({: pageNum +});
.();
},
:(){
.({: ev.,:,});
.();
},
:(){
.({:,:,});
.();
},
:(){
courseId = ev..[];
util.(+ courseId,);
},
:(){
.();
.().({:});
}});
当用户在课程列表界面点击某个课程封面时,会跳转到对应的课程详情页面。
页面要素:位置自上而下:
| 页面要素 | 描述 |
|---|---|
| 视频组件 | 点击可播放当前课程的第一章第一集的视频内容 |
| 课程详情 | 列表展示当前课程的详情,如类别,标题,作者,价格等 |
| 课程摘要 | 选项卡,点击可展示课程摘要图片 |
| 课程目录 | 选项卡,点击可展示课程章集目录 |
| 客服按钮 | 点击跳入 AI 客服页面 |
| 购物车按钮 | 点击跳入购物车选项卡 |
| 加入购物车按钮 | 点击将当前课程加入我的购物车列表 |
| 立即购买按钮 | 点击立刻购买该课程 |
开发课程详情相关页面:
detail.json:
{"usingComponents":{"van-sticky":"@vant/weapp/sticky/index","van-divider":"@vant/weapp/divider/index","van-cell-group":"@vant/weapp/cell-group/index","van-cell":"@vant/weapp/cell/index","van-tabs":"@vant/weapp/tabs/index","van-tab":"@vant/weapp/tab/index","van-rate":"@vant/weapp/rate/index","van-image":"@vant/weapp/image/index","van-goods-action":"@vant/weapp/goods-action/index","van-goods-action-icon":"@vant/weapp/goods-action-icon/index","van-goods-action-button":"@vant/weapp/goods-action-button/index","van-empty":"@vant/weapp/empty/index"}}
detail.wxml:
<van-sticky class="free-video"><video wx:if="{{videoSrc && videoPoster}}" src="{{videoSrc}}" title="{{videoTitle}}" poster="{{videoPoster}}" danmu-list="{{welcomeBarrage}}" controls show-mute-btn enable-danmudanmu-btn/><van-empty class="free-video-empty" wx:else image="error" description="暂无免费视频"/></van-sticky><view class="course-info" wx:if="{{course}}"><van-divider contentPosition="center" dashed>课程详情</van-divider><van-cell-group inset><van-cell title="课程类别" value="{{course['category']['title']}}" icon="flag-o"/><van-cell title="课程标题" value="{{course['title']}}" =/>底线{{episode['info']}底线
detail.scss:
@import"app";
.free-video{
video{
width: 98%; // 宽度
out-line: 10rpx solid $black; // 轮廓
box-sizing: border-box; // 边框
}}
.course-info{
.van-cell{
background-color: $bg-gray; // 背景色
color: $yellow;// 文字颜色
.van-cell__title{
text-align: left; // 文字对齐方式
}
.van-cell__value{
color: $white; // 文字颜色
}}}
.operation-tabs{
height: 80vh; // 高度
margin: 20rpx 30rpx;// 外边距
.van-tab{
color: $subBlack !important; // 文字颜色
font-weight: bold !important; // 字体粗细
}
.summary{
padding-top: 20rpx; // 内边距
}
.catalog{
padding-top: 20rpx; // 内边距
padding-bottom: rpx;
{
: ;
: bold;
: ;
: ;
}
{
: ;
: ;
-align: left ;
}}}
detail.js:
import api from'../../../utils/api.js';
import util from'../../../utils/util.js';
import constant from"../../../utils/const.js";
Page({
data:{
MINIO_COURSE_SUMMARY: constant.MINIO_COURSE_SUMMARY,// 视频封面 MINIO 地址
course:null,// 课程对象
videoSrc:null,// 免费视频地址
videoPoster:null,// 免费视频封面
videoTitle:null,// 免费视频标题
welcomeBarrage:[{text:'一大波弹幕即将来袭',color:'#ff0000',time:1}],// 欢迎弹幕
activeTab:'摘要'// 当前选中的 tab 的 name 值
},
// 查询视频详情
getCourseInfo:function(courseId){
let that =this;
let param ='/select/'+ courseId;
api.get('course', param).then(res=>{
res['created']= util.dateFormat(res['created']);
res['updated']= util.dateFormat(res[]);
(res[]. >&& res[][][]. >){
firstEpisode = res[][][][];
that.({: constant.+ firstEpisode[],: constant.+ firstEpisode[],: firstEpisode[],});
}
that.({: res});
}).( .(err));
},
:(){
(util.()){ util.();
}},
:(){
(util.()){
that =;
params ={: wx.().,: that..[],};
api.(,, params).({
util.();
({ util.(,);},);
}).( .(err))
}},
:(){ util.()},
:(){
(util.()){ util.();
}},
:(){
.(ev[]);
}});
当用户在课程详情左下角点击客服图标时跳转到本页面。
页面要素:位置自上而下:
| 页面要素 | 描述 |
|---|---|
| 对话框 | 显示用户和智能客服的对话内容 |
| 输入框 | 采集用户问题 |
| 提问按钮 | 请求后台智能客服的回复内容并渲染在对话框中 |
开发智能客服页面:
chat.json:
{"usingComponents":{"van-field":"@vant/weapp/field/index","van-button":"@vant/weapp/button/index","van-steps":"@vant/weapp/steps/index"}}
chat.wxml:
<scroll-view class="chat-board" scroll-top="{{scrollTop}}" scroll-y="{{true}}"><van-steps custom-class="chat-dialog" id="chatDialog" steps="{{ steps }}" active="{{ active }}" direction="vertical" active-color="#f9ed69" desc-class="desc"/></scroll-view><view class="tips">{{isReplying ? '客服回复中...' : '回复完毕'}}</view><van-field custom-class="chat-textarea" value="{{ message }}" bindinput="changeMessage" type="textarea" maxlength="{{500}}" show-word-limit="{{true}}" autosize border="{{false}}" placeholder="请输入你的问题"/><van-button custom-class="submit-btn" bind:tap="sendMessage" type="info" >提问
chat.scss:
@import"app";
.chat-board{
height: 1000rpx; // 高度
text-align: left; // 文本对齐方式
margin: 20rpx auto 50rpx;// 外边距
.chat-dialog{
background-color: $bg-gray;// 背景色
.van-step:nth-child(even){
text-align: right; // 文本对齐方式
}
.van-step--finish{
color: $white; // 字体颜色
}
.desc{
margin-top: 20rpx; // 上边距
font-size: larger; // 字体大小
font-weight: bolder; // 字体粗细
line-height: 1.5; // 行高
}}}
.tips{
font-size: smaller; // 字体大小
text-align: right; // 文本对齐方式
margin-right: 30rpx; // 右边距
color: $gray; // 字体颜色
}
.chat-textarea{
border: 3rpx solid $black; // 边框
width: auto ;
: rpx;
: rpx;
: ;
}
{
: ;
: rpx ;
}
{
: auto ;
: rpx;
: large;
: rpx;
}
chat.js:
import util from"../../../../utils/util.js";
Page({
data:{
message:'一首李白的诗',// 用户输入的问题
active:0,// 当前激活的步骤
steps:[{text:`${util.dateFormat(new Date())}`,desc:'你好,有什么可以帮助您?'}],
sseServerUrl:'http://localhost:24107/api/v1/base/chat',// SSE 服务端地址
isReplying:false,// AI 是否正在回复中
scrollTop:0// 滚动条位置
},
// 当内容改变时触发
changeMessage:function(ev){
this.setData({'message': ev.detail});
},
// 发送消息
sendMessage:function(){
let that =this;
// 如果 AI 正在回复中或用户输入的内容为空,均不处理
if(this.data.isReplying || util.isEmpty(this.data.message))return;
// 加入用户输入的信息
this.data.steps.({:,:.. });
...({:,:});
.({:..,:.. +,:});
wx.({
:.. ++..,
:
}).({
desc = that..[that..][];
(desc ===){ that..[that..][]= desc.(,);}
str = ().(res.,{:});
(!str.()){ that..[that..][]+= str;
{
query = wx.().();
query.().({
(rect).({: rect.});
}).();
});
},
:(){
}});

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online
基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online