(一)uni-app 官方文档open in new window
1.简介 一个使用 Vue.js 开发所有前端应用的框架 开发者编写一套代码,即可发布到 iOS、Android、H5、以及各种小程序(微信/支付宝/百度/头条/QQ/钉钉/淘宝)、快应用等多个平台
2.形成 移动端技术太多,跨端框架或是未来发展趋势 一套代码多端发布受开发者青睐 完整的生态,受企业青睐 3.优势
4.和 Vue 的关系 使用 VueJS 开发 在发布到 H5 时,支持所有 vue 语法 发布到 App 和小程序时,实现部分 Vue 语法 5.和小程序的关系 组件标签靠近小程序规范 接口能力(JS API)靠近微信小程序开发 完整的小程序生命周期 6.和 Web 代码编写的区别
(二)uni-app 实战项目介绍 1.项目演示 2.测试账号 3.项目结构分析 1)首页 2)关注 3)我的 4)注册登录模块 5)项目整体流程 页面构建 数据处理 逻辑实现 适配发行 多平台适配 多平台打包 多平台发布(安卓、IOS、小程序、h5) (三)微信小程序简介 1.文档相关 2.开发者工具 1)必选项处理
2)appID 获取
3.小程序代码构成 参考文档open in new window
.json
后缀的 JSON
配置文件.wxml
后缀的 WXML
模板文件.wxss
后缀的 WXSS
样式文件.js
后缀的 JS
脚本逻辑文件 4.小程序基本结构 < view class = " container" >
< view class = " userinfo" >
< button wx: if= " {{!hasUserInfo && canIUse}}" > 获取头像昵称</ button>
< block wx: else>
< image src = " {{userInfo.avatarUrl}}" background-size = " cover" > </ image>
< text class = " userinfo-nickname" > {{userInfo.nickName}}</ text>
</ block>
</ view>
< view class = " usermotto" >
< text class = " user-motto" > {{motto}}</ text>
</ view>
</ view>
5.小程序基本操作 1)配置信息 {
"pages" : [ "pages/index/index" , "pages/logs/index" ] ,
"window" : {
"navigationBarTitleText" : "Demo"
} ,
"tabBar" : {
"list" : [
{
"pagePath" : "pages/index/index" ,
"text" : "首页"
} ,
{
"pagePath" : "pages/logs/index" ,
"text" : "日志"
}
]
} ,
"networkTimeout" : {
"request" : 10000 ,
"downloadFile" : 10000
} ,
"debug" : true
}
{
"navigationBarBackgroundColor" : "#ffffff" ,
"navigationBarTextStyle" : "black" ,
"navigationBarTitleText" : "微信接口功能演示" ,
"backgroundColor" : "#eeeeee" ,
"backgroundTextStyle" : "light"
}
2)生命周期
onLaunch : function ( ) {
} ,
onShow : function ( options ) {
} ,
onHide : function ( ) {
} ,
onError : function ( msg ) {
}
onLoad : function ( options ) {
} ,
onShow : function ( ) {
} ,
onReady : function ( ) {
} ,
onHide : function ( ) {
} ,
onUnload : function ( ) {
} ,
onPullDownRefresh : function ( ) {
} ,
onReachBottom : function ( ) {
} ,
onShareAppMessage : function ( ) {
} ,
onPageScroll : function ( ) {
} ,
onResize : function ( ) {
}
Component ( {
lifetimes : {
created ( ) {
console. log ( "created,组件实例刚刚被创建好时, created 生命周期被触发" ) ;
} ,
attached ( ) {
console. log ( "组件实例进入页面节点树时执行" ) ;
} ,
detached ( ) {
console. log ( "在组件实例被从页面节点树移除时执行" ) ;
} ,
} ,
} ) ;
3)页面跳转 < navigator open-type = " navigateTo" />
< navigator open-type = " redirectTo" />
页面返回 调用 API: wx.navigateBack 用户按左上角返回按钮 < navigator open-type = " navigateBack" />
Tab 切换 调用 API: wx.switchTab 用户切换 Tab < navigator open-type = " switchTab" />
< navigator open-type = " reLaunch" />
4)数据绑定 Page ( {
data : {
message : "hello world" ,
} ,
} ) ;
5)条件渲染 < view wx: if= " {{isShow}}" > 条件判断显示</ view>
Page({
data:{
isShow:false
}
})
6)列表渲染 < view wx: for= " {{list}}" wx: for-index= " idx" wx: for-item= " itemName" > {{idx}}: {{itemName.name}}</ view>
Page ( {
data : {
list : [ { name : "a" } , { name : "b" } ] ,
} ,
} ) ;
(四)uni-app 环境搭建 1.uni-app 开发规范 < template>
< view> 页面内容</ view>
</ template>
< script>
export default {
data ( ) {
return { } ;
} ,
methods : { } ,
} ;
</ script>
< style> </ style>
uni. getStorageInfoSync ( ) ;
< template>
< view @click = " onClickFn" > 点击事件绑定</ view>
</ template>
< script>
export default {
methods : {
onClickFn ( ) {
console. log ( "click事件" ) ;
} ,
} ,
} ;
</ script>
< style lang = " scss" scoped > </ style>
2.uni-app 开发环境 1)开发工具 uni-app 官方推荐使用 HBuilderX 来开发 uni-app 类型的项目 优点 2)下载 HBuilderX 详细安装文档open in new window
3.工程搭建
4.基本目录结构 目录名/文件名 作用 【pages】 内部存放所有页面 【static】 存放所有静态资源,比如图片,字体图标 【unpackage】 存放所有打包生成后的文件 app.vue 应用配置,用来配置 App 全局样式以及监听应用生命周期 main.js Vue 初始化入口文件 manifest.json 配置应用名称、appid、logo、版本等打包信息 pages.json 配置页面路由、导航条、选项卡等页面类信息 uni.scss 用途是为了方便整体控制应用的风格。比如按钮颜色、边框风格,uni.scss 文件里预置了一批 scss 变量预置
5.项目运行 1)浏览器运行
2)小程序运行
在 HBuilderX 中,配置“微信开发者工具”的安装路径
在微信开发者工具中,通过设置-安全设置面板,开启“微信开发者工具”的服务端口
在 HBuilderX 中,点击菜单栏中的运行-运行到小程序模拟器-微信开发者工具,将当前 uni-app 项目编译之后,自动运行到微信开发者工具中,从而方便查看项目效果与调试
3)app 真机运行 确保手机与电脑是在同一个局域网下
手机开启开发者模式 选择数据管理 HBuilderX 选择真机运行 等待基座安装 安装完成手机运行项目 4)iOS 模拟器运行 (五)uni-app 基础 API 参考文档open in new window
1.基础组件 组件演示open in new window
基础组件在 uni-app 框架中已经内置 无需将内置组件的文件导入项目,也无需注册内置组件 随时可以直接使用,比如 <view>
组件 1)视图容器 2)基础内容 组件 作用 icon 图标,uni-icons text 文字,无法进行标签文本字符串解析 rich-text 文字,使用 nodes 属性可以解析标签文本字符串 progress 进度条
组件 作用 button 按钮 checkbox 多项选择器 editor 富文本输入框 form 表单 input 输入框 label 标签 picker 弹出式聊表选择器 picker-view 窗体内嵌入式聊表选择器 radio 单项选择器 slider 滑动选择器 switch 开关选择器 textarea 多行文本输入框
4)路由与页面跳转(Navigation) 组件 作用 navigator 页面链接,类似于 html 中的 a 标签
5)媒体组件 组件 作用 audio 音频 camera 相机 image 图片 video 视频
6)组件公共属性集合
特殊属性以 v-
开头,称为 vue 指令 如 v-if、v-else、v-for、v-model 2.扩展组件 参考文档open in new window
3.自定义组件 Components 文件夹下定义组件 页面引用组件,无需导入,直接使用即可 其他操作(组件传值,事件绑定)同 Vue 4.基础 API 参考文档open in new window
1)网络请求 参考文档open in new window
解决 uni.request 网络请求 API 相对简单的问题 可使用 @escook/request-miniprogram
处理网路请求 在小程序中,无法使用 fetch 及 axios 发送网络请求 测试接口地址:https://study.duyiedu.com/api/herolist 2)上传和下载 3)图片处理 4)数据缓存 参考链接open in new window
API 作用 uni.getStorage 异步获取本地数据缓存 uni.getStorageSync 同步获取本地数据缓存 uni.setStorage 异步设置本地数据缓存 uni.setStorageSync 同步设置本地数据缓存 uni.removeStorage 异步删除本地数据缓存 uni.removeStorageSync 同步删除本地数据缓存
5)交互反馈 参考链接open in new window
API 作用 uni.showToast 显示提示框 uni.showLoading 显示加载提示框 uni.hideToast 隐藏提示框 uni.hideLoading 隐藏加载提示框 uni.showModal 显示模态框 uni.showActionSheet 显示菜单列表
6)路由 API 作用 uni.navigateTo 保留当前页面,跳转到应用内某个页面 使用 uni.navigateBack 返回原页面 uni.redirectTo 关闭当前页面,跳转到应用内的某个页面 uni.reLaunch 关闭所有页面,打开应用内的某个页面 uni.switchTab 跳转到 tab Bar 页面
5.页面布局相关 1)page page {
height : 100%;
background-color : red;
}
2)尺寸单位 3)外部样式文件引入 6.uni-app 生命周期 参考文档open in new window
1)应用生命周期 只能在 App.vue 里面监听,在其他页面监听无效 钩子 作用 onLaunch 初始化完成时触发,全局只触发一次 onShow uni-app 启动,或从后台进入前台显示时触发 onHide uni-app 应用从前台进入后台时触发
2)页面生命周期 钩子 作用 onLoad 监听页面加载 可获取上个页面传递的参数 onShow 监听页面显示,每次出现在屏幕上都触发 onReady 监听页面初次渲染完成 onHide 监听页面隐藏 onUnload 监听页面卸载 onReachBottom 页面滚动到底部事件
3)组件生命周期 钩子 作用 beforeCreate 组件创建前 created 组件创建后,组件挂载前 beforeMount 组件挂载前 mounted 组件挂载后 beforeDestroy 组件销毁前 destroyed 组件销毁后
7.uni-app 特色 1)条件编译 官方文档open in new window
条件编译是用特殊的注释作为标记 在编译时根据这些特殊的注释,将注释里面的代码编译到不同平台 以 #ifdef
或 #ifndef
加 %PLATFORM%
开头,以 #endif
结尾 写法 说明 #ifdef
if defined,仅在某平台存在 #ifndef
if not defined,除了某平台均存在 %PLATFORM%
平台名称
值 生效条件 VUE3 HBuilderX 3.2.0+(uni-app js 引擎版)open in new window APP App APP-PLUS App(uni-app js 引擎版) APP-PLUS-NVUE 或 APP-NVUE App nvue 页面 APP-ANDROID App Android 平台 仅限 uts 文件 APP-IOS App iOS 平台 仅限 uts 文件 UNI-APP-X HBuilderX 3.9.0+(uni-app x 项目) H5 H5 MP-WEIXIN 微信小程序 MP-ALIPAY 支付宝小程序 MP-BAIDU 百度小程序 MP-TOUTIAO 抖音小程序 MP-LARK 飞书小程序 MP-QQ QQ 小程序 MP-KUAISHOU 快手小程序 MP-JD 京东小程序 MP-360 360 小程序 MP 微信小程序/支付宝小程序/百度小程序/抖音小程序/飞书小程序/QQ 小程序/360 小程序 QUICKAPP-WEBVIEW 快应用通用(包含联盟、华为) QUICKAPP-WEBVIEW-UNION 快应用联盟 QUICKAPP-WEBVIEW-HUAWEI 快应用华为
条件编译支持的文件 .vue .js .css pages.json 各预编译语言文件 条件编译是利用注释实现的,在不同语法里注释写法不一样 js 使用 // 注释
css 使用 /* 注释 */
vue/nvue 模板里使用 <!-- 注释 -->
; 2)插件安装 (六)uniCloud 基础配置 1.HBuilderX 中使用 uniCloud 云开发平台 uniCloud 参考文档open in new window
Web 控制台文档open in new window
1)传统业务开发流程 2)使用 uniCloud 云开发平台 3)简介 uniCloud 是 DCloud 联合阿里云、腾讯云,为开发者提供的基于 serverless 模式和 js 编程的实现后端服务的云开发平台 不需要服务器的购买配置即可快速创建一个完整的后端服务 4)优点 用 JavaScript 开发前后台整体业务 非 H5 项目免域名使用服务器 敏捷性业务处理,不需要前后端分离开发 2.开发流程
1)云函数
2)云数据库
3)云存储及 CDN 参考文档open in new window
3.创建云函数工程 1)指定 uniCloud 工程创建
2)保证 uni-app 应用标识 appID 填写
3)创建云服务空间 如果未实名认证,会跳转至实名认证页面认证 等待实名认证审核之后可以开通服务空间 若腾讯云实名认证提示身份证下已创建过多账户,则需要在腾讯云官网注销不用的账户
4)创建云函数
"use strict" ;
exports. main = async ( event, context ) => {
console. log ( "event : " , event) ;
return {
code : 0 ,
msg : "云函数调用成功" ,
} ;
} ;
5)云 WEB 控制台查看
6)云数据库操作 7)云存储 8)跨域处理 参考文档open in new window
(七)uniCloud API 操作 1.云函数调用 参考文档open in new window
1)promise 方式 uniCloud
. callFunction ( {
name : "test" ,
data : {
a : 1 ,
} ,
} )
. then ( ( res ) => { } ) ;
2)callback 方式 uniCloud. callFunction ( {
name : "test" ,
data : {
a : 1 ,
} ,
success ( ) { } ,
fail ( ) { } ,
complete ( ) { } ,
} ) ;
2.云函数实现云数据库基本增删改查 1)获取数据库引用 const db = uniCloud. database ( ) ;
2)获取数据表集合引用 const collection = db. collection ( "unicloud-test-714" ) ;
3)新增记录 const res = collection. add ( {
user : "alan" ,
} ) ;
"use strict" ;
const db = uniCloud. database ( ) ;
exports. main = async ( event, context ) => {
const collection = db. collection ( "unicloud-test-714" ) ;
const res = await collection. add ( {
user : "alan" ,
} ) ;
console. log ( res) ;
return {
code : 0 ,
msg : "云函数调用成功" ,
} ;
} ;
4)删除记录 const res = await collection. doc ( "60ee51103b7d3500014124c1" ) . remove ( ) ;
5)数据更新 const res = await collection. doc ( "60ee52a1827eca0001e56bc4" ) . update ( {
name : "joob" ,
} ) ;
const res = await collection. doc ( "60ee52a1827eca0001e56bc4" ) . set ( {
name : "joob" ,
type : "javascript" ,
} ) ;
update 与 set 的区别
当没有找到指定记录时,使用 update 无法更新 当没有找到指定记录时,使用 set 可以新增内容 6)数据查找
const res = await collection. get ( ) ;
const res = await collection. doc ( "id" ) . get ( ) ;
const res = await collection
. where ( {
name : "alan" ,
} )
. get ( ) ;
3.云存储操作 1)图片选择获取 参考文档open in new window
uni. chooseImage ( {
count : 1 ,
success ( res ) {
console. log ( JSON . stringify ( res. tempFilePaths) ) ;
} ,
} ) ;
2)文件上传 参考文档open in new window
uni. chooseImage ( {
count : 1 ,
async success ( res ) {
let result = await uniCloud. uploadFile ( {
filePath : res. tempFilePaths[ 0 ] ,
cloudPath : "a.jpg" ,
success ( res ) {
console. log ( res) ;
} ,
fail ( err ) {
console. log ( err) ;
} ,
} ) ;
} ,
} ) ;
3)图片删除 参考文档open in new window
使用 uniCloud.deleteFile 阿里云函数删除不能在客户端删除,应该在云函数中删除 let result = await uniCloud. deleteFile ( {
fileList : [ "https://vkceyugu.cdn.bspapp.com/VKCEYUGU-6ce25980-c28e-4e78-bdef-a96eb40ad98b/06a1cb3a-84b7-47a0-b554-8aff299cb255.jpg" ] ,
} ) ;
console. log ( result) ;
(八)项目搭建 1.初始化数据库 1)定义(选择)云服务空间 2)初始化数据库 参考文档open in new window
使用 db_init.json 文件 初始化 db_init.json 文件 source 文件夹 => db_init.json 文件 uniCloud 目录找到 database 目录,添加 db_init.json 文件
2.静态文件配置 1)导入 static 文件 导入项目中需要的图片文件到 static/img/
目录下 2)定义 css 预编译处理器 uni.scss 文件定义公共变量及混编方法 每个页面下直接使用样式方法及变量 @mixin flex ( $level_style : space-between, $vertical_style : row, $isWrapper : nowrap) {
display : flex;
align-items : center;
justify-content : $level_style ;
flex-wrap : $isWrapper ;
flex-direction : $vertical_style ;
}
$base-color : #f25037;
* {
box-sizing : border-box;
& ::before,
& ::after {
box-sizing : border-box;
}
}
3)创建 tabBar 参考文档open in new window
在 pages
目录中,创建首页(Home)、我的(Self)、关注(Follow) 3 个 tabBar 页面 在 HBuilderX 中快速新建页面 在 pages
目录上鼠标右键,选择 新建页面 在弹出的窗口中, 勾选 scss 模板 、填写 页面的名称 之后,点击创建按钮 配置 tabBar 效果,修改项目根目录中的 pages.json
配置文件,新增 tabBar
的配置节点 "tabBar" : {
"color" : "#666" ,
"selectedColor" : "#f25037" ,
"backgroundColor" : "#fff" ,
"list" : [ {
"pagePath" : "pages/Home/Home" ,
"iconPath" : "static/img/home.png" ,
"selectedIconPath" : "static/img/home-active.png" ,
"text" : "首页"
} ,
{
"pagePath" : "pages/Follow/Follow" ,
"iconPath" : "static/img/follow.png" ,
"selectedIconPath" : "static/img/follow-active.png" ,
"text" : "关注"
} ,
{
"pagePath" : "pages/Self/Self" ,
"iconPath" : "static/img/my.png" ,
"selectedIconPath" : "static/img/my-active.png" ,
"text" : "我的"
}
]
}
"globalStyle" : {
"navigationBarTextStyle" : "white" ,
"navigationBarTitleText" : "渡一教育" ,
"navigationBarBackgroundColor" : "#f25037" ,
"backgroundColor" : "#F8F8F8"
}
4)创建 tabBar 需要的页面文件 Home.vue Follow.vue Self.vue 5)pages.json 设置前景色 "navigationBarTextStyle" : "white"
(九)制作导航栏并适配多端 1.定义导航栏组件 NavBar 2.引入导航栏组件 同名组件支持 easycom 规范 不需要使用 import 导入 特点:局部引入 easyCom components/组件名/组件名.vue
3.搭建结构 uni-icons 图标插件open in new window
4.处理小程序显示错位问题 参考文档open in new window
const systemInfo = uni. getSystemInfoSync ( ) ;
this . statusBarHeight = systemInfo. statusBarHeight ? systemInfo. statusBarHeight * 2 : 20 ;
5.获取胶囊信息 参考文档open in new window
const menuButtonInfo = uni. getMenuButtonBoundingClientRect ( ) ;
this . marginRight = menuButtonInfo. width * 2 + 50 ;
this . statusBarHeight = menuButtonInfo. top * 2 ;
this . navBarHeight = menuButtonInfo. bottom - info. statusBarHeight + ( menuButtonInfo. top - info. statusBarHeight) ;
(十)制作选项卡
1.创建组件 定义组件 TabBar.vue 在 Home.vue 中引入 参考文档open in new window
使用 scroll-view
横向滚动的时候,内部需添加一个容器包裹滚动内容 < scroll-view class = " tab-scroll" scroll-x = " true" >
< view class = " tab-scroll-box" >
< view
v-for = " (item, index) in labelList"
:class = " { active: activeIndex === index }"
:key = " index"
@click = " handleClickTabBarItem(index)"
class = " tab-scroll-item"
>
{{ item.name }}
</ view>
</ view>
</ scroll-view>
3.点击设置按 icon 跳转到设置标签页面 < view class = " tab-icons" >
< uni-icons @click = " toLabelEditPage" type = " gear" size = " 26" color = " #666" > </ uni-icons>
</ view>
toLabelEditPage ( ) {
uni. navigateTo ( {
url : '/pages/LabelEdit/LabelEdit'
} ) ;
}
4.数据获取 在 Home.vue 的 onLoad 生命周期内定义 getLabelList
方法 onLoad ( ) {
this . getLabelList ( ) ;
}
"use strict" ;
const db = uniCloud. database ( ) ;
exports. main = async ( event, context ) => {
const collection = db. collection ( "label" ) ;
const res = await collection. get ( ) ;
return {
code : 0 ,
data : res. data,
} ;
} ;
Home.vue 获取数据,并将数据传递到 TabBar 组件 < TabBar :labelList = " labelList" > </ TabBar>
使用 uniCloud.callFunction 方法获取数据 getLabelList ( ) {
uniCloud. callFunction ( {
name : 'getLabelList' ,
success : ( res ) => {
this . labelList = res. result. data;
}
} ) ;
}
TabBar 组件内部使用 props 属性获取数据 props : {
labelList : {
type : Array,
default : ( ) => [ ]
}
}
(十一)封装请求方法 1.定义公共的 http 请求方法 创建 http.js 文件,导出一个封装好的 promise 对象 内部调用 uniCloud export default ( { name, data = { } } ) => {
return new Promise ( ( resolve, reject ) => {
uni. showLoading ( { } ) ;
uniCloud. callFunction ( {
name,
data,
success ( { result } ) {
if ( result. code === 0 ) {
resolve ( result. data) ;
} else {
uni. showToast ( {
icon : "error" ,
title : result. msg,
} ) ;
}
} ,
fail ( err ) {
reject ( err) ;
} ,
complete ( ) {
uni. hideLoading ( ) ;
} ,
} ) ;
} ) ;
} ;
2.创建接口文件调用公共方法 ajax/api/interface/home.js import ajax from "../../http.js" ;
export const _getLabelList = ( data ) =>
ajax ( {
name : "getLabelList" ,
data,
} ) ;
3.将 http 方法挂载到 Vue 原型上 供每个页面使用 使用 Webpack 的 require.context
方法对所有的请求函数收集 通过执行 require.context()
获取一个特定的上下文 主要用来实现自动化导入模块 在前端工程中,如果遇到从一个文件夹引入很多模块的情况,可以使用这个 API 会遍历文件夹中的指定文件,然后自动导入,不需要每次显式调用 import 导入模块 ajax/api/index.js
const requireApi = require. context ( "." , true , / .js$ / ) ;
let modules = { } ;
requireApi. keys ( ) . forEach ( ( key, index ) => {
if ( key === "./index.js" ) return ;
Object. assign ( modules, requireApi ( key) ) ;
} ) ;
export default modules;
import modules from "./ajax/api/index.js" ;
Vue . prototype. $http = modules;
4.页面/组件内部调用方法 async getLabelList ( ) {
const res = await this . $http. getLabelList ( ) ;
this . labelList = res;
}
(十二)制作文章列表 1.容器组件 1)定义 ArticleList 组件 使用 swiper 组件open in new window 实现滚动效果 SwiperItem 数量动态变化,当前的 SwiperItem 数量应该与选项卡的数量相同 获取选项卡的数量,根据选项卡数量渲染 SwiperItem Home.vue 传递 labelList 和 activeIndex < ArticleList :labelList = " labelList" :activeIndex = " activeIndex" @changeActiveIndex = " handleChangeActiveIndex" > </ ArticleList>
ArticleList 内根据 labelList 数量渲染 SwiperItem < swiper class = " swiper-container" >
< swiper-item v-for = " (item, index) in labelList" :key = " index" >
< view class = " swiper-item uni-bg-red" >
< ListItem :item = " item" > </ ListItem>
</ view>
</ swiper-item>
</ swiper>
2)实现选项卡与 Swiper 组件联动效果 绑定选项卡点击事件 发送事件,调整 activeIndex 值,将 activeIndex 值调整为父组件传递的值 Swiper 指定 current 属性为 currentIndex 观察 activeIndex 的变化,同步变化 currentIndex 监听 Swiper 的 change 事件,触发父组件改变 activeIndex 属性 < swiper :current = " currentIndex" @change = " changeActiveIndex" class = " swiper-container" >
< swiper-item v-for = " (item, index) in labelList" :key = " index" >
< view class = " swiper-item uni-bg-red" >
< ListItem :item = " item" > </ ListItem>
</ view>
</ swiper-item>
</ swiper>
data ( ) {
return {
currentIndex : 0
} ;
} ,
watch : {
activeIndex ( newVal ) {
this . currentIndex = newVal;
}
} ,
methods : {
changeActiveIndex ( e ) {
const idx = e. detail. current;
this . $emit ( 'changeActiveIndex' , idx) ;
}
}
3)选项卡自动调整位置 < scroll-view :scroll-with-animation = " true" :scroll-into-view = " `item${activeIndex}`" class = " tab-scroll" scroll-x = " true" >
< view class = " tab-scroll-box" >
< view
v-for = " (item, index) in labelList"
:class = " { active: activeIndex === index }"
:key = " index"
:id = " `item${index}`"
@click = " handleClickTabBarItem(index)"
class = " tab-scroll-item"
>
{{ item.name }}
</ view>
</ view>
</ scroll-view>
2.文章卡片 1)创建文章组件 ListItem < view class = " list-scroll-container" >
< scroll-view scroll-y = " true" class = " list-scroll" >
< view>
< ListCard v-for = " item in 50" :key = " item" > </ ListCard>
</ view>
</ scroll-view>
</ view>
.list-scroll-container {
height : 100%;
.list-scroll {
overflow : hidden;
height : 100%;
}
}
2)定义文章卡片组件 ListCard 创建基本样式及结构 定义多状态卡片模块 通过父组件传递 mode
属性 根据 mode 展示相应类型的卡片 3)定义 uni-app 模板 需要使用自定义模板的场景 调整页面 head 中的 meta 配置 补充 SEO 相关的一些配置(仅首页) 加入百度统计等三方 js 参考文档open in new window
Referrer 是 HTTP 请求头中的一部分,用于指示浏览器从哪个页面跳转到了当前页面 Referrer-Policy 是一个新的 HTTP 头,用于控制浏览器如何发送 Referrer 信息 never 表示浏览器永远不会发送 Referrer 信息,即使用户从其他页面跳转到当前页面 可以提高用户的隐私保护,防止敏感信息泄露 解决请求某些第三方网站资源 403 错误
< meta name = " referrer" content = " never" />
根目录下创建 index.html 文件 manifest 文件的 Web 配置中 index 模板路径使用 index.html 文件 <! DOCTYPE html >
< html lang = " zh-CN" >
< head>
< meta charset = " utf-8" />
< meta http-equiv = " X-UA-Compatible" content = " IE=edge" />
< meta name = " viewport" content = " width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
< meta name = " referrer" content = " never" />
< title> <%= htmlWebpackPlugin.options.title %></ title>
< link rel = " stylesheet" href = " <%= BASE_URL %>static/index.<%= VUE_APP_INDEX_CSS_HASH %>.css" />
</ head>
< body>
< noscript>
< strong> Please enable JavaScript to continue.</ strong>
</ noscript>
< div id = " app" > </ div>
</ body>
</ html>
3.数据渲染 1)创建云函数 定义云函数 getArticleList 删除不需要返回的文章内容 content "use strict" ;
const db = uniCloud. database ( ) ;
exports. main = async ( event, context ) => {
const res = await db
. collection ( "article" )
. aggregate ( )
. project ( {
content : 0 ,
} )
. end ( ) ;
return {
code : 0 ,
msg : "数据请求成功" ,
data : res. data,
} ;
} ;
export const getArticleList = ( data ) =>
ajax ( {
name : "getArticleList" ,
data,
} ) ;
2)前端获取数据 created ( ) {
this . getArticleList ( ) ;
} ,
methods : {
async getArticleList ( ) {
const res = await this . $http. getArticleList ( ) ;
this . articleList = res;
}
}
3)数据渲染 < view class = " list-scroll-container" >
< scroll-view scroll-y = " true" class = " list-scroll" >
< view>
< ListCard v-for = " article in articleList" :article = " article" :key = " article._id" > </ ListCard>
</ view>
</ scroll-view>
</ view>
4)根据选项卡分类渲染数据 async getLabelList ( ) {
const res = await this . $http. getLabelList ( ) ;
this . labelList = [
{
name : '全部'
} ,
... res
] ;
}
请求 ArticleList 时传递数据 为了保证导航数据的正确获取,调整 created 函数的 getArticleList
方法在 watch 中调用
watch : {
activeIndex ( newVal ) {
this . currentIndex = newVal;
} ,
labelList ( ) {
this . getArticleList ( ) ;
}
}
"use strict" ;
const db = uniCloud. database ( ) ;
exports. main = async ( event, context ) => {
const { classify } = event;
let match = { } ;
if ( classify !== "全部" ) {
match = {
classify,
} ;
}
const res = await db
. collection ( "article" )
. aggregate ( )
. match ( match)
. project ( {
content : 0 ,
} )
. end ( ) ;
return {
code : 0 ,
msg : "数据请求成功" ,
data : res. data,
} ;
} ;
前端缓存数据 将数组形式的文章列表转换存储为对象形式 每次 change 切换卡片内容时判断是否有缓存数据 使用 $set
方法重新渲染页面 data ( ) {
return {
currentIndex : 0 ,
articleData : { }
} ;
} ,
watch : {
activeIndex ( newVal ) {
this . currentIndex = newVal;
} ,
labelList ( ) {
this . getArticleList ( this . currentIndex) ;
}
} ,
methods : {
changeActiveIndex ( e ) {
const idx = e. detail. current;
this . $emit ( 'changeActiveIndex' , idx) ;
if ( ! this . articleData[ idx] || this . articleData[ idx] . length === 0 ) {
this . getArticleList ( idx) ;
}
} ,
async getArticleList ( idx ) {
const res = await this . $http. getArticleList ( {
classify : this . labelList[ idx] . name
} ) ;
this . $set ( this . articleData, idx, res) ;
}
}
4.上拉加载更多 1)ListItem 组件使用 uni-load-more 插件 下载地址open in new window
< uni-load-more v-if = " articleList.length === 0 || articleList.length > 5" :status = " loadData.loading" > </ uni-load-more>
props : {
articleList : {
type : Array,
default : ( ) => [ ]
} ,
loadData : {
type : Object,
default : ( ) => ( {
loading : 'loading'
} )
}
}
2)修改参数传递 云函数添加 page 及 pageSize 属性 云函数内限制返回值 "use strict" ;
const db = uniCloud. database ( ) ;
exports. main = async ( event, context ) => {
const { classify, page = 1 , pageSize = 10 } = event;
let match = { } ;
if ( classify !== "全部" ) {
match = {
classify,
} ;
}
const res = await db
. collection ( "article" )
. aggregate ( )
. match ( match)
. project ( {
content : 0 ,
} )
. skip ( pageSize * ( page - 1 ) )
. limit ( pageSize)
. end ( ) ;
const { total } = await db. collection ( "article" ) . where ( match) . count ( ) ;
return {
code : 0 ,
msg : "数据请求成功" ,
data : {
data : res. data,
total,
} ,
} ;
} ;
子组件监听 scroll-view 的 scrolltolower
事件 触底时,通知父组件根据当前的 page 值请求新的数据 handleLoadMoreArticle ( ) {
this . page++ ;
this . getArticleList ( this . currentIndex) ;
}
async getArticleList ( idx ) {
const { data : res, total } = await this . $http. getArticleList ( {
classify : this . labelList[ idx] . name,
page : this . page,
pageSize : this . pageSize
} ) ;
let oldList = this . articleData[ idx] || [ ] ;
oldList. push ( ... res) ;
this . $set ( this . articleData, idx, oldList) ;
}
3)分类页数处理 创建每一个分类的存储对象 存储当前请求的 page 和数据加载状态 async getArticleList ( idx ) {
if ( ! this . loadData[ idx] ) {
this . loadData[ idx] = {
page : 1 ,
loading : 'loading' ,
total : 0
} ;
}
const { data : res, total } = await this . $http. getArticleList ( {
classify : this . labelList[ idx] . name,
page : this . loadData[ idx] . page,
pageSize : this . pageSize
} ) ;
let oldList = this . articleData[ idx] || [ ] ;
oldList. push ( ... res) ;
this . loadData[ idx] . total = total;
this . $set ( this . articleData, idx, oldList) ;
}
handleLoadMoreArticle ( ) {
if ( this . loadData[ this . currentIndex] . total === this . articleData[ this . currentIndex] . length) {
this . loadData[ this . currentIndex] = {
... this . loadData[ this . currentIndex] ,
... {
loading : 'noMore' ,
page : this . loadData[ this . currentIndex] . page
}
} ;
this . $forceUpdate ( ) ;
return ;
}
this . loadData[ this . currentIndex] . page++ ;
this . getArticleList ( this . currentIndex) ;
}
(十三)用户登录 1.搭建登录页面 下载地址open in new window
2)封装 SendCode 组件 < template>
< view class = " code-container" >
< view @click = " getForm" class = " vCode-btn" > {{ runTime ? `${time}秒后重新获取` : '获取验证码' }}</ view>
</ view>
</ template>
beforeDestroy ( ) {
clearInterval ( this . timeId) ;
this . timeId = null ;
this . runTime = false ;
this . time = 60 ;
}
2.表单验证 1)创建 userRulesMixin 文件 common 文件夹下创建 userRulesMixin export default {
install ( Vue ) {
Vue. mixin ( {
data ( ) {
return {
userRules : {
loginName : {
rules : [
{
required : true ,
errorMessage : "账户名不能为空" ,
} ,
{
validateFunction : this . validateLoginName,
} ,
] ,
} ,
password : {
rules : [
{
required : true ,
errorMessage : "密码不能为空" ,
} ,
] ,
} ,
phone : {
rules : [
{
required : true ,
errorMessage : "电话号码不能为空" ,
} ,
{
validateFunction : this . validatePhone,
} ,
] ,
} ,
vCode : {
rules : [
{
required : true ,
errorMessage : "验证码不能为空" ,
} ,
] ,
} ,
} ,
mobileReg : / ^(0|86|17951)?(13[0-9]|15[012356789]|166|17[3678]|18[0-9]|14[57])[0-9]{8}$ / ,
} ;
} ,
methods : {
validateLoginName ( rule, val, data, callback ) {
switch ( true ) {
case val. length < 6 :
callback ( "用户名长度不正确" ) ;
break ;
default :
return true ;
}
} ,
validatePhone ( rule, val, data, callback ) {
switch ( true ) {
case ! this . mobileReg. test ( val) :
callback ( "手机号码格式不正确" ) ;
break ;
default :
return true ;
}
} ,
} ,
} ) ;
} ,
} ;
2)main.js 中使用 mixin 文件 import userRulesMixin from "./common/userRulesMixin.js" ;
Vue. use ( userRulesMixin) ;
3)组件或页面内直接使用 < uni-forms :modelValue = " formData" :rules = " userRules" class = " form" ref = " form" > </ uni-forms>
onLoad ( ) {
console. log ( this . test ( ) ) ;
}
验证手机号码的正则表达式
/^(0|86|17951)?(13[0-9]|15[012356789]|166|17[3678]|18[0-9]|14[57])[0-9]{8}$/
3.账户名密码登录 1)前端数据整理 定义发送函数,将用户信息和本次请求的用户登录类型传递给后端 async handleSubmitLogin ( ) {
const res = await this . $refs. form. validate ( ) ;
this . sendUserInfo ( {
... res,
type : this . type
} ) ;
}
import ajax from "../../http.js" ;
export const sendUserInfo = ( data ) =>
ajax ( {
name : "sendUserInfo" ,
data,
} ) ;
"use strict" ;
const db = new uniCloud. database ( ) ;
exports. main = async ( event, context ) => {
const { loginName, password, phone, type } = event;
const { affectedDocs, data } = await db
. collection ( "user" )
. aggregate ( )
. match (
type === "account"
? {
loginName,
password,
}
: {
phone,
} ,
)
. end ( ) ;
return affectedDocs
? {
code : 0 ,
msg : "获取用户信息成功" ,
data : data[ 0 ] ,
}
: {
code : 1 ,
msg : type === "account" ? "获取用户信息失败,请检查用户名或密码" : "验证码或手机号码错误" ,
} ;
} ;
前端接收返回信息,处理数据 跳转到上一个历史记录 使用 uni-app 内置的 Vuex 存储用户信息 2)使用 Vuex 存储用户信息 import Vue from "vue" ;
import Vuex from "vuex" ;
import state from "./state.js" ;
import mutations from "./mutations.js" ;
Vue. use ( Vuex) ;
export default new Vuex. Store ( {
state,
mutations,
} ) ;
export default {
userInfo : uni. getStorageSync ( "userInfo" ) || null ,
} ;
export default {
updateUserInfo ( state, userInfo ) {
uni. setStorageSync ( "userInfo" , userInfo) ;
state. userInfo = userInfo;
} ,
} ;
import store from "./store/index.js" ;
App. mpType = "app" ;
const app = new Vue ( {
... App,
store,
} ) ;
import { mapMutations } from "vuex" ;
export default {
methods : {
... mapMutations ( [ "updateUserInfo" ] ) ,
async handleSendUserInfo ( userInfo ) {
const res = await this . $http. sendUserInfo ( userInfo) ;
if ( res) {
this . updateUserInfo ( res) ;
uni. showToast ( {
icon : "success" ,
title : "登录成功" ,
} ) ;
setTimeout ( ( ) => {
uni. switchTab ( {
url : "/pages/Home/Home" ,
} ) ;
uni. navigateBack ( ) ;
} , 2000 ) ;
}
} ,
} ,
} ;
4.手机验证码登录 1)处理小程序兼容问题 小程序不兼容 :rules="userRules"
的写法 < SendCode @getFormRefs = " handleGetFormRefs" @setCurrentCode = " returnCode = $event" class = " code-component" > </ SendCode>
onReady ( ) {
this . $refs. form. setRules ( this . userRules) ;
}
小程序无法通过 props 获取父组件的 refs 对象 通过父子组件 emit 回调函数传参解决
handleGetFormRefs ( cb ) {
cb && cb ( this . $refs. form) ;
}
getFormRefs ( ) {
if ( this . runTime) return ;
this . $emit ( 'getFormRefs' , this . handleSendCode) ;
}
2)定义云函数 sendVerificationCode "use strict" ;
exports. main = async ( event, context ) => {
const { phone } = event;
const currentNumber = Math. random ( ) . toString ( ) . substr ( 2 , 6 ) ;
try {
const res = await uniCloud. sendSms ( {
appid : "__UNI__ACE04D2" ,
smsKey : "25cfa81fcade420555022b1ee4021a8a" ,
smsSecret : "d79e45ec2456c4d6ffc7956dba4e1f43" ,
phone,
templateId : "12333" ,
data : {
code : currentNumber,
expMinute : "1" ,
} ,
} ) ;
return {
code : 0 ,
data : {
mobileCode : currentNumber,
msg : "请在手机注意查收验证码" ,
} ,
} ;
} catch ( err) {
return {
code : 1 ,
msg : "短信验证码发送失败" ,
} ;
}
} ;
3)发送验证码 通过 form 对象单独验证手机号码格式 启动定时器,调整文本显示内容 调用云函数,发送验证码 前端接受返回值,保存验证码,加入验证码验证规则 提交用户数据,指定参数 phone 及 type 类型 methods : {
async handleSendCode ( form ) {
const { phone } = await form. validateField ( [ 'phone' ] ) ;
this . runTime = true ;
this . setTimer ( ) ;
const { msg, mobileCode } = await this . $http. sendVerificationCode ( { phone } ) ;
uni. showToast ( {
icon : 'none' ,
title : msg
} ) ;
this . $emit ( 'setCurrentCode' , mobileCode) ;
} ,
setTimer ( ) {
this . timer = setInterval ( ( ) => {
if ( this . time === 1 ) {
clearInterval ( this . timer) ;
this . timer = null ;
this . runTime = false ;
this . time = 60 ;
}
this . time-- ;
} , 1000 ) ;
}
} ,
beforeDestroy ( ) {
if ( this . timer) clearInterval ( this . timer) ;
this . timer = null ;
this . runTime = false ;
this . time = 60 ;
}
(十四)实现收藏按钮组件 1.获取用户信息及文章信息 由于多个页面中都会用到 userInfo 对象,可将 userInfo 对象放在一个全局的 mixin 中 保证每个页面或组件内部都可以使用 1)定义 commonMixin 文件 import { mapState, mapMutations } from "vuex" ;
export default {
install ( Vue ) {
Vue. mixin ( {
computed : {
... mapState ( [ "userInfo" ] ) ,
} ,
methods : {
... mapMutations ( [ "updateUserInfo" ] ) ,
async handleCheckedLogin ( ) {
return new Promise ( ( resolve ) => {
if ( this . userInfo) {
resolve ( ) ;
} else {
uni. navigateTo ( {
url : "/pages/UserInfo/Login/Login" ,
} ) ;
}
} ) ;
} ,
} ,
} ) ;
} ,
} ;
async handleChangeSaveStatus ( ) {
await this . handleCheckedLogin ( ) ;
}
2)main.js 文件中使用 commonMixin 文件
import commonMixin from "./common/commonMixin.js" ;
Vue. use ( commonMixin) ;
2.定义云函数 获取用户记录值 找到指定字段 article_likes_ids
通过 db.command
更新指令修改 article_likes_ids
处理完成,将 article_likes_ids
修改后重新存储 "use strict" ;
const db = uniCloud. database ( ) ;
const dbCmd = db. command;
exports. main = async ( event, context ) => {
const { userId, articleId } = event;
const userInfo = await db. collection ( "user" ) . doc ( userId) . get ( ) ;
const articleIds = userInfo. data[ 0 ] . article_likes_ids;
let returnMsg = null ;
let articleArr = null ;
if ( articleIds. includes ( articleId) ) {
articleArr = dbCmd. pull ( articleId) ;
returnMsg = "取消收藏成功" ;
} else {
articleArr = dbCmd. addToSet ( articleId) ;
returnMsg = "添加收藏成功" ;
}
await db. collection ( "user" ) . doc ( userId) . update ( {
article_likes_ids : articleArr,
} ) ;
const updateUserInfo = await db. collection ( "user" ) . doc ( userId) . get ( ) ;
return {
code : 0 ,
data : {
msg : returnMsg,
newUserInfo : updateUserInfo. data[ 0 ] ,
} ,
} ;
} ;
3.调用云函数 1)将文章信息传递到 SaveLikes.vue 内部使用
< SaveLikes :item = " article" > </ SaveLikes>
2)暴露云函数调用 export const updateSaveLikes = ( data ) =>
ajax ( {
name : "updateSaveLikes" ,
data,
} ) ;
3)点击 icon 发送数据到云服务器 修改存储的用户信息 article_likes_ids
数组 弹出提示信息 async handleChangeSaveStatus ( ) {
await this . handleCheckedLogin ( ) ;
const { msg, newUserInfo } = await this . $http. updateSaveLikes ( {
articleId : this . item. _id,
userId : this . userInfo. _id
} ) ;
uni. showToast ( {
icon : 'success' ,
title : msg
} ) ;
this . updateUserInfo ( {
... this . userInfo,
... newUserInfo
} ) ;
}
< view @click.stop = " handleChangeSaveStatus" class = " save-icons" >
< uni-icons :type = " isLike ? 'heart-filled' : 'heart'" color = " #ff6600" size = " 20" > </ uni-icons>
</ view>
computed : {
isLike ( ) {
return this . userInfo && this . userInfo. article_likes_ids. includes ( this . item. _id) ;
}
}
(十五)搜索页面 1.调整 NavBar 为动态组件 1)isSearch 当页面为搜索页面时,添加返回 icon 图标,并绑定返回事件 < view v-if = " isSearch" :style = " { top: statusBarHeight + 'rpx' }" @click = " returnArticleList" class = " return-icon" >
< uni-icons type = " back" size = " 22" color = " white" > </ uni-icons>
</ view>
2)为点击事件添加条件处理 toSearchPage ( ) {
if ( this . isSearch) return ;
uni. navigateTo ( {
url : '/pages/Search/Search'
} ) ;
}
3)添加回退事件 returnArticleList ( ) {
uni. switchTab ( {
url : '/pages/Home/Home'
} ) ;
uni. navigateBack ( ) ;
}
2.搭建搜索页面结构 < view class = " search-container" >
< NavBar :isSearch = " isSearch" > </ NavBar>
< view class = " search-wrapper" >
< view v-if = " false" class = " search-history-container" >
< view class = " search-header" >
< text class = " history-text" > 搜索历史</ text>
< text class = " history-clean" > 清空</ text>
</ view>
< view class = " search-history-content" >
< view v-for = " item in 10" :key = " item" class = " history-content-item" > 直播</ view>
</ view>
< view class = " no-data" > 当前没有搜索历史</ view>
</ view>
< view v-else class = " search-list-container" >
< ListItem v-if = " searchList.length" > </ ListItem>
< view v-else class = " no-data" > 没有搜索到相关数据</ view>
</ view>
</ view>
</ view>
3.业务逻辑处理 1)获取文本输入内容 input 组件参考文档open in new window
< NavBar :isSearch = " isSearch" :parentVal = " parentVal" @sendSearchData = " handleSendSearchKeyword" @updateVal = " parentVal = $event" > </ NavBar>
computed : {
searchVal : {
get ( ) {
return this . parentVal;
} ,
set ( val) {
this . $emit ( 'updateVal' , val) ;
if ( ! val) {
this . $emit ( 'sendSearchData' ) ;
}
}
}
}
2)定义云函数并返回数据 "use strict" ;
const db = uniCloud. database ( ) ;
exports. main = async ( event, context ) => {
const { searchVal } = event;
const res = await db
. collection ( "article" )
. aggregate ( )
. match ( {
title : new RegExp ( searchVal) ,
} )
. project ( {
content : 0 ,
} )
. end ( ) ;
return {
code : 0 ,
msg : "搜索数据请求成功" ,
data : {
articleList : res. data,
} ,
} ;
} ;
3)搜索关键词 处理空数据内容,清空操作后返回一个空的 searchList async handleSendSearchKeyword ( ) {
this . isShowHistory = false ;
if ( ! this . parentVal) {
this . isShowHistory = true ;
this . searchList = [ ] ;
return ;
}
const { articleList, total } = await this . $http. getSearchHistory ( {
searchVal : this . parentVal
} ) ;
this . searchList = articleList;
}
4)切换历史记录显示
data ( ) {
return {
isShowHistory : true ,
searchList : [ ] ,
}
}
toArticleDetail ( ) {
this . $emit ( 'saveHistory' ) ;
}
通过自定义事件形式传递数据到 Search.vue
< view>
< ListCard v-for = " article in articleList" :article = " article" :key = " article._id" @saveHistory = " $emit('saveHistory')" > </ ListCard>
</ view>
< uni-load-more v-if = " isShowLoading && (articleList.length === 0 || articleList.length > 5)" :status = " loadData.loading" > </ uni-load-more>
saveHistory ( ) {
this . updateSearchHistory ( this . parentVal) ;
}
5)Vuex 管理搜索历史记录
export default {
searchHistory : uni. getStorageSync ( "searchHistory" ) || [ ] ,
} ;
updateSearchHistory ( state, keyword ) {
let history = state. searchHistory;
if ( history. length > 0 && history. findIndex ( item => item === keyword) > - 1 )
return ;
history. unshift ( keyword)
uni. setStorageSync ( "searchHistory" , history) ;
state. searchHistory = history;
}
openHistory ( val ) {
this . parentVal = val;
this . handleSendSearchKeyword ( ) ;
}
点击清空按钮清空历史记录 通过 ListCard.vue 发送自定义事件 Search.vue 接收到自定义事件后,通过触发 mutation 里面的指定事件,清空 state 的指定属性
cleanSearchHistory ( state ) {
uni. removeStorageSync ( "searchHistory" ) ;
state. searchHistory = [ ] ;
uni. showToast ( {
icon : "success" ,
title : "清空完成" ,
} ) ;
}
(十六)标签页面 1.跳转标签页面鉴权
async toLabelEditPage ( ) {
await this . handleCheckedLogin ( ) ;
uni. navigateTo ( {
url : '/pages/LabelEdit/LabelEdit'
} ) ;
}
2.控制标签按钮状态 < view @click = " handleEditLabel" class = " label-edit" > {{ this.isEdit ? '完成' : '编辑' }}</ view>
data ( ) {
return {
isEdit : false
} ;
} ,
methods : {
handleEditLabel ( ) {
this . isEdit && this . handleUpdateLabel ( ) ;
this . isEdit = ! this . isEdit;
} ,
handleUpdateLabel ( ) { }
}
3.保存 labelList 到全局仓库
export default {
labelList : uni. getStorageSync ( "labelList" ) || [ ] ,
} ;
updateLabelList ( state, labelList ) {
uni. setStorageSync ( 'labelList' , labelList) ;
state. labelList = labelList;
} ,
在 ArticleList.vue 替换 labelList 的引用 created ( ) {
this . labelList. length && this . getArticleList ( this . activeIndex) ;
}
import { mapState, mapMutations } from "vuex" ;
export default {
name : "Home" ,
onLoad ( ) {
this . getLabelList ( ) ;
} ,
data ( ) {
return {
activeIndex : 0 ,
} ;
} ,
computed : {
... mapState ( [ "labelList" ] ) ,
} ,
methods : {
... mapMutations ( [ "updateLabelList" ] ) ,
async getLabelList ( ) {
if ( this . labelList. length) return ;
const res = await this . $http. getLabelList ( ) ;
this . updateLabelList ( [
{
name : "全部" ,
} ,
... res,
] ) ;
} ,
} ,
} ;
4.选项卡业务逻辑处理 1)创建我的标签及推荐标签数组 computed : {
... mapState ( [ 'labelList' ] ) ,
selfLabelList ( ) {
return this . labelList. filter ( ( item ) => this . labelIds. includes ( item. _id) ) ;
} ,
recommendLabelList ( ) {
return this . labelList. filter ( ( item ) => ! this . labelIds. includes ( item. _id) && item. _id) ;
}
}
初始化定义本地数据对象 为了不影响用户数据,页面内定义属性拷贝用户数组信息数据 在 watch 中调用,为了防止用户在修改之后,改变用户时无法获取准确信息 使用 immediate 初始化 watch : {
userInfo : {
immediate : true ,
handler ( newVal, oldVal ) {
this . labelIds = [ ... this . userInfo. label_ids] ;
}
}
}
< view class = " label-content" >
< view v-for = " (item, index) in selfLabelList" :key = " item._id" class = " label-content-item" >
{{ item.name }}
< uni-icons v-if = " isEdit" @click = " deleteLabelItem(item)" class = " icon-close" type = " clear" size = " 20" color = " red" > </ uni-icons>
</ view>
< view v-if = " !selfLabelList.length" class = " no-data" > 当前没有数据</ view>
</ view>
< view class = " label-content" >
< view v-for = " (item, index) in recommendLabelList" :key = " item._id" @click = " changeSelfList(item)" class = " label-content-item" > {{ item.name }}</ view>
< view v-if = " !recommendLabelList.length" class = " no-data" > 当前没有数据</ view>
</ view>
为标签切换添加点击事件 切换标签应该在 isEdit 情况下切换 追加用户标签,直接修改 labelIds 删除标签,只过滤匹配内容 在未点击保存按钮时,不需要真正地存储数据 changeSelfList ( item ) {
if ( ! this . isEdit) return ;
this . labelIds. push ( item. _id) ;
} ,
deleteLabelItem ( item ) {
this . labelIds = this . labelIds. filter ( ( val ) => val !== item. _id) ;
}
2)发送数据 "use strict" ;
const db = uniCloud. database ( ) ;
exports. main = async ( event, context ) => {
const { userId, label_ids } = event;
const res = await db. collection ( "user" ) . doc ( userId) . update ( {
label_ids,
} ) ;
return {
code : 0 ,
data : {
msg : "修改成功" ,
} ,
} ;
} ;
async handleUpdateLabel ( ) {
const label_ids = this . selfLabelList. map ( ( item ) => item. _id) ;
const res = await this . $http. updateLabelIds ( {
userId : this . userInfo. _id,
label_ids
} ) ;
uni. showToast ( {
icon : 'success' ,
title : res. msg
} ) ;
this . updateUserInfo ( {
... this . userInfo,
label_ids
} ) ;
}
3)处理其他页面副作用 调整 Home.vue 中 labelList 获取方式 判断是否包含用户信息 不包含直接返回 否则,显示用户收藏内容 computed : {
labelList ( ) {
if ( this . userInfo) {
this . activeIndex = 0 ;
return [ ... this . $store. state. labelList. slice ( 0 , 1 ) , ... this . $store. state. labelList. filter ( ( item ) => this . userInfo. label_ids. includes ( item. _id) ) ] ;
} else {
return this . $store. state. labelList;
}
}
}
ArticleList.vue 监听到 labelList 的值改变后,清空列表对象 watch : {
labelList ( ) {
this . articleData = { } ;
this . loadData = { } ;
this . getArticleList ( this . currentIndex) ;
}
}
(十七)文章详情页面
1.URL 参数处理 在 ListCard 组件跳转过程中,携带参数到详情页面实现数据渲染 toArticleDetail ( ) {
this . $emit ( 'saveHistory' ) ;
const {
_id,
title,
author,
create_time,
thumbs_up_count,
browse_count
} = this . article;
const params = {
_id,
title,
author,
create_time,
thumbs_up_count,
browse_count
} ;
uni. navigateTo ( {
url : ` /pages/ArticleDetail/ArticleDetail?params= ${ JSON . stringify ( params) } `
} ) ;
}
onLoad ( ... options) {
this . articleData = JSON . parse ( options[ 0 ] . params) ;
}
2.发送数据请求 onLoad ( ... options) {
this . articleData = JSON . parse ( options[ 0 ] . params) ;
this . getArticleDetail ( ) ;
} ,
data ( ) {
return {
articleData : null
} ;
} ,
methods : {
async getArticleDetail ( ) {
const res = await this . $http. getArticleDetail ( {
article_id : this . articleData. _id
} ) ;
this . articleData = res;
}
}
"use strict" ;
const db = uniCloud. database ( ) ;
exports. main = async ( event, context ) => {
const { article_id } = event;
const articleList = await db
. collection ( "article" )
. aggregate ( )
. match ( {
_id : article_id,
} )
. end ( ) ;
return {
code : 0 ,
msg : "文章获取成功" ,
data : articleList. data[ 0 ] ,
} ;
} ;
3.将文章详情内容解析为 Markdown 格式 1)解析内容数据 import uParse from "@/components/gaoyia-parse/parse.vue" ;
export default {
name : "ArticleDetail" ,
components : {
uParse,
} ,
} ;
< style>
@import url ( "/components/gaoyia-parse/parse.css" ) ;
</ style>
2)解析 markdown 文本 使用第三方插件 marked npm install marked
或 yarn add marked
import marked from "marked" ;
export default {
computed : {
content ( ) {
try {
return marked ( this . articleData. content) ;
} catch ( e) {
return null ;
}
} ,
} ,
} ;
4.制作评论组件 < CommentModel :showModel = " showModel" > </ CommentModel>
完成内容区域结构样式创建 当用户为未登录状态时,跳转登录页面 < view @click = " handleOpenCommentModel" class = " input-container" >
< text> 谈谈你的看法</ text>
< uni-icons type = " compose" size = " 16" color = " #f07373" > </ uni-icons>
</ view>
async handleOpenCommentModel ( ) {
await this . handleCheckedLogin ( ) ;
this . showModel = true ;
}
前端收集数据,获取用户 id,文章 id,评论内容 async handleSendComment ( content ) {
const { msg } = await this . $http. updateArticleComment ( {
articleId : this . articleData. _id,
userId : this . userInfo. _id,
content
} ) ;
uni. showToast ( {
icon : 'success' ,
title : msg
} ) ;
this . showModel = false ;
}
创建云函数 为 article 追加评论内容 为 comment_id 指定随机 id 值 "use strict" ;
const db = uniCloud. database ( ) ;
const dbCmd = db. command;
exports. main = async ( event, context ) => {
const { userId, articleId, content } = event;
let user = await db. collection ( "user" ) . doc ( userId) . get ( ) ;
user = user. data[ 0 ] ;
const article = await db. collection ( "article" ) . doc ( articleId) . get ( ) ;
let commentObj = {
comment_id : generatedId ( 5 ) ,
comment_content : content,
create_time : Date. now ( ) ,
author : {
author_id : user. _id,
author_name : user. author_name,
avatar : user. avatar,
professional : user. professional,
} ,
replyArr : [ ] ,
is_reply : false ,
} ;
commentObj = dbCmd. unshift ( commentObj) ;
await db. collection ( "article" ) . doc ( articleId) . update ( {
comments : commentObj,
} ) ;
function generatedId ( num ) {
return Number ( Math. random ( ) . toString ( ) . substr ( 3 , num) + Date. now ( ) ) . toString ( 36 ) ;
}
return {
code : 0 ,
data : {
msg : "添加数据成功" ,
} ,
} ;
} ;
5.制作评论展示组件 async getCommentList ( ) {
const res = await this . $http. getCommentList ( {
articleId : this . articleData. _id
} ) ;
this . commentList = res;
}
"use strict" ;
const db = uniCloud. database ( ) ;
exports. main = async ( event, context ) => {
const { articleId, pageSize = 10 , page = 1 } = event;
const list = await db
. collection ( "article" )
. aggregate ( )
. match ( {
_id : articleId,
} )
. unwind ( "$comments" )
. project ( {
_id : 0 ,
comments : 1 ,
} )
. replaceRoot ( {
newRoot : "$comments" ,
} )
. skip ( pageSize * ( page - 1 ) )
. limit ( pageSize)
. end ( ) ;
return {
code : 0 ,
msg : "数据请求成功" ,
data : list. data,
} ;
} ;
const articleList = await db
. collection ( "article" )
. aggregate ( )
. match ( {
_id : article_id,
} )
. project ( {
comments : 0 ,
} )
. end ( ) ;
6.回复指定评论 1)绑定指定评论回复按钮点击事件 添加 comment_id 传递给云函数 保证云函数知道当前是回复哪条评论 < view class = " comment-info" >
< view @click = " handleReplyComment({ comment, isReply })" class = " comment-button" > 回复</ view>
</ view>
2)调整云函数 从当前的 comments 集合当中,通过传递的 comment_id 获取指定的记录值 为当前的记录值的 replyArr 属性插入新的评论内容 修改指定索引值的数据的 replyArr 的记录值 "use strict" ;
const db = uniCloud. database ( ) ;
const dbCmd = db. command;
exports. main = async ( event, context ) => {
const { userId, articleId, content, comment_id = "" , reply_id = "" , is_reply = false } = event;
let user = await db. collection ( "user" ) . doc ( userId) . get ( ) ;
user = user. data[ 0 ] ;
const article = await db. collection ( "article" ) . doc ( articleId) . get ( ) ;
const comments = article. data[ 0 ] . comments;
let commentObj = {
comment_id : generatedId ( 5 ) ,
comment_content : content,
create_time : Date. now ( ) ,
author : {
author_id : user. _id,
author_name : user. author_name,
avatar : user. avatar,
professional : user. professional,
} ,
replyArr : [ ] ,
is_reply,
} ;
if ( comment_id === "" ) {
commentObj. replyArr = [ ] ;
commentObj = dbCmd. unshift ( commentObj) ;
} else {
let commentAuthor = null ;
let commentIndex = comments. findIndex ( ( item ) => item. comment_id === comment_id) ;
if ( is_reply) {
commentAuthor = comments[ commentIndex] . replyArr. find ( ( item ) => item. comment_id === reply_id) . author. author_name;
} else {
commentAuthor = comments. find ( ( item ) => item. comment_id === comment_id) . author. author_name;
}
commentObj. to = commentAuthor;
commentObj = {
[ commentIndex] : {
replyArr : dbCmd. unshift ( commentObj) ,
} ,
} ;
}
await db. collection ( "article" ) . doc ( articleId) . update ( {
comments : commentObj,
} ) ;
function generatedId ( num ) {
return Number ( Math. random ( ) . toString ( ) . substr ( 3 , num) + Date. now ( ) ) . toString ( 36 ) ;
}
return {
code : 0 ,
data : {
msg : "添加数据成功" ,
} ,
} ;
} ;
3)使用组件自调用的形式 在自调用的过程当中需要保证组件指定了 name 属性值 使用自调用的过程中父组件 ArticleDetail 会接收到两次$emit 事件 一次是当前组件 CommentList 作为父组件时获取到的递归子组件触发的事件 一次是当前组件 CommentList 向上级 ArticleDetail 组件传递时触发的事件
< view class = " comment-info" >
< view @click = " handleReplyComment({ comment, isReply })" class = " comment-button" > 回复</ view>
</ view>
< view v-for = " item in comment.replyArr" :key = " item._comment_id" class = " comment-reply-list" >
< CommentList :isReply = " true" :comment = " item" @replyComment = " handleReplyComment" > </ CommentList>
</ view>
handleReplyComment ( data ) {
if ( data. isReply) {
data. comment. reply_id = data. comment. comment_id;
data. comment. comment_id = this . comment. comment_id;
}
this . $emit ( 'replyComment' , data) ;
}
4)父组件处理更新评论内容 当指定回复发送事件时,需要指定 reply_id,并且调整 comment_id 值 渲染完成后,将 replayData 清空 handleReplyComment ( data ) {
this . replyData = {
comment_id : data. comment. comment_id,
is_reply : data. isReply
} ;
data. comment. reply_id && ( this . replyData. reply_id = data. comment. reply_id) ;
this . handleOpenCommentModel ( ) ;
}
7.关注作者 < button @click = " handleFollowAuthor" type = " default" class = " detail-header-button" > {{ isFollowing ? '取消关注' : '关注' }}</ button>
computed : {
isFollowing ( ) {
try {
return this . userInfo && this . userInfo. author_likes_ids. includes ( this . articleData. author. id) ;
} catch ( e) {
return false ;
}
}
}
绑定事件,传递文章作者 ID 及当前登录用户 ID async handleFollowAuthor ( ) {
await this . handleCheckedLogin ( ) ;
const { msg } = await this . $http. updateFollowAuthor ( {
authorId : this . articleData. author. id,
userId : this . userInfo. _id
} ) ;
uni. showToast ( {
icon : 'success' ,
title : msg
} ) ;
let followIds = [ ... this . userInfo. author_likes_ids] ;
if ( followIds. includes ( this . articleData. author. id) ) {
followIds = followIds. filter ( ( item ) => item !== this . articleData. author. id) ;
} else {
followIds. push ( this . articleData. author. id) ;
}
this . updateUserInfo ( {
... this . userInfo,
author_likes_ids : followIds
} ) ;
}
"use strict" ;
const db = uniCloud. database ( ) ;
const dbCmd = db. command;
exports. main = async ( event, context ) => {
const { userId, authorId } = event;
const user = await db. collection ( "user" ) . doc ( userId) . get ( ) ;
const authorLikesIds = user. data[ 0 ] . author_likes_ids;
let returnMsg = "" ;
let author_ids = null ;
if ( authorLikesIds. includes ( authorId) ) {
returnMsg = "取消关注成功" ;
author_ids = dbCmd. pull ( authorId) ;
} else {
returnMsg = "关注作者成功" ;
author_ids = dbCmd. addToSet ( authorId) ;
}
await db. collection ( "user" ) . doc ( userId) . update ( {
author_likes_ids : author_ids,
} ) ;
return {
code : 0 ,
data : {
msg : returnMsg,
} ,
} ;
} ;
< SaveLikes :item = " articleData" size = " 22" class = " detail-bottom-icon-box" > </ SaveLikes>
< uni-icons :type = " isLike ? 'heart-filled' : 'heart'" :size = " size" color = " #ff6600" > </ uni-icons>
< script>
export default {
props : {
item : Object,
size : {
type : String,
default : "20" ,
} ,
} ,
} ;
</ script>
ArticleList.vue 解决每次请求都要加载文章列表的 Bug labelList ( newVal, oldVal ) {
if ( JSON . stringify ( newVal) === JSON . stringify ( oldVal) ) return ;
this . articleData = { } ;
this . loadData = { } ;
this . getArticleList ( this . currentIndex) ;
}
8.实现点赞+浏览次数 请求文章详情的云函数 getArticleDetail ,浏览次数+1 "use strict" ;
const db = uniCloud. database ( ) ;
const dbCmd = db. command;
exports. main = async ( event, context ) => {
const { article_id } = event;
await db. collection ( "article" ) . update ( {
browse_count : dbCmd. inc ( 1 ) ,
} ) ;
const articleList = await db
. collection ( "article" )
. aggregate ( )
. match ( {
_id : article_id,
} )
. project ( {
comments : 0 ,
} )
. end ( ) ;
return {
code : 0 ,
msg : "文章获取成功" ,
data : articleList. data[ 0 ] ,
} ;
} ;
ArticleDetail.vue 使用计算属性渲染点赞 icon isLike ( ) {
try {
return this . userInfo && this . userInfo. thumbs_up_article_ids. includes ( this . articleData. _id) ;
} catch ( e) {
return false ;
}
}
"use strict" ;
const db = uniCloud. database ( ) ;
const dbCmd = db. command;
exports. main = async ( event, context ) => {
const { articleId, userId } = event;
const userList = await db. collection ( "user" ) . doc ( userId) . get ( ) ;
const thumbs_up_article_ids = userList. data[ 0 ] . thumbs_up_article_ids;
let tempArr = null ;
let returnMsg = "" ;
let thumbsNumber = null ;
if ( thumbs_up_article_ids. includes ( articleId) ) {
tempArr = dbCmd. pull ( articleId) ;
thumbsNumber = - 1 ;
returnMsg = "您取消了点赞" ;
} else {
tempArr = dbCmd. addToSet ( articleId) ;
thumbsNumber = 1 ;
returnMsg = "点赞成功" ;
}
await await db. collection ( "user" ) . doc ( userId) . update ( {
thumbs_up_article_ids : tempArr,
} ) ;
await await db
. collection ( "article" )
. doc ( articleId)
. update ( {
thumbs_up_count : dbCmd. inc ( thumbsNumber) ,
} ) ;
return {
code : 0 ,
data : {
msg : returnMsg,
} ,
} ;
} ;
async handleLikeArticle ( ) {
await this . handleCheckedLogin ( ) ;
const { msg } = await this . $http. updateLikeArticle ( {
articleId : this . articleData. _id,
userId : this . userInfo. _id
} ) ;
msg &&
uni. showToast ( {
icon : 'success' ,
title : msg
} ) ;
let thumbsArr = [ ... this . userInfo. thumbs_up_article_ids] ;
if ( thumbsArr. includes ( this . articleData. _id) ) {
this . articleData. thumbs_up_count -= 1 ;
thumbsArr = thumbsArr. filter ( ( item ) => item != this . articleData. _id) ;
} else {
this . articleData. thumbs_up_count += 1 ;
thumbsArr. push ( this . articleData. _id) ;
}
this . updateUserInfo ( {
... this . userInfo,
thumbs_up_article_ids : thumbsArr
} ) ;
}
(十八)评论页面 创建页面 pages.json 中注册页面 跳转页面时携带指定的文章 ID toCommentDetail ( ) {
uni. navigateTo ( {
url : ` /pages/CommentDetail/CommentDetail?articleId= ${ this . articleData. _id} `
} ) ;
}
2.编写结构样式 1)使用评论组件 < view v-for = " item in commentList" :key = " item.comment_id" class = " comment-content-container" >
< CommentList :comment = " item" @replyComment = " handleReplyComment" > </ CommentList>
</ view>
2)使用 uni-load-more 组件 < uni-load-more
v-if = " commentList.length === 0 || commentList.length > 5"
:status = " loading"
:contentText = " { contentdown: '上拉显示更多', contentrefresh: '正在加载...', contentnomore: '没有更多评论了' }"
> </ uni-load-more>
3)追加评论列表 async getCommentList ( ) {
const res = await this . $http. getCommentList ( {
articleId : this . articleId,
page : this . page,
pageSize : this . pageSize
} ) ;
if ( res. length === 0 ) {
this . loading = 'noMore' ;
return ;
}
let oldList = JSON . parse ( JSON . stringify ( this . commentList) ) ;
oldList. push ( ... res) ;
this . commentList = oldList;
}
3.添加评论内容输入组件 < CommentModel :showModel = " showModel" @sendComment = " handleSendComment" @hideCommentModel = " showModel = $event" > </ CommentModel>
4.发布评论 1)回复事件函数处理 handleReplyComment ( data ) {
this . replyData = {
comment_id : data. comment. comment_id,
is_reply : data. isReply
} ;
data. comment. reply_id && ( this . replyData. reply_id = data. comment. reply_id) ;
this . handleOpenCommentModel ( ) ;
}
2)处理副作用 async handleSendComment ( content ) {
const { msg } = await this . $http. updateArticleComment ( {
articleId : this . articleId,
userId : this . userInfo. _id,
content,
... this . replyData
} ) ;
uni. showToast ( {
icon : 'success' ,
title : msg
} ) ;
this . showModel = false ;
this . getCommentList ( ) ;
this . replyData = { } ;
this . page = 1 ;
this . commentList = [ ] ;
this . loading = 'loading' ;
}
(十九)关注页面 1.添加路由守卫 uni-simple-routeropen in new window
快速上手open in new window
{
"dependencies" : {
"marked" : "^2.1.3" ,
"uni-read-pages" : "^1.0.5" ,
"uni-simple-router" : "^2.0.7"
}
}
1)pages.json 增加配置项 meta {
"path" : "pages/Follow/Follow" ,
"style" : {
"navigationBarTitleText" : "关注" ,
"enablePullDownRefresh" : false
} ,
"meta" : {
"auth" : true
} , {
"path" : "pages/UserInfo/Login/Login" ,
"style" : {
"navigationBarTitleText" : "用户登录" ,
"enablePullDownRefresh" : false
} ,
"name" : "Login"
}
}
const TransformPages = require ( "uni-read-pages" ) ;
const { webpack } = new TransformPages ( ) ;
module. exports = {
configureWebpack : {
plugins : [
new webpack. DefinePlugin ( {
ROUTES : webpack. DefinePlugin. runtimeValue ( ( ) => {
const tfPages = new TransformPages ( {
includes : [ "path" , "name" , "meta" , "aliasPath" ] ,
} ) ;
return JSON . stringify ( tfPages. routes) ;
} , true ) ,
} ) ,
] ,
} ,
} ;
3)添加守卫规则
router. beforeEach ( ( to, from, next ) => {
if ( to. meta. auth && ! store. state. userInfo) {
next ( {
name : "Login" ,
NAVTYPE : "push" ,
} ) ;
} else {
next ( ) ;
}
} ) ;
4)ArticleDetail.vue 修改路由参数接收方式 onLoad ( ... options) {
this . articleData = this . $Router. currentRoute. query. params;
this . getArticleDetail ( ) ;
this . getCommentList ( ) ;
}
5)Follow.vue 处理小程序无法兼容问题 onLoad ( ) {
if ( ! this . $store. state. userInfo) {
uni. redirectTo ( {
url : '/pages/UserInfo/Login/Login'
} ) ;
return ;
}
}
2.收藏文章列表 1)定义云函数 "use strict" ;
const db = uniCloud. database ( ) ;
const $ = db. command. aggregate;
exports. main = async ( event, context ) => {
const { userId } = event;
let userInfo = await db. collection ( "user" ) . doc ( userId) . get ( ) ;
let article_likes_ids = userInfo. data[ 0 ] . article_likes_ids;
const list = await db
. collection ( "article" )
. aggregate ( )
. addFields ( {
is_like : $. in ( [ "$_id" , article_likes_ids] ) ,
} )
. project ( {
content : 0 ,
} )
. match ( {
is_like : true ,
} )
. end ( ) ;
return {
code : 0 ,
msg : "请求成功" ,
data : list. data,
} ;
} ;
2)获取当前登录用户的收藏文章列表 onLoad ( ) {
if ( ! this . $store. state. userInfo) {
uni. redirectTo ( {
url : '/pages/UserInfo/Login/Login'
} ) ;
return ;
}
this . getFollowArticle ( ) ;
} ,
data ( ) {
return {
currentIndex : 0 ,
articleList : [ ] ,
isShowLoading : false ,
noData : false
} ;
} ,
methods : {
async getFollowArticle ( ) {
const res = await this . $http. getFollowArticle ( {
userId : this . userInfo. _id
} ) ;
this . articleList = res;
! res. length && ( this . noData = true ) ;
}
}
3.关注作者列表 1)监听全局事件 处理关注页面数据不刷新问题 使用 uni.$emit
事件注册全局事件
async handleChangeSaveStatus ( ) {
uni. $emit ( 'updateArticle' ) ;
}
onLoad ( ) {
if ( ! this . $store. state. userInfo) {
uni. redirectTo ( {
url : '/pages/UserInfo/Login/Login'
} ) ;
return ;
}
this . getFollowArticle ( ) ;
uni. $on ( 'updateArticle' , ( e ) => {
this . getFollowArticle ( 'noLoading' ) ;
} ) ;
}
2)搭建关注作者区域结构 < swiper :current = " currentIndex" @change = " currentIndex = $event.detail.current" class = " follow-list-swiper" >
< swiper-item :class = " { 'no-data': articleDataNone }" >
{{ articleDataNone && '暂无收藏的文章' }}
< ListItem v-if = " articleList.length" :isShowLoading = " isShowLoading" :articleList = " articleList" > </ ListItem>
</ swiper-item>
< swiper-item>
< AuthorList v-if = " authorList.length" :authorList = " authorList" > </ AuthorList>
< view v-if = " authorDataNone" class = " no-data" > 暂无关注的作者</ view>
</ swiper-item>
</ swiper>
3)定义云函数 "use strict" ;
const db = uniCloud. database ( ) ;
const $ = db. command. aggregate;
exports. main = async ( event, context ) => {
const { userId } = event;
let userInfo = await db. collection ( "user" ) . doc ( userId) . get ( ) ;
let author_likes_ids = userInfo. data[ 0 ] . author_likes_ids;
const list = await db
. collection ( "user" )
. aggregate ( )
. addFields ( {
is_like : $. in ( [ "$id" , author_likes_ids] ) ,
} )
. project ( {
password : 0 ,
loginName : 0 ,
} )
. match ( {
is_like : true ,
} )
. end ( ) ;
return {
code : 0 ,
msg : "请求成功" ,
data : list. data,
} ;
} ;
4)客户端渲染 < AuthorList v-if = " authorList.length" :authorList = " authorList" > </ AuthorList>
async getFollowAuthor ( isLoading ) {
const res = await this . $http. getFollowAuthor ( {
userId : this . userInfo. _id,
isLoading
} ) ;
if ( res. length) {
this . authorDataNone = '' ;
} else {
this . authorDataNone = true ;
}
this . authorList = res;
}
export default ( { name, data = { } } ) => {
const loading = data. isLoading;
data. isLoading && delete data. isLoading;
return new Promise ( ( resolve, reject ) => {
! loading &&
uni. showLoading ( {
title : "加载中..." ,
} ) ;
uniCloud. callFunction ( {
name,
data,
success ( { result } ) {
if ( result. code === 0 ) {
resolve ( result. data) ;
} else {
uni. showToast ( {
icon : "error" ,
title : result. msg,
} ) ;
}
} ,
fail ( err ) {
reject ( err) ;
} ,
complete ( ) {
! loading && uni. hideLoading ( ) ;
} ,
} ) ;
} ) ;
} ;
5)同步数据 定义全局事件,实现修改关注作者时触发 updateAuthor 事件重新调用
async handleFollowAuthor ( ) {
uni. $emit ( 'updateAuthor' ) ;
}
uni. $on ( "updateAuthor" , ( e ) => {
this . getFollowAuthor ( "noLoading" ) ;
} ) ;
6)修改卡片组件跳转方式
toArticleDetail ( ) {
this . $Router. push ( {
path : '/pages/ArticleDetail/ArticleDetail' ,
query : params
} ) ;
}
onLoad ( ... options) {
this . articleData = this . $Router. currentRoute. query;
this . getArticleDetail ( ) ;
this . getCommentList ( ) ;
}
(二十)个人中心 1.页面结构搭建 1)未登录状态 2)登录状态 2.我的文章 1)渲染用户信息 2)Self.vue 处理 app 升级版本逻辑 初始化 onLoad 事件 判断是否有新版本下载,获取当前的版本 onLoad ( ) {
uni. getSystemInfo ( {
success : ( res ) => {
if ( res. platform == 'android' ) {
plus. runtime. getProperty ( plus. runtime. appid, ( wgtinfo ) => {
this . currentVersion = wgtinfo;
this . checkVersion ( ) ;
} ) ;
}
}
} ) ;
}
async checkVersion ( ) {
const { version, downLoadLinkUrl } = await this . $http. getCurrentVersion ( ) ;
if ( version > this . currentVersion) {
this . haveNewVersion = true ;
this . downLoadLinkUrl = downLoadLinkUrl;
}
}
import ajax from "../../http.js" ;
export const getCurrentVersion = ( data ) =>
ajax ( {
name : "getCurrentVersion" ,
data,
} ) ;
getNewVersion ( ) {
uni. showLoading ( {
title : '下载中,请稍后'
} ) ;
let dTask = plus. downloader. createDownload ( this . downLoadLinkUrl, { } , function ( d, status ) {
uni. hideLoading ( { } ) ;
if ( status == 200 ) {
plus. runtime. install ( plus. io. convertLocalFileSystemURL ( d. filename) , { } , { } , function ( error ) {
uni. showToast ( {
title : '安装失败' ,
duration : 1500 ,
icon : 'none'
} ) ;
} ) ;
} else {
uni. showToast ( {
title : '更新失败' ,
duration : 1500 ,
icon : 'none'
} ) ;
}
} ) ;
dTask. start ( ) ;
}
3)处理我的文章 < view>
< ListCard v-for = " article in articleList" :article = " article" :key = " article._id" > </ ListCard>
< view v-if = " noArticle" class = " no-data" > 您还没有发布过任何文章</ view>
</ view>
"use strict" ;
const db = uniCloud. database ( ) ;
const dbCmd = db. command;
exports. main = async ( event, context ) => {
const { userId } = event;
let userInfo = await db. collection ( "user" ) . doc ( userId) . get ( ) ;
let article_ids = userInfo. data[ 0 ] . article_ids;
const list = await db
. collection ( "article" )
. aggregate ( )
. project ( {
content : 0 ,
comments : 0 ,
} )
. match ( {
id : dbCmd. in ( article_ids) ,
} )
. end ( ) ;
return {
code : 0 ,
msg : "请求成功" ,
data : list. data,
} ;
} ;
3.意见反馈 1)搭建页面结构 2)上传图片 ChooseImage 参考文档open in new window
async handleUploadImage ( ) {
const count = 5 - this . imageList. length;
uni. chooseImage ( {
count,
success : ( res ) => {
const tempFilePaths = res. tempFilePaths;
tempFilePaths. forEach ( ( url, index ) => {
if ( index < count) {
this . imageList. push ( {
url,
name : res. tempFiles[ index] . name,
name : Date. now ( ) . toString ( ) + url. substr ( url. lastIndexOf ( '.' ) )
} ) ;
}
} ) ;
}
} ) ;
}
3)发送数据到云函数 图片直传参考文档open in new window
getFileId ( ) {
return this . imageList. map ( ( item ) => {
return new Promise ( async ( resolve ) => {
const result = await uniCloud. uploadFile ( {
filePath : item. url,
cloudPath : item. name
} ) ;
resolve ( result. fileID) ;
} ) ;
} ) ;
}
"use strict" ;
const db = uniCloud. database ( ) ;
exports. main = async ( event, context ) => {
const { userId, feedbackImageList, content } = event;
await db. collection ( "feedback" ) . add ( {
user_id : userId,
feedbackImageList,
content,
} ) ;
return {
code : 0 ,
data : {
msg : "提交反馈成功" ,
} ,
} ;
} ;
4)修改用户头像 "use strict" ;
const db = uniCloud. database ( ) ;
const dbCmd = db. command;
exports. main = async ( event, context ) => {
const { userId, filePath } = event;
const user = await db. collection ( "user" ) . doc ( userId) . get ( ) ;
const oldUrl = user. data[ 0 ] . avatar;
try {
await uniCloud. deleteFile ( {
fileList : [ oldUrl] ,
} ) ;
} catch ( e) {
console. log ( e) ;
}
const id = user. data[ 0 ] . id;
let res = await db
. collection ( "article" )
. where ( {
"author.id" : id,
} )
. update ( {
"author.avatar" : filePath,
} ) ;
await db. collection ( "user" ) . doc ( userId) . update ( {
avatar : filePath,
} ) ;
return {
code : 0 ,
data : {
msg : "修改头像成功" ,
} ,
} ;
} ;
async getAvatarFileId ( filePath, cloudPath ) {
const { fileID } = await uniCloud. uploadFile ( {
filePath,
cloudPath
} ) ;
return fileID;
} ,
async handleUploadAvatar ( filePath ) {
const { msg } = await this . $http. updateUserAvatar ( {
userId : this . userInfo. _id,
filePath
} ) ;
uni. showToast ( {
title : msg,
icon : 'none'
} ) ;
this . updateUserInfo ( {
... this . userInfo,
avatar : filePath
} ) ;
} ,
handleChangeAvatar ( ) {
uni. chooseImage ( {
count : 1 ,
success : async ( res ) => {
const filePath = await this . _uploadFile (
res. tempFilePaths[ 0 ] ,
res. tempFiles[ 0 ] . name || Date. now ( ) . toString ( ) + res. tempFilePaths[ 0 ] . substr ( res. tempFilePaths[ 0 ] . lastIndexOf ( '.' ) )
) ;
await this . handleUploadAvatar ( filePath) ;
}
} ) ;
}
(二十一)发布 1.Wap 端发行打包 打包参考文档open in new window
uniCloud 网页托管配置 使用 uniCloud 前端网页托管,需要配置安全域名 参考文档open in new window
2.微信小程序发布 打包参考文档open in new window
参考文档open in new window
3.App 安卓系统应用打包发布 1)配置
2)证书下载 证书下载地址open in new window