MyLesson 微信小程序前台前端开发(一)
S01. 基础环境搭建
设计思路: ml-miniapp 是 MyLesson 项目中的前台用户交互载体,作为整个项目面向终端用户的核心入口,承担着连接系统服务与用户需求的关键桥梁作用。它基于微信小程序技术生态开发,充分依托微信平台的高渗透率、轻量化使用特性。
项目完整文件结构:
|_ ml-miniapp
|_ custom-tab-bar/*.*
|_ miniprogram_npm
|_ node_modules
|_ pages
|_ utils
|_ api.js
|_ const.js
|_ util.js
|_ app.js
|_ app.json
|_ app.scss
|_ package.json
|_ project.config.json
E01. 安装基础组件
1. 样式预处理 SCSS
实现步骤: 在小程序项目中添加 SCSS 支持。
- 在
project.config.json 的 settings 块的首行添加 "useCompilerPlugins": ["sass"] 代码以添加 SCSS 支持。
- 将
app.wxss 文件修改为 app.scss,然后在该文件中添加一些全局样式。
$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;
}
.van-divider {
padding: 0 20rpx;
}
.baseline {
height: 200rpx;
padding-bottom: 50rpx;
}
2. 前端框架 VantWeapp
实现步骤: 在小程序项目中集成 VantWeapp。
- 安装 VantWeapp 依赖:
npm install @vant/[email protected] --save
- 将
app.json 中的 "style": "v2" 去除,规避部分组件样式混乱。
- 在
project.config.json 文件中开启 NPM 支持。
- 在微信小程序开发者工具的右上角依次点击
工具 -> 构建 npm,构建完成后,即可引入组件。
E02. 封装通用组件
1. 封装通用工具 util
实现步骤: 开发通用工具 utils/util.js 文件。
function isNotNull(value) {
return value !== null && value !== undefined;
}
function isNull(value) {
return !isNotNull(value);
}
function hasNull(...values) {
for (let i in values) {
if (isNull(values[i])) {
return true;
}
}
return false;
}
function isNotEmpty(value) {
return value !== null && value !== undefined && value !== '';
}
function isEmpty(value) {
return !(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
};
2. 封装常量工具 const
实现步骤: 开发常量工具 utils/const.js 文件。
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
};
= {
: [{ : , : }],
: [{ : , : }],
: [{ : , : }],
: [{ : , : }],
: [{ : , : }],
: [{ : , : }],
: [{ : , : }],
: [{ : , : }],
: [{ : , : }],
: [{ : , : }],
: [{ : , : }],
: [{ : , : }],
: [{ : , : }],
: [{ : , : }],
: [{ : , : }],
: [{ : , : }],
: [{ : , : }]
};
= ;
= ;
= [
{ : , : , : [{ : , : }, { : , : }, { : , : }, { : , : }, { : , : }] },
{ : , : , : [{ : , : }, { : , : }, { : , : }] },
{ : , : , : [{ : , : }, { : , : }, { : , : }, { : , : }, { : , : }, { : , : }, { : , : }] },
{ : , : , : [{ : , : }, { : , : }, { : , : }, { : , : }, { : , : }, { : , : }] },
{ : , : , : [{ : , : }, { : , : }, { : , : }, { : , : }, { : , : }] },
{ : , : , : [{ : , : }, { : , : }, { : , : }, { : , : }, { : , : }] },
{ : , : , : [{ : , : }, { : , : }, { : , : }] }
];
= [, , , , , , , , , , , ];
. = {
,
,
,
,
,
,
,
,
,
,
,
,
,
,
};
3. 封装请求工具 api
实现步骤: 开发常量工具 utils/api.js 文件。
import util from './util.js';
import constant from './const.js';
function apiPrefixFormat(module) {
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']
};
const microServiceName = Object.keys(serviceModuleMap).find(key => serviceModuleMap[key].includes(module));
return `/${microServiceName}/api/v1/`;
}
() {
= 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. : );
}
{
util.(res[]);
.(res[]);
}
},
() {
( + err);
}
});
});
}
() {
({ , url, params });
}
() {
({ , url, params, : });
}
() {
({ , url, params, : });
}
() {
({ , url, params, : });
}
. = {
get,
post,
put,
del
};
E03. 开发底部导航栏
设计思路: 微信小程序的底部支持自定义导航栏。
实现步骤: 开发小程序的底部导航栏。
1. 开发导航栏相关页面
通过右键 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)。
2. 开发底部导航栏组件
设计思路: 微信小程序自定义的底部导航栏需要自行创建,且注意目录名称,组件名称和位置均固定,不可随意更改。
实现步骤: 在项目根目录开发底部导航栏组件(自定义)。
- 在项目根目录创建
custom-tab-bar 目录,然后右键 New -> Wechat Mini program Component 创建 index 组件。
- 开发 index 组件:
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) {
let tabIndex = ev.detail;
let tab = this.data.tabs[tabIndex];
let pagePath = tab['pagePath'];
util.tab(pagePath);
}
}
});
- 在
app.json 中配置底部导航栏:
{
..."tabBar": {
"custom": true,
"list": [
{ "pagePath": "pages/index/index" },
{ "pagePath": "pages/course/course" },
{ "pagePath": "pages/user/user" },
{ "pagePath": "pages/cart/cart" }
]
}
}
3. 配置导航栏切换效果
分别在四个标签页面的 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});
}
});
S02. 导航栏 - 首页
设计思路: 本模块包含项目首页展示,用户登录页面(包括 账号登录 和 手机 + 验证码登录 两种)和用户注册页面。
E01. 项目首页
设计思路: 项目首页主要用于展示通知,标题,横幅,公告新闻和秒杀活动等营销类型的内容,此界面无需用户登录即可查看。
页面要素: 位置自上而下:
| 页面要素 | 描述 |
|---|
| 一条通知 | 从后台取出第 1 条通知内容并滚动播放 |
| 登录提示 | 若用户未登录时,提供提示和登录按钮 |
| 大小标题 | 展示项目大标题和小标题 |
| 轮播图片 | 从后台取出前 5 条横幅内容并滚动播放 |
| 公告列表 | 从后台取出前 5 条新闻内容并使用折叠组件进行展示 |
| 整点秒杀 | 从后台取出今日的秒杀活动并分别展示每场的秒杀物品详情 |
实现步骤: 在 pages/index/ 目录下,开发项目首页。
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: 210px;
width: 400rpx;
}
}
}
.article {
text-align: left;
.van-cell {
background-color: $black;
color: $yellow;
font-weight: 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,
banners: null,
currentArticleIdx: 1,
articles: null,
seckills: null,
activeSeckillIdx: 0,
MINIO: constant.MINIO_COURSE_COVER
},
toLogin: function() {
util.page('/pages/index/login-by-account/login-by-account', true);
},
topNotice1: function() {
let that = this;
api.get('notice', '/top/1').then(res => that.setData({ currentNotice: res[0][] })).( .(err));
},
: () {
that = ;
api.(, ).( that.({ : res })).( .(err));
},
: () {
that = ;
api.(, ).( that.({ : res })).( .(err));
},
: () {
that = ;
api.(, ).( that.({ : res })).( .(err));
},
: () {
.({ : ev. });
},
: () {
.({ : ev. });
},
: () {
.({ : !wx.() });
.();
.();
.();
.();
.().({ : });
}
});
E02. 用户登录
1. 账号登录
设计思路: 账号登录页面用于通过账号和密码进行系统登录,登录成功在前端保存用户信息和 Token 令牌,并会跳回首页。
实现步骤: 在 pages/index/login-by-account/ 目录下,开发账号登录页面。
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.tip(constant.RULE.PASSWORD[0][]);
;
}
api.(, , { username, password }).( {
wx.(, res[]);
wx.(, res[]);
util.();
( util.(), );
}).( .(err));
},
: () {
util.(, );
},
: () {
util.(, );
},
: () {
util.();
},
: () {
wx.();
wx.();
}
});
2. 手机登录
设计思路: 手机登录页面用于通过手机号码和验证码进行系统登录。
实现步骤: 在 pages/index/login-by-phone/ 目录下,开发手机登录页面。
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({ vcode: res });
}).catch(err => .(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.();
}
});
E03. 用户注册
设计思路: 注册页面用于注册新账号,注册成功后会跳入账号密码登录页面。
实现步骤: 在 pages/index/register/ 目录下,开发注册页面。
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.;
(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.();
},
: () {}
});
S03. 导航栏 - 课程
设计思路: 本模块包含课程列表页面和课程详情页面,这两个页面均不需要用户登录即可访问。
E01. 课程列表
设计思路: 当用户点击底部选项卡'课程'的时候,跳转到课程列表页面,页面加载后,向后台请求课程列表(ElasticSearch 数据库)并渲染到页面,出于性能考虑,页面默认显示 12 条课程数据,当滚动条触底时,进行下一次分页查询。
实现步骤: 在 pages/course/ 目录下,开发课程列表相关页面。
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: 20rpx;
}
}
}
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;
if (keyword.length > 42) {
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, );
},
: () {
.();
.().({ : });
}
});
E02. 课程详情
设计思路: 当用户在课程列表界面点击某个课程封面时,会跳转到对应的课程详情页面。
实现步骤: 在 pages/course/detail/ 目录下,开发课程详情相关页面。
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-danmu danmu-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= =/>
底线
{{episode['info']}}
底线
detail.scss:
@import "app";
.free-video {
video {
width: 98%;
outline: 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: 150rpx;
.van-cell-group__title {
color: $black;
font-weight: bold;
background: $yellow;
padding-bottom: 15px;
}
{
: ;
: ;
-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,
course: null,
videoSrc: null,
videoPoster: null,
videoTitle: null,
welcomeBarrage: [{ text: '一大波弹幕即将来袭', color: '#ff0000', time: 1 }],
activeTab: '摘要'
},
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['updated']);
if (res['seasons'].length > 0 && res['seasons'][][]. > ) {
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[]);
}
});
1. 智能客服
设计思路: 当用户在课程详情左下角点击客服图标时跳转到本页面。
实现步骤: 在 pages/course/detail/chat/ 目录下,开发智能客服页面。
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= >提问
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 !important;
margin: 20rpx;
border-radius: 15rpx;
background-color: $bg-gray !important;
}
.van-field__control {
color: $white !important;
: 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',
isReplying: false,
scrollTop: 0
},
changeMessage: function(ev) {
this.setData({ 'message': ev.detail });
},
sendMessage: function() {
let that = this;
if (this.data.isReplying || util.isEmpty(this.data.message)) return;
this.data.steps.push({ text: `${util.dateFormat(new Date())}`, desc: this.data. });
...({ : , : });
.({ : .., : .. + , : });
wx.({
: .. + + ..,
:
}).( {
desc = that..[that..][];
(desc === ) {
that..[that..][] = desc.(, );
}
str = ().(res., { : });
str = str.(, ).();
(!str.()) {
that..[that..][] += str;
that.({ : that.. });
} {
that.({ : });
}
query = wx.().();
query.().( {
(rect) that.({ : rect. });
}).();
});
that.({ : });
},
: () {}
});