[TOC]

学习目的:
通过学习,对标企业级「大前端」开发实践标准,从零到一构建「社区论坛类」客户端应用项目, 输出相关 Web H5、微信小程序、安卓 APP、IOS APP 多客户端应用。

一、项目准备

1.1 ThinkSNS 简介及相关文档

ThinkSNS 简介
ThinkSNS 是智士软件旗下的开源微博、社交系统,采用 PHP+MySQL 技术,核心(微博、用户系统)+ 多应用 + 多插件的产品模式,拥有Web、H5、IOS、Android APP以及微信、支付宝、百度等小程序多端服务场景。
ThinkSNS 服务
ThinkSNS 能够建立社交网站、微博平台、教育平台、商城系统、粉丝社区、知识社区、企业文化社区等多种互联网平台,是移动互联网创业的首选二次开发平台。
社区、论坛实践项目简介

  1. 基于 ThinkSNS 开源免费体系,搭建行业内基础 PGC、UGC 内容管理社区、论坛服务体系。
  2. 商家运营人员可以发布管理 PGC 资讯,管理用户、动态、广告等操作
  3. 客户端用户可以查阅、点赞、评论、分享相关动态资讯内容,可以自主发布、删除 UGC 内容、同步 UGC 内容审核

image.png
相关文档

  1. API 接口文档,详情查阅《 ThinkSNS 社区服务接口文档.md 》
  2. ThinkSNS 综合管理系统,开源代码 git 地址:https://gitee.com/joysapp/thinksns-plus
  3. 客户端小程序源代码 git 地址:https://gitee.com/buhehpc/jxsns.git

    1.2 使用 UniAPP 构建项目

  4. 使用 Hbuilder uni-app 默认空白模板构建项目 study

image.png

  1. 在 manifest.json 文件中配置微信小程序相关信息

image.png

1.3 构建项目文件结构

  1. 构建项目基础结构

image.png

  1. 初始化基础页面及配置 pages.json Pages 路由

“pages”: [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
{
“path”: “pages/index/index”,
“style”: {
// custorm 取消顶部的 navBar 显示
“navigationStyle”: “custom”,
“enablePullDownRefresh”: true
}
}, {
“path”: “pages/feeds/feeds”,
“style”: {
“navigationBarTitleText”: “动态中心”,
“enablePullDownRefresh”: true
}

    }, {<br />            "path": "pages/me/me",<br />            "style": {<br />                "navigationStyle": "custom",<br />                "enablePullDownRefresh": true<br />            }

    }<br />]

1.4 配置页面 TabBar 导航

“tabBar”: {
“color”: “#000”,
“selectedColor”: “#0050FF”,
“list”: [{
“iconPath”: “/static/tabbar-icons/index.png”,
“selectedIconPath”: “/static/tabbar-icons/index_s.png”,
“text”: “首页”,
“pagePath”: “pages/index/index”
},
{
“iconPath”: “/static/tabbar-icons/feeds.png”,
“selectedIconPath”: “/static/tabbar-icons/feeds_s.png”,
“text”: “动态”,
“pagePath”: “pages/feeds/feeds”
},
{
“iconPath”: “/static/tabbar-icons/me.png”,
“selectedIconPath”: “/static/tabbar-icons/me_s.png”,
“text”: “我的”,
“pagePath”: “pages/me/me”
}
]
}

1.5 使用 npm 引入 uView UI 插件库

  1. 使用 HBuilder 导入插件 uViewUI 或者使用 npm 安装相关依赖(推荐使用 npm 安装)

// 如果您的项目是HX创建的,根目录又没有package.json文件的话,请先执行如下命令:
npm init -y
// 安装
npm install uview-ui
// 更新
npm update uview-ui

  1. main.js引入uView库

// main.js
import uView from ‘uview-ui’;
Vue.use(uView);

  1. 编辑器安装相关依赖 工具 — 插件安装 — scss 编译支持

image.png

  1. App.vue引入基础样式

/ App.vue /

  1. uni.scss引入全局scss变量文件

/ uni.scss /
@import “uview-ui/theme.scss”;

  1. pages.json配置easycom规则(按需引入)

// pages.json
{
“easycom”: {
// 下载安装的方式需要前面的”@/“,npm安装的方式无需”@/“
// “^u-(.)”: “@/uview-ui/components/u-$1/u-$1.vue”
// npm安装方式
“^u-(.
)”: “uview-ui/components/u-$1/u-$1.vue”
},
// 此为本身已有的内容
“pages”: [
// ……
]
}

二、首页功能实现

2.1 首页 header 广告位轮播图功能实现

关键技术点:

  1. swiper 轮播器组件的使用
  2. 使用 this.$u.get/post 异步请求数据
  3. 使用 v-for 循环遍历渲染轮播内容












UniAPP 社区论坛项目多端开发实战 - 图7



UniAPP 社区论坛项目多端开发实战 - 图8

精彩动态



UniAPP 社区论坛项目多端开发实战 - 图9

个人中心





推荐
资讯


2.2 全局配置 WebView 展示第三方 web 页面

关键技术点:我们通常为整个应用的 Web H5 相关页面,构建一个专门用来展示的 webview 路由页面,但是要注意路由传参的时候,对应的 url 需要 decodeURI 、encodeURI

2.3 uViewUI 网络 http 请求及 API 集中管理

  1. 请求头、拦截、返回 /common/http.interceptor.js 相关配置

// 这里的vm,就是我们在vue文件里面的this,所以我们能在这里获取vuex的变量,比如存放在里面的token变量
const install = (Vue, vm) => {
// 此为自定义配置参数,具体参数见上方说明
Vue.prototype.$u.http.setConfig({
baseUrl: ‘http://47.115.83.135/api/v2‘, // 请求的本域名
dataType: ‘json’, // 设置为json,返回后会对数据进行一次JSON.parse()
showLoading: true, // 是否显示请求中的loading
loadingText: ‘请求中…’, // 请求loading中的文字提示
loadingTime: 800, // 在此时间内,请求还没回来的话,就显示加载中动画,单位ms
originalData: true, // 是否在拦截器中返回服务端的原始数据
loadingMask: true, // 展示loading的时候,是否给一个透明的蒙层,防止触摸穿透
// 配置请求头信息
header: {
‘content-type’: ‘application/json;charset=UTF-8’
},
});

// 请求拦截,配置Token等参数<br />    Vue.prototype.$u.http.interceptor.request = (config) => {<br />        // 引用token<br />        // 方式一,存放在vuex的token,假设使用了uView封装的vuex方式<br />        // 见:https://uviewui.com/components/globalVariable.html<br />        // config.header.token = vm.token;

    // 方式二,如果没有使用uView封装的vuex方法,那么需要使用$store.state获取<br />        // config.header.token = vm.$store.state.token;

    // 方式三,如果token放在了globalData,通过getApp().globalData获取<br />        // config.header.token = getApp().globalData.username;

    // 方式四,如果token放在了Storage本地存储中,拦截是每次请求都执行的<br />        // 所以哪怕您重新登录修改了Storage,下一次的请求将会是最新值<br />        // const token = uni.getStorageSync('token');<br />        // config.header.token = token;<br />        config.header.Token = 'xxxxxx';

    // 可以对某个url进行特别处理,此url参数为this.$u.get(url)中的url值<br />        // if(config.url == '/user/login') config.header.noToken = true;<br />        // 最后需要将config进行return<br />        return config;<br />        // 如果return一个false值,则会取消本次请求<br />        // if(config.url == '/user/rest') return false; // 取消某次请求<br />    }

// 响应拦截,判断状态码是否通过<br />    Vue.prototype.$u.http.interceptor.response = (res) => {<br />        if(res.code == 401) {<br />            // 如果返回false,则会调用Promise的reject回调,<br />            // 并将进入this.$u.post(url).then().catch(res=>{})的catch回调中,res为服务端的返回值<br />            vm.$u.toast('当前请求 API 接口不存在');<br />            return false;<br />        } else {<br />            // res为服务端返回值,可能有code,result等字段<br />            // 这里对res.result进行返回,将会在this.$u.post(url).then(res => {})的then回调中的res的到<br />            // 如果配置了originalData为true,请留意这里的返回值<br />            return res;<br />        }<br />    }<br />}

export default {
install
}

  1. 请求 API 集中管理 /common/http.api.js

// 此处第二个参数vm,就是我们在页面使用的this,你可以通过vm获取vuex等操作,更多内容详见uView对拦截器的介绍部分:
const install = (Vue, vm) => {
let api = {}
// 获取广告位列表信息
api.getAdvert = params => vm.$u.get(‘/advertisingspace/advertising’, params)

// 获取动态列表信息<br />    api.getFeeds = params => vm.$u.get('/feeds', params)

// 获取资讯列表信息<br />    api.getNews = params => vm.$u.get('/news', params)

// 将各个定义的接口名称,统一放进对象挂载到vm.$u.api(因为vm就是this,也即this.$u.api)下<br />    vm.$u.api = api<br />}

export default {
install
}

  1. 在 main.js 中配置,插件注册第二个参数 app 要传入到实例中去,所以要注意引入顺序

const app = new Vue({
…App
})
// http拦截器,此为需要加入的内容,如果不是写在common目录,请自行修改引入路径
import httpInterceptor from ‘@/common/http.interceptor.js’
// 这里需要写在最后,是为了等Vue创建对象完成,引入”app”对象(也即页面的”this”实例)
Vue.use(httpInterceptor, app)
// http接口API集中管理引入部分
import httpApi from ‘@/common/http.api.js’
Vue.use(httpApi, app)
app.$mount()

2.4 首页「推荐、资讯」平滑切换实现

关键技术点:

  1. 使用轮播器,不自动播放,来构建一个左右滑动切换页面的效果
  2. 需要注意的是,轮播器的高度要设置一个固定值





…动态页面瀑布流展示



…资讯页面列表展示

2.5 首页「推荐」瀑布流布局效果实现

瀑布流布局其核心是基于一个网格的布局,而且每行包含的项目列表高度是随机的(随着自己内容动态变化高度),同时每个项目列表呈堆栈形式排列,最为关键的是,堆栈之间彼此之间没有多余的间距差存大。
实现瀑布流的解决方案,可以使用 CSS3 布局来实现,也可以使用 JS 脚本来实现,使用 CSS3 实现代码如下:
/
这里的关键属性是column-count,设置列数为2。然后是break-inside:avoid,为了控制文本块分解成单独的列,以免项目列表的内容跨列,破坏整体的布局,这样就实现了瀑布流布局。
/
.waterfall {
width: 80%;
margin: 0 auto;
/ 瀑布流容器内元素的间隔 /
column-gap:10px;
/ 瀑布容器内排列的列数 /
column-count: 2;
}
.item {
padding: 10px;
margin-bottom: 10px;
/ avoid避免在主体框中插入任何中断(页面,列或区域) /
break-inside:avoid
}
使用 CSS3 实现瀑布流布局,性能高于js,缺点是用户体验差,比如:移除数据、更新数据会造成整个页面结构的复杂变化,让用户突然失去焦点的感觉非常不友好!所以使用 js 实现优势也是比较明显的,我们这里使用 uViewUI 的瀑布流插件来实现





PS :swiper 实现 滑动切换页面的效果需要设置一个固定的高度值,但是咱们瀑布流的高度是未知的,那么该怎么办呢?iViewUI 中的 u-waterfall 源码分析,我们要通过全局事件设置的方式来动态的设置 高度,同时,咱们这个定制化的 u-waterfall 需要单独再拿出来进行使用,拓展咱们定制化的插件

  1. 在 uViewUI 中创建一个自定义使用的 u-waterfall-sns.vue 插件,主要分为三步:
    • 在 uViewUI 组件库 components 中找到 u-waterfall 文件,复制一份命名为u-waterfall-sns
    • 文件内 u-waterfall.vue 插件,复制一份命名为u-waterfall-sns.vue 插件
    • u-waterfall-sns.vue 插件内,name 属性更改为 u-waterfall-sns,就可以调用咱们拓展出来的插件了
  2. 使用 uni.$on 设置全局事件,在 u-waterfall-sns.vue 插件内部瀑布流渲染完毕后触发这个事件,动态修改首页轮播切换页面内的 swiper 高度data() {
    return {
    // 轮播器高度
    swiperSliderHeight: ‘500px’
    }
    },
    async onLoad() {
    // 根据瀑布流计算的高度设置全局事件,动态修正页面滑动轮播器高度
    uni.$on(‘swiperHeightChange’, height =>{
    console.log(height)
    this.swiperSliderHeight = height
    })
    },
  3. 设置 image 组件的 mode 属性为 widthFil ,让动态瀑布流错落有致的状态更友好

    2.6 首页「推荐、资讯」轮播切换动态设置轮播器高度

    关键技术点:切换 资讯、动态 的时候,轮播器切换页面的高度是需要动态调整的,我们需要监听行为进行调整
    export default {
    data() {
    return {
    // 当前 推荐 资讯 滑动位置
    currentSwiperIndex: 0,
    // 滑动页面轮播器的高度
    swiperSliderHeight: ‘500px’,
    swiperSliderFeedsHeight: 0,
    swiperSliderNewsHeight: 0
    }
    },
    async onLoad() {
    // 在这里注册一个 uniAPP 的顶层事件,用来作为数据通信
    uni.$on(“swiperHeightChange”, height => {
    this.swiperSliderFeedsHeight = height
    this.swiperSliderHeight = height
    })

         // 我们要在这里初始化请求相关数据<br />            this.getAdverts()<br />            this.getFeedsList()<br />            this.getNewsList()
    
     },<br />        methods: {<br />            // 请求 广告轮播图信息<br />            async getAdverts(){},<br />            // 请求 feeds 列表数据<br />            async getFeedsList(){},<br />            // 请求资讯列表数据<br />            async getNewsList(){<br />                let news = await this.$u.api.getNews()<br />                let newsList= news.data.map(item => {<br />                    console.log(timeFrom(new Date(item.created_at)))<br />                    return {<br />                        ...item,<br />                        cover: this.BaseFileURL + item.image.id<br />                    }<br />                })
    
             this.newsList = [...this.newsList , ...newsList]<br />                this.swiperSliderNewsHeight = this.newsList.length * 95 + 100 + 'px'<br />                this.swiperSliderHeight = this.swiperSliderNewsHeight<br />            },<br />        // 页面滑动左右分页的时候实现的效果<br />            swiperSlider(event){<br />                if(event.detail.current === 0){<br />                    this.swiperSliderHeight = this.swiperSliderFeedsHeight<br />                }else{<br />                    this.swiperSliderHeight = this.swiperSliderNewsHeight<br />                }<br />                this.currentSwiperIndex = event.detail.current<br />            },<br />            // 点击按钮实现切换效果<br />            swiperChange(index){<br />                if(index === 0){<br />                    this.swiperSliderHeight = this.swiperSliderFeedsHeight<br />                }else{<br />                    this.swiperSliderHeight = this.swiperSliderNewsHeight<br />                }<br />                this.currentSwiperIndex = index<br />            }<br />        }<br />}
    

2.7 首页滚动状态下动态设置「推荐、资讯」位置及 NavBar 状态

在插件市场找到一个功能比较丰富的 NavBar 插件 https://ext.dcloud.net.cn/plugin?id=813
PS:注意,导入别人的插件后,咱们先体验体验,在使用的时候,尽量去看看插件的源码,以便于我们对当前使用插件更深层次的理解和掌握
第一步:导入和使用插件
// main.js 中全局引入插件 uni-nav-bar
import uniNavBar from “@/components/uni-nav-bar.vue”
Vue.component(“uni-nav-bar”, uniNavBar);



推荐
资讯


// 根据滚动状态动态显示隐藏导航栏
data() {
return {
// navBar 显示状态控制
navBarShowTag: false,
// 记录 推荐滚动 所在的位置
oldFeedsScrollTop: 0,
// 记录 资讯滚动 所在位置
oldNewsScrollTop: 0
}
}
第二步:监听页面滚动事件,判断滚动显示和隐藏 navBar 导航状态
// 监听滚动事件,动态显示隐藏 Navbar
onPageScroll(event) {
if (event.scrollTop > 220) {
this.navBarShowTag = true
} else {
this.navBarShowTag = false
}
}
第三步:记录滚动状态下「动态、资讯」位置
// 监听滚动事件,记录滚动位置
onPageScroll(event) {
if (this.currentSwiperIndex === 0) {
this.oldFeedsScrollTop = event.scrollTop
} else {
this.oldNewsScrollTop = event.scrollTop
}
}
第四步:切换「动态、资讯」滚动到指定记录位置
// 页面滑动左右分页的时候实现的效果
swiperSlider(event) {
if(event.detail.current === 0) {
this.swiperSliderHeight = this.swiperSliderFeedsHeight
uni.pageScrollTo({
duration: 0, //过渡时间必须为0,uniapp bug,否则运行到手机会报错
scrollTop: this.oldFeedsScrollTop, //滚动到目标位置
})
} else {
this.swiperSliderHeight = this.swiperSliderNewsHeight
uni.pageScrollTo({
duration: 0, //过渡时间必须为0,uniapp bug,否则运行到手机会报错
scrollTop: this.oldNewsScrollTop, //滚动到目标位置
})
}
this.currentSwiperIndex = event.detail.current
},
// 点击按钮实现切换效果
swiperChange(index) {
if (index === 0) {
this.swiperSliderHeight = this.swiperSliderFeedsHeight
uni.pageScrollTo({
duration: 0, //过渡时间必须为0,uniapp bug,否则运行到手机会报错
scrollTop: this.oldFeedsScrollTop, //滚动到目标位置
})
} else {
this.swiperSliderHeight = this.swiperSliderNewsHeight
uni.pageScrollTo({
duration: 0, //过渡时间必须为0,uniapp bug,否则运行到手机会报错
scrollTop: this.oldNewsScrollTop, //滚动到目标位置
})
}
this.currentSwiperIndex = index
}

2.8 顶部下拉、底部置底请求更新数据

关键技术点:

  1. onReachBottom、onPullDownRefresh 全局方法调用
  2. this.$refs.waterfall.clear() 调用插件对应方法清空数据流

// 下拉到底请求新的数据
onReachBottom() {
// 请求新的数据
if (this.currentSwiperIndex === 0) {
this.getFeeds()
} else {
this.getNews()
}
},
// 顶部下拉请求新数据
onPullDownRefresh() {
this.feedsList = []
this.$refs.waterfall.clear()
if (this.currentSwiperIndex === 0) {
this.getFeeds()
} else {
this.getNews()
}
},
methods: {
// 获取动态信息
async getFeeds() {
let feeds = await this.$u.api.getFeeds()
let feedsList = feeds.feeds.map(item => {
return {
…item,
cover: this.BaseFileURL + item.images[0].file,
avatar: !!item.user.avatar ? item.user.avatar.url : ‘/static/nopic.png’,
name: item.user.name,
}
})
this.feedsList = […this.feedsList, …feedsList]
// 在这里注册一个 uniAPP 的顶层事件,用来作为数据通信
uni.$once(“swiperHeightChange”, height => {
console.log(‘瀑布流计算出来轮播器高度为:’ + height)
this.swiperSliderFeedsHeight = height + ‘px’
this.swiperSliderHeight = this.swiperSliderFeedsHeight
})
},
// 获取咨询列表信息
async getNews() {
let news = await this.$u.api.getNews()
let newsList = news.map(item => {
return {
…item,
cover: this.BaseFileURL + item.image.id
}
})
this.newsList = […this.newsList, …newsList]
this.swiperSliderNewsHeight = (this.newsList.length * 95 + 120) + ‘px’
this.swiperSliderHeight = this.swiperSliderNewsHeight
}
}

三、资讯动态相关页面功能实现

3.1 使用 Grid 栅格布局实现动态瀑布流页面

技术关键点:

  1. 动态请求数据,并对数据进行加工处理成为我们期望的 分组数组 目标数据
  2. grid 栅格化布局
  3. 顶部下拉刷新更新数据
  4. 滚动置底请求数据
  5. 请求状态控制器

### 3.2 使用分包构建业务逻辑页面 技术关键点:page.json 中配置分包业务逻辑
“subPackages”: [{
“root”: “subpages”,
“pages”: [{
“path”: “feedinfo”,
“style”: {
“navigationBarTitleText”: “动态详情”,
“navigationBarBackgroundColor”: “#FFFFFF”,
“navigationBarTextStyle”: “black”,
“backgroundColor”: “#FFFFFF”
}
}]
}], ### 3.3 动态详情页面功能实现 技术关键点: 1. 分享朋友圈 wx.showShareMenu({
withShareTicket: true,
menus: [‘shareAppMessage’, ‘shareTimeline’]
}) 1. 图片预览 methods: {
previewImage(index) {
uni.previewImage({
current: index,
urls: this.feedInfo.images
});
}
} 1. 使用日期格式化过滤器显示时间 // 引入 时间日期格式化显示函数
import timeFrom from ‘@/tools/timeFrom.js’
// 设置过滤器
filters: {
timeFormate(timeDate) {
let Time = new Date(timeDate);
let timestemp = Time.getTime();
let t = timeFrom(timestemp, “yyyy年mm月dd日”);
return t;
}
},
// 调用过滤器
{{ feedInfo.created_at | timeFormate }} 发布 ### 3.4 资讯详情页面富文本解析功能实现 技术关键点: 1. 顶部毛玻璃插件的引入及使用 // 引入毛玻璃组件
import picBlur from “@/components/pic-blur/pic-blur.vue”;
components: {
picBlur
},


1. 资讯详情内容 富文本内容 深度解析 import htmlParse from “@/components/html-parse/parse.vue”;

async onLoad(options) {
// 分享
wx.showShareMenu({
withShareTicket: true,
menus: [‘shareAppMessage’, ‘shareTimeline’]
})
let res = await this.$u.api.getNewInfo(options);
res = res.data // let cp = res.content.replace(/@![(\d).jpg]((\d))/g,”UniAPP 社区论坛项目多端开发实战 - 图11“)
let cp = res.content.replace(/@![.]((\d))/g, “UniAPP 社区论坛项目多端开发实战 - 图12“) this.newInfo = {
…res,
cutTitle: res.title.length > 11 ? res.title.substring(0, 11) + “…” : res.title,
cover: this.BaseFileURL + res.image.id,
userId: res.user_id,
content: cp,
views_count: res.hits
}
this.getRequestOK = true;
} 1. NavBar 根据滚动动态显示


UniAPP 社区论坛项目多端开发实战 - 图13


{{ newInfo.cutTitle }}
data() {
return {
// 是否显示 navbar
navBarShow: false
};
},
onPageScroll(res) {
if (res.scrollTop > 100) {
this.navBarShow = true;
} else {
this.navBarShow = false;
}
} ### 3.5 评论组件评论列表展示功能实现 关键技术点:使用 props 接收到的数据可以直接渲染到页面上,但是 props 接收到的数据如果修改后,则不会动态触发 对应 dom 结构的内容更新,我们需要做一个变量去保存 props 传递的初始化值然后基于此变量更新页面结构和内容
## 四、用户注册、登陆业务功能实现 ### 4.1 用户登陆、注册功能组件实现及触发显示 关键技术点: 1. 获取当前微信用户的 昵称 和 头像信息 2. 使用 u-form 实现登陆、手机注册、邮箱注册基础逻辑 3. 使用 u-form 实现表单验证相关逻辑 ### 4.2 Vuex + Storage 登陆状态管理 关键技术点: 1. 构建 vuex 注册 main.js 全局,并配合使用 storage 实现刷新后数据保存 import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
// 这个代表全局可以访问数据对象,就像是咱们在组件中声明的 data 属性
state: {
loginState: !!uni.getStorageSync('loginState') ? true : false,
userInfo: !!uni.getStorageSync('userInfo') ? JSON.parse(uni.getStorageSync('userInfo')) : {
name: '未知用户',
avatar: '/static/nopic.png',
liked: 0,
commented: 0
}
},
// 这个实时监听 state 内的数据对象变化,类似 咱们组件中的 computed 属性,会依赖 state 数据变化而变化
getters: { },
// 用来同步设置 state 的值
mutations: {
userLogin(state, userInfo) {
state.loginState = true
state.userInfo = userInfo
uni.setStorageSync('loginState', 'ok')
uni.setStorageSync('userInfo', JSON.stringify(userInfo))
},
userLogout(state) {
state.loginState = false
state.userInfo = {
name: '未知用户',
avatar: '/static/nopic.png',
liked: 0,
commented: 0
}
uni.clearStorageSync('userInfo')
uni.clearStorageSync('loginState')
uni.clearStorageSync('token')
}
},
// 通过提交 mutations 内部的方法,异步更新 state 的状态,官方推荐都使用这种方法比较合适
actions: {
userLoginAction(context, userInfo) {
context.commit('userLogin', userInfo)
},
userLogoutAction(context) {
context.commit('userLogout')
}
}
})
export default store 1. 使用 mapState、mapActions 实现 vuex 变量快速调用 import {
mapState,
mapActions
} from 'vuex'
computed: {
...mapState(['loginState', 'userInfo'])
},
methods: {
...mapActions(['userLoginAction', 'userLogoutAction'])
} 1. 使用 uni.$on $emit 触发首页及个人中心页面登录后对应的数据更新 // index onload
uni.$on('indexUserLogin', ()=>{
this.currentSwiperIndex = 0
this.feedsList = []
this.$refs.waterfall.clear()
this.getFeedsList()
})
// me onload
// 用户登录后触发数据更新
uni.$on('meUserLogin', this.getInfos) uni.$emit('meUserLogin')
uni.$emit('indexUserLogin') ### 4.3 个人中心页面基础功能实现 关键技术点: 1. 请求当前登录用户发布动态列表数据 // 获取当前用户的 动态信息
let res = await this.$u.api.getFeeds({
type: 'users'
}) 1. 使用 瀑布流 插件实现排版布局


1. 使用 vuex 状态数据显示用户登录状态 import {
mapState,
mapActions
} from 'vuex'
computed: {
...mapState(['loginState', 'userInfo'])
},
methods: {
...mapActions(['userLoginAction', 'userLogoutAction'])
} 1. 用户删除一条动态更新瀑布流并通信 index 页面同步更新 // 选择删除一条动态
async openSheet(fid) {
uni.showActionSheet({
itemList: ["删除"],
success: async res => {
console.log('删除 id 为' + fid + '的动态')
await this.$u.api.deleteFeed({
id: fid
})
this.$refs.waterfall.remove(fid);
// 触发 首页动态 同步删除事件
uni.$emit('indexFeedRemove', fid)
uni.showToast({
title: "当前动态已删除",
duration: 1000
})
}
});
},
// index 页面
// 个人中心删除一条动态后,触发更新首页数据
uni.$on("indexFeedRemove", fid =>{
this.$refs.waterfall.remove(fid);
}) ### 4.4 用户设置页面基础功能实现 关键技术点: 1. 使用 vuex 状态管理器 actions 方法快速退出 2. 使用 uni.$on $emit 触发首页及个人中心页面用户退出后对应的数据更新 // index onload
uni.$on('indexUserLogout', ()=>{
this.currentSwiperIndex = 0
this.feedsList = []
this.$refs.waterfall.clear()
this.getFeedsList()
})
// me onload
// 用户退出后触发数据更新
uni.$on('meUserLogout', () => {
console.log('触发了退出操作')
this.feedsList = []
this.avatar = ''
this.bio = ''
this.$refs.waterfall.clear()
})
uni.$emit('meUserLogout')
uni.$emit('indexUserLogout') ## 五、登陆态下业务功能实现 ### 5.1 使用 mixins 实现动态点赞、取消点赞功能 关键技术点: 1. 使用 mixins 构建复用的动态 点赞、取消点赞 事件方法 let feedMixin = {
methods: {
// 点赞或者取消点赞一条动态
async clickLove(item) {
// 判断当前登录状态
if (!this.loginState) {
this.$refs.login.openLogin()
return
}
// 动态点赞
if (item.has_like) {
--item.like_count;
item.has_like = false;
await this.$u.api.unlikeThisFeed({
id: item.id,
});
uni.showToast({
title: "取消点赞",
icon: "success",
duration: 1000,
});
} else {
++item.like_count;
item.has_like = true;
await this.$u.api.likeThisFeed({
id: item.id,
});
uni.showToast({
title: "点赞成功",
icon: "success",
duration: 1000,
});
}
uni.$emit('indexFeedLoveChange',item)
uni.$emit('myFeedLoveChange',item)
}
}
}
export default feedMixin 1. 调用 mixins 实现点赞功能 // index \ me 两个页面
import feedMixin from '@/mixins/todoFeed.js'
mixins: [feedMixin] 1. 点赞完成后通信相关页面更新显示状态 // me 页面 用户点赞一条动态后触发数据更新
uni.$on('myFeedLoveChange', item => {
this.$refs.waterfall.modify(item.id, "like_count", item.like_count);
this.$refs.waterfall.modify(item.id, "has_like", item.has_like);
})
// index 用户点赞一条动态后触发数据更新
uni.$on('indexFeedLoveChange', item => {
this.$refs.waterfall.modify(item.id, "like_count", item.like_count);
this.$refs.waterfall.modify(item.id, "has_like", item.has_like);
}) // minxins 中触发通信
uni.$emit('indexFeedLoveChange',item)
uni.$emit('myFeedLoveChange',item) ### 5.2 登陆状态下评论组件点赞、评论功能实现 关键技术点: 1. 登陆插件引入及登陆状态判断

// 判断当前登录状态
if (!this.loginState) {
this.$refs.login.openLogin()
return
} 1. 正常实现资讯点赞功能 // 点赞逻辑操作
async sendLove(){
// 判断当前登录状态
if (!this.loginState) {
this.$refs.login.openLogin()
return
}
if (this.type === "feed") {
this.clickLove(this.oneInfoClone)
} else {
// 动态点赞
if (this.oneInfoClone.has_like) {
--this.oneInfoClone.digg_count;
this.oneInfoClone.has_like = false;
await this.$u.api.unlikeThisNew({
id: this.oneInfoClone.id,
});
uni.showToast({
title: "取消点赞",
icon: "success",
duration: 1000,
});
} else {
++this.oneInfoClone.digg_count;
this.oneInfoClone.has_like = true;
await this.$u.api.likeThisNew({
id: this.oneInfoClone.id,
});
uni.showToast({
title: "点赞成功",
icon: "success",
duration: 1000,
});
}
}
} 1. 实现评论功能,注意:评论完成后修改页面数据不能直接修改 props 中的数据,通知个人中心评论数量增加 // 发送评论信息
async sendComment(){
// 发送状态判定
if (this.disableSendCommentTag) return
this.disableSendCommentTag = true if (this.type === 'feed') {
await this.$u.api.commentOneFeed({
id: this.oneInfoClone.id,
body: this.cinput
});
++this.oneInfoClone.feed_comment_count
// 通知 个人中心当前动态评论增加
uni.$emit('myFeedCommentChange', this.oneInfoClone)
} else {
await this.$u.api.commentOneInfo({
id: this.oneInfoClone.id,
body: this.cinput
});
++this.oneInfoClone.comment_count
}
uni.showToast({
title: "评论成功",
icon: "success",
duration: 1000,
});
this.cinput = ''
this.closeComment()
this.getCommentsList()
} ### 5.3 UGC 动态发布功能实现 关键技术点: 1. 公共组件 发布按钮 ,注意用户未登录状态下不显示这个按钮
  1. 构建 share 页面用来专门实现动态上传能力