跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
JavaScriptWeChat大前端

MyLesson 微信小程序前台前端开发(一)

MyLesson 微信小程序前台前端开发,包括环境搭建、SCSS 支持、VantWeapp 集成、通用工具封装、底部导航栏定制、首页营销模块、用户登录注册、课程列表分页查询及详情展示、智能客服对接等核心功能实现。

星辰大海发布于 2026/4/6更新于 2026/10/24123 浏览
MyLesson 微信小程序前台前端开发(一)
文章目录
  • S01. 基础环境搭建
    • E01. 安装基础组件
      • 1. 样式预处理 SCSS
      • 2. 前端框架 VantWeapp
    • E02. 封装通用组件
      • 1. 封装通用工具 util
      • 2. 封装常量工具 const
      • 3. 封装请求工具 api
    • E03. 开发底部导航栏
      • 1. 开发导航栏相关页面
      • 2. 开发底部导航栏组件
      • 3. 配置导航栏切换效果
  • S02. 导航栏 - 首页
    • E01. 项目首页
    • E02. 用户登录
      • 1. 账号登录
      • 2. 手机登录
    • E03. 用户注册
  • S03. 导航栏 - 课程
    • E01. 课程列表
    • E02. 课程详情
      • 1. 智能客服

提示:ml-miniapp 是 MyLesson 项目中的前台用户交互载体,作为整个项目面向终端用户的核心入口,承担着连接系统服务与用户需求的关键桥梁作用,它基于微信小程序技术生态开发,充分依托微信平台的高渗透率、轻量化使用特性,让 MyLesson 系统的目标用户无需下载独立 App,仅通过微信即可快速访问,极大降低了用户的使用门槛,提升了系统的触达效率。

项目完整文件结构:

|_ ml-miniapp 
 |_ custom-tab-bar/*.* # 自定义底部导航栏目录:包含.wxml/.wxss/.js/.json 等文件,用于实现个性化底部导航
 |_ miniprogram_npm # 小程序专用依赖目录:存放经转换后可在小程序环境直接运行的 npm 包(自动生成)
 |_ node_modules # 原始依赖存储目录:存放通过 npm/yarn 安装的所有原始依赖包(含开发依赖与生产依赖)
 |_ pages # 页面开发目录:每个子目录对应一个页面,包含 .wxml/.wxss/.js/.json 四个文件
 |_ utils 
 |_ api.js # 请求工具文件:封装基本 CRUD 请求和拦截器等
 |_ const.js # 常量工具文件:集中管理项目常量,如 API 地址、状态码、路由路径等
 |_ util.js # 通用工具文件:存放格式化、验证、转换等通用业务工具方法
 |_ app.js # 项目入口文件:定义小程序生命周期、全局数据及全局方法
 |_ app.json # 全局配置文件:配置页面路径、窗口表现、tabBar 基础配置、网络超时等
 |_ app.scss # 全局样式文件:配置全局样式
 |_ package.json 
 |_ package-lock.json 
 |_ project.config.json 
 |_ project..config.js 
 |_ sitemap.json 
# 项目依赖配置文件
# 项目依赖配置文件(锁定版本)
# 配置小程序项目名称、appid、基础库版本、编译配置等
private
# 存放个人开发相关配置,会覆盖 project.config.json 中的同名配置
# 配置微信索引对小程序页面的收录规则,影响小程序搜索可见性

S01. 基础环境搭建

提示:微信小程序的开发过程中,个人推荐使用【微信小程序开发工具】作为模拟器,使用 IDEA 作为编辑器。

微信小程序页面组成:微信小程序的每个页面都由四个同名文件组成,比如创建一个页面为 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 块中添加对应的路径。

提示:使用微信开发者工具 + IDEA 搭建 ml-miniapp 项目基础环境。

  1. 下载微信开发者工具 wechat_devtools_xxxx_win32_x64.exe,傻瓜式安装即可,启动后需要扫码登录自己的微信账号。
  2. 创建微信小程序项目,具体选项如下:
项值备注
项目名称ml-miniapp
目录必须是一个空目录,且避免中文和特殊符号建议提前在本地创建一个 ml-miniapp 目录
AppIDxxx点击 测试号 按钮直接生成
开发模式小程序
后端服务不使用云服务
模板选择JS-基础模板
  1. 点击模拟器左上角,选择机型为 iPhone 15 Pro Max 100%,并选择显示比例为 自适应 即可:

在这里插入图片描述

  1. 左上角依次点击 设置 -> 通用设置 [ctrl + ,] -> 通用,然后修改 内存限制 为 2048M:

在这里插入图片描述

  1. 右上角依次点击 详情 -> 本地设置,然后调整 调试基础库 为当时百分比最高的版本(不一定是图示中的版本,哪个百分比高选哪个):

在这里插入图片描述

  1. 右上角依次点击 详情 -> 本地设置,然后勾选 不校验合法域名、web-view(业务域名)、TLS 版本以及 HTTPS 证书 项,以便于在开发阶段快速联调未备案域名、本地 IP 或 HTTP 接口,而无需提前配置正式域名与证书:

在这里插入图片描述

  1. 使用 IDEA 打开微信小程序项目,推荐将小程序项目作为顶层项目打开,否则某些快捷提示可能会失效,影响开发体验:
    • 调整 IDEA 的 UTF-8 编码。
    • 安装 Wechat mini program support 插件(ZXY 版本)。
    • 安装 WeChat weapp Support 插件。
  2. 在 IDEA 中,优化小程序初始代码:
    • 删除 /pages/index/index.wxml 中的全部内容(文件本身留着)。
    • 删除 /pages/index/index.wxss 中的全部内容(文件本身留着)。
    • 删除 /pages/logs 目录。
    • 优化 /pages/index/index.js 文件为 Page({ data: {} }) 代码。
    • 优化 /utils/util.js 文件为 module.exports = { } 代码。
    • 优化 /app.js 文件为 App({ globalData: {} }) 代码。
    • 优化 /app.json 文件如下:
{"pages":["pages/index/index"],"window":{"navigationBarTextStyle":"black","navigationBarTitleText":"我的课堂小程序","navigationBarBackgroundColor":"#ffffff"},"style":"v2","componentFramework":"glass-easel","sitemapLocation":"sitemap.json","lazyCodeLoading":"requiredComponents"}

E01. 安装基础组件

1. 样式预处理 SCSS

提示:创建项目时选择的 JS 基础模板默认不支持 SCSS,若有需要,则需要先在项目中添加 SCSS 支持,然后手动将每个 .wxss 文件修改为 .scss 文件。

SCSS 语法教程:参考相关 CSS 文章。

提示:在小程序项目中添加 SCSS 支持。

  1. 在 project.config.json 的 settings 块的首行添加 "useCompilerPlugins": ["sass"] 代码以添加 SCSS 支持,若已存在一个相同项,则需要将其删除:
{"setting":{"useCompilerPlugins":["sass"],...},}
  1. 将 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; // 颜色 
}
/** vant 分割线 */
.van-divider{
padding: 0 20rpx; // 内边距 
}
/** 底线 */
.baseline{
height: 200rpx; // 高度 
padding-bottom: 50rpx; // 下边距 
}
2. 前端框架 VantWeapp

提示:Vant 是一个 轻量、可靠的移动端组件库,于 2017 年开源,目前 Vant 官方提供了 Vue 2 版本、Vue 3 版本 和 微信小程序版本,并由社区团队维护 React 版本 和 支付宝小程序版本。

提示:在小程序项目中添加 SCSS 支持。

  1. 安装 VantWeapp 依赖:
npm install @vant/[email protected] --save
  1. 将 app.json 中的 "style": "v2" 去除,规避部分组件样式混乱。
  2. 在 project.config.json 文件中开启 NPM 支持:
    1. 使用 "packNpmManually": true 代码开启 NPM 支持。
    2. 使用 "packNpmRelationList" -> "packageJsonPath": "" 指定 NPM 依赖文件的位置。
    3. 使用 "packNpmRelationList" -> "miniprogramNpmDistDir": "" 指定 NPM 在哪个目录中打包。
{"setting":{...
"packNpmManually":true,
"packNpmRelationList":[{"packageJsonPath":"./package.json","miniprogramNpmDistDir":"./"}],}}
  1. 在微信小程序开发者工具的右上角依次点击 工具 -> 构建 npm,构建完成后,即可引入组件:

在这里插入图片描述

E02. 封装通用组件

1. 封装通用工具 util

提示:开发通用工具 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])){
returntrue;
}
}
returnfalse;
}
/**
 * 判断空字符串
 *
 * @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);
}
/**
 * 判断是否存在空字符串
 *
 * @param values 被判断的值,不定长列表
 * @return boolean 返回 true 表示包含 null 或 undefined 或空字符串
 */
function hasEmpty(...values){
for(let i in values){
if(isEmpty(values[i])){
returntrue;
}
}
returnfalse;
}
/**
 * 日期字符串处理:1999-01-02T12:12:12 -> 1999 年 01 月 02 日 12:12
 *
 * @param dateStr 日期字符串
 * @return string 返回格式化后的日期字符串
 */
function datetimeFormat(dateStr){
if(isNull(dateStr))return'';// 将日期字符串转为日期格式
let date =newDate(dateStr);// 获取日期中的元素:年,月,日,时,分
lettoDouble=e=> e <10?'0'+ e : e;
let yy =toDouble(date.getFullYear());
let mm =toDouble(date.getMonth()+1);
let dd =toDouble(date.getDate());
let hh =toDouble(date.getHours());
let mi =toDouble(date.getMinutes());// 返回美化后的日期字符串
return`${yy}年${mm}月${dd}日 ${hh}:${mi}`;
}
/**
 * 日期字符串处理:1999-01-02T12:12:12 -> 1999 年 01 月 02 日
 *
 * @param dateStr 日期字符串
 * @return string 返回格式化后的日期字符串
 */
function dateFormat(dateStr){
if(isNull(dateStr))return'';// 将日期字符串转为日期格式
let date =newDate(dateStr);// 获取日期中的元素:年,月,日,时,分
lettoDouble=e=> e <10?'0'+ e : e;
let yy =toDouble(date.getFullYear());
let mm =toDouble(date.getMonth()+1);
let dd =toDouble(date.getDate());// 返回美化后的日期字符串
return`${yy}年${mm}月${dd}日`;
}
/**
 * 随机生成一个 len 位的字符串
 *
 * @param len 随机字符串位数,范围在 1 ~ 36 之间,默认为 18
 * @return string 随机字符串
 * */
functionrandomStr(len =18){// 随机一个小数,并将其转为 36 进制字符并消除所有点符号
let result = Math.random().toString(36).replaceAll('.','');// 截取后 len 位字符串并返回:len 不小于 1 且不超过 36 
len = Math.max(len,1); len = Math.min(len,36);
return result.slice(-len);
}
/**
 * 随机生成一个十六进制的颜色值
 *
 * @returns {string} 十六进制的颜色值如 '#000000'
 */
functionrandomColor(){
const rgb =[]
for(let i =0; i <3;++i){
let color = Math.floor(Math.random()*256).toString(16) 
color = color.length ===1?'0'+ color : color 
rgb.push(color)
}
return'#'+ rgb.join('')
}
/**
 * 带按钮的确认弹框
 *
 * @param content 弹框内容
 * @param onConfirm 确认时的回调函数
 * @param onCancel 取消时的回调函数
 */
functionconfirm(content, onConfirm, onCancel){
 wx.showModal({
title:'提示',
content: content,
success:function(sm){
if(sm.confirm && onConfirm)onConfirm();
if(sm.cancel && onCancel)onCancel();
}});
}
/**
 * 跳转到另一个标签页
 *
 * @param url 目标页面地址
 * @param reload 是否重新加载目标页面,默认 true
 */
functiontab(url, reload =true){
 wx.switchTab({
url: url,
success:()=>{
if(reload){
let page =getCurrentPages().pop();
if(page) page.onLoad();
}});
}
/**
 * 跳转到另一个普通页
 *
 * @param url 目标页面地址
 * @param reload 是否重新加载目标页面,默认 true
 */
functionpage(url, reload =true){
 wx.navigateTo({
url: url,
success:()=>{
if(reload){
let page =getCurrentPages().pop();
if(page) page.onLoad();
}});
}
/**
 * 成功提示框(微信)
 *
 * @param title 提示文字内容,长度限制约 7 个汉字(超出会被截断)。
 */
functionsuccess(title){
 wx.showToast({
title: title,
icon:'success',
duration:2000});
}
/**
 * 失败提示框(微信)
 *
 * @param title 提示文字内容,长度限制约 7 个汉字(超出会被截断)。
 */
functionerror(title){
 wx.showToast({
title: title,
icon:'error',
duration:2000});
}
/**
 * 普通提示框(微信)
 *
 * @param title 提示内容,长度无限制
 */
functiontip(title){
 wx.showToast({
title: title,
icon:'none',
duration:2000});
}
/**
 * 模态框(微信)
 *
 * @param title 提示文字
 * @param content 提示内容
 * @param success 用户点击确定后的回调函数
 * @param cancel 用户点击取消后的回调函数
 */
functionmodal(title, content, success, cancel){
 wx.showModal({
title: title,
content: content,
success:function(res){
if(res.confirm){
if(success)success();
}else{
if(cancel)cancel();
}});
}
/**
 * 判断用户是否登录
 *
 * @returns {boolean} false 未登录,true 已登录
 */
functionisLogin(){
if(!wx.getStorageSync('token')){
error('请先登录');
setTimeout(()=>{
page('/pages/index/login-by-account/login-by-account',false);
},500);
returnfalse;
}
returntrue;
}// 导出模块 
module.exports ={ isNotNull, isNull, hasNull, isNotEmpty, isEmpty, hasEmpty, dateFormat, datetimeFormat, randomStr, randomColor, confirm, tab, page, success, error, tip, modal, isLogin }
2. 封装常量工具 const

提示:开发常量工具 utils/const.js 文件。

// 环境 IP 地址
constHOST='localhost';
constLINUX_HOST='192.168.40.77';
constGATEWAY_HOST=`http://${HOST}:24101`;
constSOCKET_SERVER=`ws://${HOST}:24107`;
constMINIO_HOST=`http://${LINUX_HOST}:9001/mylesson/`;
constMINIO_AVATAR=MINIO_HOST+'/avatar/';
constMINIO_BANNER=MINIO_HOST+'/banner/';
constMINIO_COURSE_COVER=MINIO_HOST+'/course-cover/';
constMINIO_COURSE_SUMMARY=MINIO_HOST+'/course-summary/';
constMINIO_EPISODE_VIDEO=MINIO_HOST+'/episode-video/';
constMINIO_EPISODE_VIDEO_COVER=MINIO_HOST+'/episode-video-cover/';
constUPLOAD_AVATAR_URL=GATEWAY_HOST+'/user-server/api/v1/user/uploadAvatar/';
// 常用请求状态码
constSTATUS={SUCCESS:1000}// 常用表单规则
constRULE={
TITLE:[{pattern:/^.{1,42}$/,message:'标题长度必须在 1~42 之间'}],
AUTHOR:[{pattern:/^.{1,42}$/,message:'作者名称长度必须在 1~42 之间'}],
INFO:[{pattern:/^.{1,170}$/,message:'描述长度必须在 1~170 之间'}],
CONTENT:[{pattern:/^.{1,170}$/,message:'内容长度必须在 1~170 之间'}],
VCODE:[{pattern:/^[0-9]{6}$/,message:'验证码必须是 6 位数字'}],
MENU_URL:[{pattern:/^\/ [a-zA-Z]{0,256}$/,message:'跳转地址必须以 / 开头,后续内容仅支持 0~256 个英文字母'}],
MENU_ICON:[{pattern:/^[a-zA-Z]{1,256}$/,message:'图标仅支持 1~256 个英文字母'}],
USERNAME:[{pattern:/^[a-zA-Z0-9]{4,20}$/,message:'账号必须由 4 到 20 个英文字母或数字组成'}],
PASSWORD:[{pattern:/^[a-zA-Z0-9]{4,20}$/,message:'密码必须由 4 到 20 个英文字母或数字组成'}],
REALNAME:[{pattern:/^[\u4e00-\u9fa5]{2,6}$/,message:'真实姓名必须由 2 到 6 个中文组成'}],
NICKNAME:[{pattern:/^[\u4e00-\u9fa5|_a-zA-Z0-9]{2,10}$/,message:'昵称必须由 2 到 10 个中文、英文或数字组成'}],
IDCARD:[{pattern:/^[1-9]\d{5}(19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/',message:'身份证号格式不正确'}],
PHONE:[{pattern:/^1(3[0-9]|4[01456879]|5[0-35-9]|6[2567]|7[0-8]|8[0-9]|9[0-35-9])\d{8}$/,message:'手机号码格式不正确'}],
EMAIL:[{pattern:/^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/,message:'电子邮箱格式不正确'}],
PROVINCE:[{pattern:/^[\u4e00-\u9fa5]{2,20}$/,message:'省份必须由 2 到 20 个中文组成'}],
CODE:[{pattern:/^.{1,42}$/,message:'兑换口令长度必须在 1~42 之间'}],
SN:[{pattern:/^.{1,42}$/,message:'订单编号长度必须在 1~42 之间'}],
}// 项目大标题
constPROJECT_TITLE='绝对精品课 - 我的课堂';
// 项目小标题
constPROJECT_SUB_TITLE='Welcome To Lesson Project';
// 省份选项
constPROVINCE_OPTIONS=[
{id:0,text:"华北地区",children:[{text:"北京市",id:"北京市"},{text:"天津市",id:"天津市"},{text:"河北省",id:"河北省"},{text:"山西省",id:"山西省"},{text:"内蒙古自治区",id:"内蒙古自治区"}]},
{id:1,text:"东北地区",children:[{text:"辽宁省",id:"辽宁省"},{text:"吉林省",id:"吉林省"},{text:"黑龙江省",id:"黑龙江省"}]},
{id:2,text:"华东地区",children:[{text:"上海市",id:"上海市"},{text:"江苏省",id:"江苏省"},{text:"浙江省",id:"浙江省"},{text:"安徽省",id:"安徽省"},{text:"福建省",id:"福建省"},{text:"江西省",id:"江西省"},{text:"山东省",id:"山东省"}]},
{id:3,text:"中南地区",children:[{text:"河南省",id:"河南省"},{text:"湖北省",id:"湖北省"},{text:"湖南省",id:"湖南省"},{text:"广东省",id:"广东省"},{text:"广西壮族自治区",id:"广西壮族自治区"},{text:"海南省",id:"海南省"}]},
{id:4,text:"西南地区",children:[{text:"重庆市",id:"重庆市"},{text:"四川省",id:"四川省"},{text:"贵州省",id:"贵州省"},{text:"云南省",id:"云南省"},{text:"西藏自治区",id:"西藏自治区"}]},
{id:5,text:"西北地区",children:[{text:"陕西省",id:"陕西省"},{text:"甘肃省",id:"甘肃省"},{text:"青海省",id:"青海省"},{text:"宁夏回族自治区",id:"宁夏回族自治区"},{text:"新疆维吾尔自治区",id:"新疆维吾尔自治区"}]},
{id:6,text:"港澳台地区",children:[{text:"台湾省",id:"台湾省"},{text:"香港特别行政区",id:"香港特别行政区"},{text:"澳门特别行政区",id:"澳门特别行政区"}]}]
// 星座选项
constZODIAC_OPTIONS=['白羊座(Aries)','金牛座(Taurus)','双子座(Gemini)','巨蟹座(Cancer)','狮子座(Leo)','处女座(Virgo)','天秤座(Libra)','天蝎座(Scorpio)','射手座(Sagittarius)','摩羯座(Capricorn)','水瓶座(Aquarius)','双鱼座(Pisces)'];
// 导出 module.exports ={GATEWAY_HOST,SOCKET_SERVER,STATUS,RULE,MINIO_AVATAR,MINIO_BANNER,MINIO_COURSE_COVER,MINIO_COURSE_SUMMARY,MINIO_EPISODE_VIDEO,MINIO_EPISODE_VIDEO_COVER,UPLOAD_AVATAR_URL,PROJECT_TITLE,PROJECT_SUB_TITLE,PROVINCE_OPTIONS,ZODIAC_OPTIONS}
3. 封装请求工具 api

提示:开发常量工具 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 等,末尾无 / 符号
 * */
functionapiPrefixFormat(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}`;
}
/**
 * (私有)请求方法
 *
 * <p> module: 模块名称,如 user,必传。
 * <p> url: 请求地址,如 /selectById,必传。
 * <p> method: 请求方式,默认 GET。
 * <p> header: 请求头,默认 application/json 类型并自动携带 Token 令牌。
 * <p> params: API 请求函数参数,JSON 格式,作为请求参数。
 *
 * @returns Promise 函数
 */
functionsendRequest(config){// 必传参数(需要空值保护)
let module = config['module'];
let url = config['url'];
if(util.hasNull(module, url))return;// 可选参数(需要设置默认值)
let method = config['method'];
let params = config['params'];
let header = config['header'];
if(util.isEmpty(method)) method ='GET';
if(util.isNull(header)){
let token = wx.getStorageSync('token')||null; header ={'Content-Type':'application/json','token': token};
}// 处理 url 参数:添加对应的网关路由名称 
url = constant.GATEWAY_HOST+apiPrefixFormat(module)+ url;// 发送请求
returnnewPromise((resolve, reject)=>{
 wx.request({
url: url,
method: method,
data: params,
header: header,
success(res){
if(util.isNull(res)) util.error('服务器无响应');// 若存在 2 层 data,则直接拆除第 1 层 data 
res =undefined!== res.data &&undefined!== res.data['data']? res.data : res;// 请求成功,返回 data 数据
if(res['code']=== constant.STATUS.SUCCESS){// DQL 操作成功时返回查询到的数据,DML 操作成功时返回 trueresolve(util.isNotNull(res.data)? res.data :true);
}// 请求失败 - 服务器异常
else{ util.error(res['message']); console.error(res['coderMessage']);// TODO 生产环境下删除}},
// 请求失败
fail(err){reject('请求异常:'+ err);
}});
});
}
/**
 * 发送 GET 请求
 *
 * @param module 模块名称,如 user 等
 * @param url 请求地址,如 /selectById 等
 * @param params API 请求函数参数,JSON 格式,默认 null
 * @returns {Promise<unknown>}
 */
functionget(module, url, params =null){
returnsendRequest({module, url, params});
}
/**
 * 发送 POST 请求
 *
 * @param module 模块名称,如 user 等
 * @param url 请求地址,如 /selectById 等
 * @param params API 请求函数参数,JSON 格式,默认 null
 * @returns {Promise<unknown>}
 */
functionpost(module, url, params =null){
returnsendRequest({module, url, params,method:'POST'});
}
/**
 * 发送 PUT 请求
 *
 * @param module 模块名称,如 user 等
 * @param url 请求地址,如 /selectById 等
 * @param params API 请求函数参数,JSON 格式,默认 null
 * @returns {Promise<unknown>}
 */
functionput(module, url, params =null){
returnsendRequest({module, url, params,method:'PUT'});
}
/**
 * 发送 DEL 请求
 *
 * @param module 模块名称,如 user 等
 * @param url 请求地址,如 /selectById 等,默认 null
 * @param params API 请求函数参数,JSON 格式
 * @returns {Promise<unknown>}
 */
functiondel(module, url, params =null){
returnsendRequest({module, url, params,method:'DELETE'});
}// 导出 module.exports ={get, post, put, del};

E03. 开发底部导航栏

提示:微信小程序的底部支持自定义导航栏。

在这里插入图片描述

提示:开发小程序的底部导航栏。

1. 开发导航栏相关页面

提示:通过右键 New -> Wechat Mini program Page 的方式创建'首页 + 课程 + 购物车 + 我的'四个选项卡相关的页面(其中首页 index 是项目本身就存在的)。

  1. 开发 /pages/index/index 页面(json + wxml + scss + js)。
  2. 开发 /pages/course/course 页面(json + wxml + scss + js)。
  3. 开发 /pages/cart/cart 页面(json + wxml + scss + js)。
  4. 开发 /pages/user/user 页面(json + wxml + scss + js)。
2. 开发底部导航栏组件

提示:微信小程序自定义的底部导航栏需要自行创建,且注意目录名称,组件名称和位置均固定,不可随意更改。

提示:在项目根目录开发底部导航栏组件(自定义)。

  1. 在项目根目录创建 custom-tab-bar 目录,然后右键 New -> Wechat Mini program Component 创建 index 组件:
|_ ml-miniapp 
 |_ custom-tab-bar 
 |_ index.js 
 |_ index.json 
 |_ index.scss 
 |_ index.wxml 
 ... 
  1. 开发 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:

<viewcustom-class="tab-bar"><van-tabbaractive="{{activeTab}}"bind:change="changeTab"><van-tabbar-itemwx: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);
}}});
  1. 在 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-stickyclass="notice-bar"><van-notice-bartext="{{currentNotice}}"left-icon="volume-o"/></van-sticky><viewclass="login-tips"wx:if="{{isLogin}}"> 游客部分功能受限,点我 <textbind:tap="toLogin"class="link">登录系统</text>! </view><viewclass="project-title"><viewclass="title">{{PROJECT_TITLE}}</view><viewclass="sub-title">{{PROJECT_SUB_TITLE}}</view></view><viewclass="banner"><swiperwx:if="{{banners}}"autoplay="3000"><swiper-itemwx:for="{{banners}}"wx:key="id"><van-imagesrc="{{MINIO_BANNER}}/{{item['url']}}"width="100%"height="150px"fit="fill"/></swiper-item></swiper><van-emptywx:elsecustom-class="no-banner-tip"image="error"description="暂无轮播图片"/></view><viewclass="article"><van-dividercontentPosition="center"dashed>公告列表</van-divider><van-collapsecustom-class="article-collapse"value="{{ currentArticleIdx }}"bind:change="changeArticle"accordion><van-collapse-itemwx:for="{{articles}}"title="{{item['title']}}"name="{{item['idx']}}"wx:key="title"icon="fire-o"> {{item['content']}} </van-collapse-item></van-collapse></view><viewclass="seckill"><van-dividercontentPosition="center"dashed>整点秒杀</van-divider><van-tabswx:if="{{seckills && seckills.length > 0}}"active="{{ activeSeckillIdx }}"bind:change="changeSeckill"><van-tabwx:for="{{seckills}}"title="{{seckill['title']}}"wx:for-item="seckill"wx:key="id"><viewwx:if="{{seckill['seckillDetails'].length > 0}}"><van-cardwx:for="{{seckill['seckillDetails']}}"wx:key="id"title="{{item['courseTitle']}}"price="{{item['skPrice']}}"origin-price="{{item['coursePrice']}}"thumb="{{MINIO}}{{item['courseCover']}}"thumb-mode="scaleToFill"tag="秒杀"desc="描述信息"><viewslot="footer"><viewclass="sk-btn sk-btn-0"wx:if="{{seckill['status'] === 0}}">未开始</view><viewclass="sk-btn sk-btn-1"wx:if="{{seckill['status'] === 1}}">秒杀中</view><viewclass="sk-btn sk-btn-2"wx:if="{{seckill['status'] === 2}}">已结束</view></view></van-card></view><viewwx:else><van-emptyimage="error"description="暂无秒杀商品"/></view></van-tab></van-tabs><viewwx:else><van-emptyimage="error"description="暂无秒杀活动"/></view></view><viewclass="baseline"><van-dividercontentPosition="center"dashed>底线</van-divider></view>

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; // 字体粗细 
letter-spacing: 5rpx; // 字间距 
}
.article-collapse{
margin: 20rpx; // 外边距 
}
.van-collapse-item__content{
color: $black; // 字体颜色 
font-size: 25rpx; // 字体大小 
}}
.seckill{
padding: 0 20rpx;// 内边距 
.van-tab{
background: $bg-gray; // 背景颜色 
color: $white; // 字体颜色 
}
.van-tab--active{
background: $black;// 背景颜色 
.van-ellipsis{
color: $yellow; // 字体颜色 
}}}
.van-card{
color: $white; // 字体颜色 
background: $bg-gray; // 背景颜色 
border: 2rpx solid $black; // 边框圆角 
margin: 10rpx 0 0; // 外边距 
}
.van-card__content{
padding-left: 30rpx; // 内边距 
text-align: left; // 文本对齐方式 
}
.van-card__title{
padding-top: 20rpx; // 内边距 
font-size: large; // 字体大小 
margin-bottom: 10rpx; // 外边距 
color: $yellow; // 字体颜色 
}
.van-card__price{
color: $orange; // 前景色 
}
.van-card__thumb{
margin-top: 20rpx; // 外边距 
width: 220rpx; // 宽度 
}
.sk-btn{
width: 170rpx; // 宽度 
height: 70rpx; // 高度 
line-height: 70rpx; // 行高 
text-align: center; // 文本对齐方式 
font-size: large; // 字体大小 
margin-top: -70px; // 外边距 
float: right; // 浮动 
rotate: -25deg; // 旋转 
border-top-left-radius: 50%; // 边框圆角 
border-bottom-right-radius: 50%; // 边框圆角 
}
.sk-btn-0, .sk-btn-2{
opacity: 0.6; // 透明度 
background-color: $subBlack; // 背景颜色 
color: $white; // 字体颜色 
}
.sk-btn-1{
background-color: $yellow; // 背景颜色 
color: $orange; // 字体颜色 
}}

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:function(){
let that =this; api.get('notice','/top/1').then(res=> that.setData({currentNotice: res[0]['content']})).catch(err=> console.error(err));},// 查询 5 条轮播图记录
topBanner5:function(){
let that =this; api.get('banner','/top/5').then(res=> that.setData({banners: res})).catch(err=> console.error(err));},// 查询 5 条通知记录
topArticle5:function(){
let that =this; api.get('article','/top/5').then(res=> that.setData({articles: res})).catch(err=> console.error(err));},// 查询今日的秒杀活动
todaySeckill:function(){
let that =this; api.get('seckill','/today').then(res=> that.setData({seckills: res})).catch(err=> console.error(err));},// 切换公告
changeArticle:function(ev){this.setData({'currentArticleIdx': ev.detail});},// 切换秒杀活动
changeSeckill:function(ev){this.setData({'activeSeckillIdx': ev.detail});},// 加载函数
onLoad:function(){
this.setData({isLogin:!wx.getStorageSync('token')});
this.topNotice1();
this.topBanner5();
this.topArticle5();
this.todaySeckill();
this.getTabBar().setData({"activeTab":0});
},});

E02. 用户登录

1. 账号登录

提示:账号登录页面用于通过账号和密码进行系统登录,登录成功在前端保存用户信息和 Token 令牌,并会跳回首页。

页面要素:位置自上而下:

页面要素描述
账号输入框采集登录账号,测试环境下固定为 admin,开发环境下置空
密码输入框采集登录密码,测试环境下固定为 123456789,开发环境下置空
登录按钮向后台请求登录,提交账号和密码,登录成功后在前端保存用户信息和 Token 令牌
注册新账号按钮跳转到注册页面
手机号码登录按钮跳转到通过手机号码登录的页面
返回首页按钮跳转到首页选项卡
加载函数在前端清空用户信息和 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:

<viewclass="login-by-account-board"><van-fieldlabel="登录账号"model:value="{{username}}"custom-class="field"right-icon="user-o"border="{{false}}"clearable="{{true}}"placeholder="请输入账号"/><van-fieldlabel="登录密码"model:value="{{password}}"custom-class="field"type="password"right-icon="eye-o"border="{{false}}"clearable="{{true}}"placeholder="请输入密码"/><van-buttoncustom-class="login-btn"type="info"blockbind:tap="loginByAccount">登录</van-button><van-rowcustom-class="link"><van-colspan="8"bind:tap="toRegister">注册新账号</van-col><van-colspan="8"bind:tap="toLoginByPhone">手机号码登录</van-col><van-colspan="8"bind:tap="toIndex">返回首页</van-col></van-row></view>

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]['message']);return;}// 发送登录请求 
api.post('user','/loginByAccount',{username, password}).then(res=>{// 将用户信息以及对应的 Token 令牌存储起来 
wx.setStorageSync('token', res['token']); wx.setStorageSync('user', res['user']); util.success('登录成功');// 0.5 秒后切换回 "首页" 选项卡
setTimeout(()=> util.tab('/pages/index/index'),500);}).catch(err=> console.error(err));},// 跳转到注册页面
toRegister:function(){ util.page('/pages/index/register/register',false);},// 跳转到手机登录页面
toLoginByPhone:function(){ util.page('/pages/index/login-by-phone/login-by-phone',false);},// 返回首页
toIndex:function(){ util.tab('/pages/index/index');},// 加载函数
onLoad:function(options){ wx.removeStorageSync('token'); wx.removeStorageSync('user');}});
2. 手机登录

提示:手机登录页面用于通过手机号码和验证码进行系统登录:用户输入手机号码,然后点击'发送验证码'按钮,获取到一个验证码(测试环境下直接返回给前端并自动填充,生产环境下给对应手机号码发送短信)。用户输入验证码,点击登录按钮,向后台请求登录,提交手机号码和验证码。登录成功后在前端保存用户信息和 Token 令牌,并跳回首页。

页面要素:位置自上而下:

页面要素描述
手机号码输入框采集手机号码,测试环境下固定为 17766541438,开发环境下置空
短信验证码输入框采集验证码
发送验证码按钮向后台请求一个验证码,并自动填充到短信验证码输入框中
登录按钮向后台请求登录,提交手机号码和验证码,登录成功后在前端保存用户信息和 Token 令牌
注册新账号按钮跳转到注册页面
账号密码登录按钮跳转到通过账号密码登录的页面
返回首页按钮跳转到首页选项卡
加载函数在前端清空用户信息和 Token 令牌

图示如下:

在这里插入图片描述

提示:在 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:

<viewclass="login-by-phone-board"><van-fieldlabel="手机号码"model:value="{{phone}}"custom-class="field"right-icon="user-o"border="{{false}}"clearable="{{true}}"placeholder="请输入手机号码"/><van-fieldlabel="短信验证码"model:value="{{vcode}}"custom-class="field"centeruse-button-slotborder="{{false}}"clearable="{{true}}"placeholder="请输入短信验证码"><van-buttonbind:tap="getVcode"slot="button"size="small"type="primary">发送验证码</van-button></van-field><van-buttoncustom-class="login-btn"bind:tap="loginByPhone"type="info"block>登录</van-button><van-rowcustom-class="link"><van-colspan="8"bind:tap="toRegister">注册新账号</van-col><van-colspan="8"bind:tap="toLoginByAccount">账号密码登录</van-col><van-colspan="8"bind:tap="toIndex">返回首页</van-col></van-row></view>

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=> console.error(err));},// 根据手机号码和验证码登录系统
loginByPhone:function(){
let phone =this.data.phone;
let vcode =this.data.vcode;// 空值校验
if(util.hasEmpty(phone, vcode)){ util.tip('手机号码或验证码不能为空');return;}// 校验手机号码规则
if(!constant.RULE.PHONE[0]['pattern'].test(phone)){ util.tip(constant.RULE.PHONE[0]['message']);return;}// 发送登录请求 
api.post('user','/loginByPhone',{phone, vcode}).then(res=>{// 将用户信息以及对应的 Token 令牌存储起来 
wx.setStorageSync('token', res['token']); wx.setStorageSync('user', res['user']); util.success('登录成功');// 0.5 秒后切换到 "首页" 选项卡
setTimeout(()=> util.tab('/pages/index/index'),500);}).catch(err=> console.error(err));},// 跳转到注册页面
toRegister:function(){ util.page('/pages/index/register/register',false);},// 跳转到账号登录页面
toLoginByAccount:function(){ util.page('/pages/index/login-by-account/login-by-account',false);},// 返回首页
toIndex:function(){ util.tab('/pages/index/index');},// 加载函数
onLoad:function(options){ wx.removeStorageSync('token'); wx.removeStorageSync('user');}});

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:

<viewclass="register-board"><van-fieldlabel="登录账号"model:value="{{ username }}"custom-class="field"right-icon="user-o"placeholder="请输入登录账号"clearable="{{true}}"border="{{ false }}"/><van-fieldlabel="登录密码"model:value="{{ password }}"type="password"custom-class="field"right-icon="eye-o"placeholder="请输入登录密码"clearable="{{true}}"border="{{ false }}"/><van-fieldlabel="确认密码"model:value="{{ rePassword }}"type="password"custom-class="field"right-icon="eye-o"placeholder="请确认登录密码"clearable="{{true}}"border="{{ false }}"/><van-fieldlabel="真实姓名"model:value="{{ realname }}"custom-class="field"right-icon="contact-o"placeholder="请输入真实姓名"clearable="{{true}}"border="{{ false }}"/><van-fieldlabel="手机号码"model:value="{{ phone }}"custom-class="field"right-icon="phone-o"placeholder="请输入手机号码"clearable="{{true}}"border="{{ false }}"/><van-fieldlabel="身份证号"model:value="{{ idcard }}"custom-class="field"right-icon="user-circle-o"placeholder="请输入身份证号"clearable="{{true}}"border="{{ false }}"/><van-fieldlabel="电子邮箱"model:value="{{ email }}"custom-class="field"right-icon="envelop-o"placeholder="请输入电子邮箱"clearable="{{true}}"border="{{ false }}"/><van-buttoncustom-class="register-btn"bind:tap="register"type="info"block>注册</van-button><van-rowcustom-class="link"><van-colspan="8"bind:tap="toLoginByAccount">账号密码登录</van-col><van-colspan="8"bind:tap="toLoginByPhone">手机号码登录</van-col><van-colspan="8"bind:tap="toIndex">返回首页</van-col></van-row></view>

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;// 检验两次密码是否一致
if(password !== rePassword){ 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]['message']);return;}// 校验真实姓名规则
if(!constant.RULE.REALNAME[0]['pattern'].test(realname)){ util.tip(constant.RULE.REALNAME[0]['message']);return;}// 校验电话号码规则
if(!constant.RULE.PHONE[0]['pattern'].test(phone)){ util.tip(constant.RULE.PHONE[0]['message']);return;}// 校验身份证号规则
if(!constant.RULE.IDCARD[0]['pattern'].test(idcard)){ util.tip(constant.RULE.IDCARD[0]['message']);return;}// 校验电子邮箱规则
if(!constant.RULE.EMAIL[0]['pattern'].test(email)){ util.tip(constant.RULE.EMAIL[0]['message']);return;}// 发送登录请求
let param ={username, password, realname, phone, idcard, email}; api.post('user','/insert', param).then(res=>{ util.success('注册成功');// 0.5 秒后切换到登录页面
setTimeout(()=>{ util.page('/pages/index/login-by-account/login-by-account',false);},500);}).catch(err=> console.error(err));},// 跳转到账号登录页面
toLoginByAccount:function(){ util.page('/pages/index/login-by-account/login-by-account',false);},// 跳转到手机登录页面
toLoginByPhone:function(){ util.page('/pages/index/login-by-phone/login-by-phone',false);},// 返回首页
toIndex:function(){ util.tab('/pages/index/index');},// 加载函数
onLoad:function(options){}});

S03. 导航栏 - 课程

提示:本模块包含课程列表页面和课程详情页面,这两个页面均不需要用户登录即可访问。

课程列表:课程列表是导航页面,当用户点击底部导航栏'课程'按钮时进行分页展示全部课程列表。

课程详情:当点击课程列表中的某个具体课程封面时,跳转到对应该课程的详情页面。

E01. 课程列表

提示:当用户点击底部选项卡'课程'的时候,跳转到课程列表页面,页面加载后,向后台请求课程列表(ElasticSearch 数据库)并渲染到页面,出于性能考虑,页面默认显示 12 条课程数据,当滚动条触底时,进行下一次分页查询(再查 12 条)。

页面要素:位置自上而下:

页面要素描述
搜索框用户可通过课程名称或者作者名称进行分词搜索
课程列表向后台请求课程列表(ElasticSearch 数据库)并渲染到页面

图示如下:

在这里插入图片描述

提示:在 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-searchclass="search-ipt"value="{{keyword}}"bind:search="searchByKeyword"bind:clear="cancelSearch"background="#252a34"shape="round"clear-trigger="always"input-align="center"placeholder="请输入课程标题进行搜索"/></van-sticky><scroll-viewclass="course-list"bindscrolltolower="onListEnd"scroll-y="true"><van-dividercontentPosition="center"dashed>课程列表</van-divider><van-gridclass="course-list-grid"column-num="2"gutter="20rpx"><van-grid-itemcontent-class="course-item"wx:for="{{courses}}"wx:key="id"data-course-id="{{item['id']}}"bind:tap="showDetail"use-slot><van-imagesrc="{{MINIO_COURSE_COVER}}/{{item['cover']}}"width="300rpx"height="200rpx"/><viewclass="title">{{ item['title'] }}</view><viewclass="info"><view><van-iconname="user-o"/> 作者: {{item['author']}} </view><view><van-iconname="gold-coin-o"/> 价格: {{item['price']}} 元 </view></view></van-grid-item></van-grid><van-dividercontentPosition="center"dashed>没有更多课程了</van-divider></scroll-view><viewclass="baseline"><van-dividercontentPosition="center"dashed>底线</van-divider></view>

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;// 搜索关键字不能超过 42 个字
if(keyword.length >42){ util.error('搜索关键字过长');return;}// 请求数据
let params ={pageNum, pageSize,keyword: keyword.trim()}; api.get('course','/search', params).then(res=>{ that.setData({'courses': pageNum ===1? res['records']: that.data.courses.concat(res['records']),'pageInfo.pageNum': res['pageNum'],'pageInfo.pageSize': res['pageSize'],'pageInfo.totalPage': res['totalPage'],'pageInfo.totalRow': res['totalRow'],});}).catch(err=> console.error(err));},// 列表触底时
onListEnd:function(){
let that =this;
let pageNum = that.data.pageInfo['pageNum'];
let totalPage = that.data.pageInfo['totalPage'];
if(pageNum < totalPage){// 查询下一页数据
this.setData({'pageInfo.pageNum': pageNum +1});
this.page();
},// 按课程标题搜索课程
searchByKeyword:function(ev){this.setData({'keyword': ev.detail,'pageInfo.pageNum':1,});this.page();},// 取消搜索
cancelSearch:function(ev){this.setData({'keyword':'','pageInfo.pageNum':1,});this.page();},// 查看课程详情
showDetail:function(ev){
let courseId = ev.currentTarget.dataset['courseId']; util.page('/pages/course/detail/detail?courseId='+ courseId,false);},// 加载函数
onLoad:function(options){
this.page();
this.getTabBar().setData({"activeTab":1});
}});

E02. 课程详情

提示:当用户在课程列表界面点击某个课程封面时,会跳转到对应的课程详情页面。

页面要素:位置自上而下:

页面要素描述
视频组件点击可播放当前课程的第一章第一集的视频内容
课程详情列表展示当前课程的详情,如类别,标题,作者,价格等
课程摘要选项卡,点击可展示课程摘要图片
课程目录选项卡,点击可展示课程章集目录
客服按钮点击跳入 AI 客服页面
购物车按钮点击跳入购物车选项卡
加入购物车按钮点击将当前课程加入我的购物车列表
立即购买按钮点击立刻购买该课程

图示如下:

在这里插入图片描述|196

在这里插入图片描述

提示:在 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-stickyclass="free-video"><videowx:if="{{videoSrc && videoPoster}}"src="{{videoSrc}}"title="{{videoTitle}}"poster="{{videoPoster}}"danmu-list="{{welcomeBarrage}}"controls"show-mute-btn"enable-danmudanmu-btn/><van-emptyclass="free-video-empty"wx:elseimage="error"description="暂无免费视频"/></van-sticky><viewclass="course-info"wx:if="{{course}}"><van-dividercontentPosition="center"dashed>课程详情</van-divider><van-cell-groupinset><van-celltitle="课程类别"value="{{course['category']['title']}}"icon="flag-o"/><van-celltitle="课程标题"value="{{course['title']}}"icon="notes-o"/><van-celltitle="课程作者"value="{{course['author']}}"icon="user-o"/><van-celltitle="创建日期"value="{{course['created']}}"icon="clock-o"/><van-celltitle="更新日期"value="{{course['updated']}}"icon="clock-o"/><van-celltitle="课程价格"value="{{course['price'] + ' 元'}}"icon="cash-o"/><van-celltitle="课程简介"value="{{course['info']}}"icon="font-o"/></van-cell-group></view><viewclass="operation-tabs"><van-tabsactive="{{activeTab}}"color="orange"type="card"animatedtab-class="tab"><van-tabtitle="课程摘要"name="摘要"><viewclass="summary"wx:if="{{course['summary']}}"><van-imagesrc="{{MINIO_COURSE_SUMMARY}}{{course['summary']}}"width="100%"fit="widthFix"/></view><van-emptyclass="summary-empty"wx:elseimage="error"description="暂无摘要图片"/><viewclass="baseline"><van-dividercontentPosition="center"dashed>底线</van-divider></view></van-tab><van-tabtitle="课程目录"name="目录"><viewclass="catalog"wx:if="{{course['seasons'].length > 0}}"><viewwx:for="{{course['seasons']}}"wx:key="id"wx:for-item="season"wx:for-index="si"><van-cell-grouptitle="{{'第' + (si + 1) + '季:' + season['title']}}"><viewwx:if="{{season['episodes'].length > 0}}"><van-cellwx:for="{{season['episodes']}}"wx:key="id"wx:for-item="episode"wx:for-index="ei"title="{{'第' + (ei + 1) + '集:' + episode['title']}}"use-label-slot><viewslot="label"class="van-multi-ellipsis--l2">{{episode['info']}}</view></van-cell></view></van-cell-group></view><viewclass="baseline"><van-dividercontentPosition="center"dashed>底线</van-divider></view></view><van-emptyclass="catalog-empty"wx:elseimage="error"description="该课程暂无季次信息,请联系运维人员。"/></van-tab></van-tabs></view><viewclass="detail-foot"><van-goods-action><van-goods-action-icontext="客服"bind:click="chatMe"icon="chat-o"/><van-goods-action-icontext="购物车"bind:click="toCart"icon="cart-o"/><van-goods-action-buttontext="加入购物车"bind:click="addToCart"type="warning"/><van-goods-action-buttontext="立即购买"bind:click="pay"/></van-goods-action></view>

detail.scss:

@import"app";
.free-video{
video{
width: 98%; // 宽度 
outine: 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; // 内边距 
}
.van-cell{
color: $white; // 文字颜色 
background: $black; // 背景色 
text-align: left !important; // 文字对齐方式 
}}}

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['updated']);
if(res['seasons'].length >0&& res['seasons'][0]['episodes'].length >0){
let firstEpisode = res['seasons'][0]['episodes'][0]; that.setData({'videoSrc': constant.MINIO_EPISODE_VIDEO+ firstEpisode['video'],'videoPoster': constant.MINIO_EPISODE_VIDEO_COVER+ firstEpisode['cover'],'videoTitle': firstEpisode['title'],});
}
that.setData({'course': res});}).catch(err=> console.error(err));},// 跳转到购物车页面
toCart:function(){
if(util.isLogin()){ util.tab('/pages/cart/cart');}},// 添加购物车
addToCart:function(){
if(util.isLogin()){
let that =this;
let params ={'fkUserId': wx.getStorageSync('user').id,"fkCourseId": that.data.course['id'],}; api.post('cart','/insert', params).then(res=>{ util.error('加购成功');
setTimeout(()=>{ util.tab('/pages/cart/cart',true);},500);}).catch(err=> console.error(err))}},// 客服
chatMe:function(){ util.page('/pages/course/detail/chat/chat')},// 立即购买
pay:function(){
if(util.isLogin()){ util.error('功能暂未开放');}},// 加载函数
onLoad:function(ev){// 查询视频详情:获取路径传递过来的值
this.getCourseInfo(ev['courseId']);
}});
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-viewclass="chat-board"scroll-top="{{scrollTop}}"scroll-y="{{true}}"><van-stepscustom-class="chat-dialog"id="chatDialog"steps="{{ steps }}"active="{{ active }}"direction="vertical"active-color="#f9ed69"desc-class="desc"/></scroll-view><viewclass="tips">{{isReplying ? '客服回复中...' : '回复完毕'}}</view><van-fieldcustom-class="chat-textarea"value="{{ message }}"bindinput="changeMessage"type="textarea"maxlength="{{500}}"show-word-limit="{{true}}"autosizeborder="{{false}}"placeholder="请输入你的问题"/><van-buttoncustom-class="submit-btn"bind:tap="sendMessage"type="info"block>提问</van-button>

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; // 字体颜色 
height: 100rpx !important; // 高度 
}
.submit-btn{
width: auto !important; // 宽度 
margin: 20rpx; // 外边距
;font-size: large; // 字体大小 
border-radius: 15rpx; // 圆角 
}

chat.js:

import util from"../../../../utils/util.js";
Page({
data:{message:'一首李白的诗',// 用户输入的问题
active:0,// 当前激活的步骤
steps:[{text:`${util.dateFormat(newDate())}`,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.push({text:`${util.dateFormat(newDate())}`,desc:this.data.message });// 加入 AI 回复的信息
this.data.steps.push({text:`${util.dateFormat(newDate())}`,desc:'waiting...'});// 更新聊天框
this.setData({steps:this.data.steps,active:this.data.active +2,isReplying:true});// 发送请求并接收分块响应的消息 
wx.request({
url:this.data.sseServerUrl +'?msg='+this.data.message,
enableChunked:true// 启用分块传输
}).onChunkReceived((res)=>{// 获取当前 AI 回复的信息
let desc = that.data.steps[that.data.active]['desc'];;// 如果当前 AI 回复的信息是 "waiting...",则移除它
if(desc ==='waiting...'){ that.data.steps[that.data.active]['desc']= desc.replaceAll('waiting...','');}// AI 回复的信息类型固定为 arrayBuffer,需要开发者自己进行转换为字符串
let str =newTextDecoder('utf-8').decode(res.data,{stream:true});// 移除 "data:" 
str = str.replaceAll('data:','').trim();// 如果不是 "[over]",则拼接 AI 回复的信息
if(!str.includes('[over]')){ that.data.steps[that.data.active]['desc']+= str;// 更新 that.setData({steps: that.data.steps});}// 否则,AI 回复完毕
else{// 更新 that.setData({isReplying:false});}// 滚动到底部
const query = wx.createSelectorQuery().in(this); query.select('#chatDialog').boundingClientRect((rect)=>{
if(rect)this.setData({scrollTop: rect.height});
}).exec();
}// 清空用户输入的内容 that.setData({'message':''});},
onLoad:function(options){}});

目录

  1. 文章目录
  2. S01. 基础环境搭建
  3. E01. 安装基础组件
  4. 1. 样式预处理 SCSS
  5. 2. 前端框架 VantWeapp
  6. E02. 封装通用组件
  7. 1. 封装通用工具 util
  8. 2. 封装常量工具 const
  9. 3. 封装请求工具 api
  10. E03. 开发底部导航栏
  11. 1. 开发导航栏相关页面
  12. 2. 开发底部导航栏组件
  13. 3. 配置导航栏切换效果
  14. S02. 导航栏 - 首页
  15. E01. 项目首页
  16. E02. 用户登录
  17. 1. 账号登录
  18. 2. 手机登录
  19. E03. 用户注册
  20. S03. 导航栏 - 课程
  21. E01. 课程列表
  22. E02. 课程详情
  23. 1. 智能客服

更多推荐文章

查看全部
  • VSCode Copilot 配置文件提示未知工具警告解析
  • CentOS 7 安装 JDK 1.8 及修复 wget 缺失问题
  • Google NotebookLM:基于文档的 AI 研究与笔记助手
  • Python JSON Logger 完整指南:如何实现结构化日志记录
  • Java 遍历 Map 的常用方式与性能对比
  • WebPShop 插件实战指南:Photoshop 原生支持 WebP 格式
  • 前端网页开发学习路径:HTML+CSS+JS
  • Python ORM 框架:SQLAlchemy 完全指南
  • DFS 算法详解:求解数组子集问题
  • Stable Diffusion 底模 VAE 推荐与配置策略
  • C++十大核心应用领域:技术栈与代码示例详解
  • Qt 布局管理器详解:QVBoxLayout、QHBoxLayout、QGridLayout、QFormLayout
  • 红黑树深入剖析及 Java 实现
  • MCP 插件配置指南:以 browser-tools-mcp 为例
  • 利用 Anthropic Skill 提升大模型前端设计审美
  • ChatGPT 实用技巧:文本与数据的结构化方法解析
  • 宇树科技机器人核心技术
  • Python 布尔类型详解:真值规则与逻辑运算实战
  • 论文解读:利用人类反馈训练语言模型遵循指令
  • 1060 爱丁顿数 Python 实现与解析

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online