基于 UniAPP 社区论坛项目多端开发实战

社区论坛项目多端开发实战

  • 基于 UniAPP 社区论坛项目多端开发实战
    • 一、项目准备
      • 1.1 ThinkSNS 简介及相关文档
      • 1.2 使用 UniAPP 构建项目
      • 1.3 构建项目文件结构
      • 1.4 配置页面 TabBar 导航
      • 1.5 使用 npm 引入 uView UI 插件库
    • 二、首页功能实现
      • 2.1 首页 header 广告位轮播图功能实现
      • 2.2 全局配置 WebView 展示第三方 web 页面
      • 2.3 uViewUI 网络 http 请求及 API 集中管理
      • 2.4 首页「推荐、资讯」平滑切换实现
      • 2.5 首页「推荐」瀑布流布局效果实现
      • 2.6 首页「推荐、资讯」轮播切换动态设置轮播器高度
      • 2.7 首页滚动状态下动态设置「推荐、资讯」位置及 NavBar 状态
      • 2.8 顶部下拉、底部置底请求更新数据
    • 三、资讯动态相关页面功能实现
      • 3.1 使用 Grid 栅格布局实现动态瀑布流页面
      • 3.2 使用分包构建业务逻辑页面
      • 3.3 动态详情页面功能实现
      • 3.4 资讯详情页面富文本解析功能实现
      • 3.5 评论组件评论列表展示功能实现
    • 四、用户注册、登陆业务功能实现
      • 4.1 用户登陆、注册功能组件实现及触发显示
      • 4.2 Vuex + Storage 登陆状态管理
      • 4.3 个人中心页面基础功能实现
      • 4.4 用户设置页面基础功能实现
    • 五、登陆态下业务功能实现
      • 5.1 使用 mixins 实现动态点赞、取消点赞功能
      • 5.2 登陆状态下评论组件点赞、评论功能实现
      • 5.3 UGC 动态发布功能实现
    • 六、多端打包发布
      • 6.1 微信小程序打包发布
      • 6.2 H5 端相关兼容性开发及打包发布
      • 6.3 安卓云端体验版本打包发布

基于 UniAPP 社区论坛项目多端开发实战

学习目的:

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

在这里插入图片描述

一、项目准备

1.1 ThinkSNS 简介及相关文档

ThinkSNS 简介

ThinkSNS 是智士软件旗下的开源微博、社交系统,含微博、论坛、资讯、频道、商城、活动、定位、找人、直播、问答等一系列功能。

ThinkSNS 采用 PHP+MySQL 技术,核心(微博、用户系统)+ 多应用 + 多插件的产品模式,拥有Web、H5、IOS、Android APP以及微信、支付宝、百度等小程序多端服务场景。

ThinkSNS 服务

ThinkSNS 能够建立社交网站、微博平台、教育平台、商城系统、粉丝社区、知识社区、企业文化社区等多种互联网平台,是移动互联网创业的首选二次开发平台。

社区、论坛实践项目简介

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

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

相关文档

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

1.2 使用 UniAPP 构建项目

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

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

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

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

1.3 构建项目文件结构

  1. 构建项目基础结构

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  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}}, {"path": "pages/me/me","style": {"navigationStyle": "custom","enablePullDownRefresh": true}}
]

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 编译支持

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. App.vue引入基础样式
/* App.vue */
<style lang="scss">
@import "uview-ui/index.scss";
</style>
  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 循环遍历渲染轮播内容
<!-- 页面 header 相关部分 -->
<view class="header-box"><!-- 顶部广告位轮播图 --><swiper class="swiper" :indicator-dots="false" :autoplay="true" :interval="2500" :duration="500"><swiper-item v-for="item in swiperAdList" :key="item.id"><navigator open-type="navigate" :url=" '/pages/webview/webview?url='+item.link"><image class="banner-swiper-img" :src="item.image" mode="aspectFill" /></navigator></swiper-item></swiper><!-- 遮罩使用弧形框 --><image class="crile" src="@/static/crile.png" mode="aspectFill" /><!-- 两个选项按钮 --><view class="card-header"><view class="card-one card-left" @tap="gotoFeeds('/pages/feeds/feeds')"><image class="img" src="@/static/coffee.png" mode="aspectFill" /><view class="iright"><view class="title">精彩动态</view></view></view><view class="card-one card-right" @tap="gotoFeeds('/pages/me/me')"><image class="img" src="@/static/ran.png" mode="aspectFill" /><view class="iright"><view class="title">个人中心</view></view></view></view><!-- Tab 选项卡 --><view class="tabs-box"><view class="one-nav" :class="currentSwiperIndex === 0 ? 'nav-actived' : '' " @tap="swiperChange(0)">推荐</view><view class="one-nav" :class="currentSwiperIndex === 1 ? 'nav-actived' : '' " @tap="swiperChange(1)">资讯</view></view>
</view>
<script>// 请求 广告轮播图信息async getAdverts() {let adverts = await this.$u.api.getAdvert({space: '1,2,3'})this.swiperAdList = adverts.data.map(item => {return {id: item.id,link: item.data.link,image: item.data.image}})}
</script>

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

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

<template><web-view :src="url" />
</template>
<script>export default {data() {return {url:''}},// 接收传递过来的参数地址,将 web-view 相关页面展开集合到一个地方onLoad(options){this.url = decodeURI(options.url)}}
</script>

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, // 是否显示请求中的loadingloadingText: '请求中...', // 请求loading中的文字提示loadingTime: 800, // 在此时间内,请求还没回来的话,就显示加载中动画,单位msoriginalData: true, // 是否在拦截器中返回服务端的原始数据loadingMask: true, // 展示loading的时候,是否给一个透明的蒙层,防止触摸穿透// 配置请求头信息header: {'content-type': 'application/json;charset=UTF-8'},});// 请求拦截,配置Token等参数Vue.prototype.$u.http.interceptor.request = (config) => {// 引用token// 方式一,存放在vuex的token,假设使用了uView封装的vuex方式// 见:https://uviewui.com/components/globalVariable.html// config.header.token = vm.token;// 方式二,如果没有使用uView封装的vuex方法,那么需要使用$store.state获取// config.header.token = vm.$store.state.token;// 方式三,如果token放在了globalData,通过getApp().globalData获取// config.header.token = getApp().globalData.username;// 方式四,如果token放在了Storage本地存储中,拦截是每次请求都执行的// 所以哪怕您重新登录修改了Storage,下一次的请求将会是最新值// const token = uni.getStorageSync('token');// config.header.token = token;config.header.Token = 'xxxxxx';// 可以对某个url进行特别处理,此url参数为this.$u.get(url)中的url值// if(config.url == '/user/login') config.header.noToken = true;// 最后需要将config进行returnreturn config;// 如果return一个false值,则会取消本次请求// if(config.url == '/user/rest') return false; // 取消某次请求}// 响应拦截,判断状态码是否通过Vue.prototype.$u.http.interceptor.response = (res) => {if(res.code == 401) {// 如果返回false,则会调用Promise的reject回调,// 并将进入this.$u.post(url).then().catch(res=>{})的catch回调中,res为服务端的返回值vm.$u.toast('当前请求 API 接口不存在');return false;} else {// res为服务端返回值,可能有code,result等字段// 这里对res.result进行返回,将会在this.$u.post(url).then(res => {})的then回调中的res的到// 如果配置了originalData为true,请留意这里的返回值return res;}}
}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)// 获取动态列表信息api.getFeeds = params => vm.$u.get('/feeds', params)// 获取资讯列表信息api.getNews = params => vm.$u.get('/news', params)// 将各个定义的接口名称,统一放进对象挂载到vm.$u.api(因为vm就是this,也即this.$u.api)下vm.$u.api = api
}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. 需要注意的是,轮播器的高度要设置一个固定值
<!-- 内容轮播导航实现 -->
<swiper  class="swiper-box" style="height:1000upx" :current="0"><!-- 推荐动态实现 --><swiper-item class="swiper-item sns-now">...动态页面瀑布流展示</swiper-item><!-- 资讯列表实现 --><swiper-item class="swiper-item sns-news">...资讯页面列表展示</swiper-item>
</swiper>

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 的瀑布流插件来实现

<u-waterfall v-model="flowList" ref="uWaterfall"><template v-slot:left="{leftList}"><view v-for="(item, index) in leftList" :key="index"><!-- 这里编写您的内容,item为您传递给v-model的数组元素 --></view></template><template v-slot:right="{rightList}"><view v-for="(item, index) in rightList" :key="index"><!-- 这里编写您的内容,item为您传递给v-model的数组元素 --></view></template>
</u-waterfall>
<script>
export default {methods:{remove(id) {this.$refs.uWaterfall.remove(id);},clear() {this.$refs.uWaterfall.clear();}}
}
</script>

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

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);
<!-- 顶部导航 -->
<uni-nav-bar v-if="navBarShowTag"><view class="tabs-box"><view class="one-nav" :class="currentSwiperIndex === 0 ? 'nav-actived' : '' " @tap="swiperChange(0)">推荐</view><view class="one-nav" :class="currentSwiperIndex === 1 ? 'nav-actived' : '' " @tap="swiperChange(1)">资讯</view></view>
</uni-nav-bar>
// 根据滚动状态动态显示隐藏导航栏
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.swiperSliderFeedsHeightuni.pageScrollTo({duration: 0, //过渡时间必须为0,uniapp bug,否则运行到手机会报错scrollTop: this.oldFeedsScrollTop, //滚动到目标位置})} else {this.swiperSliderHeight = this.swiperSliderNewsHeightuni.pageScrollTo({duration: 0, //过渡时间必须为0,uniapp bug,否则运行到手机会报错scrollTop: this.oldNewsScrollTop, //滚动到目标位置})}this.currentSwiperIndex = event.detail.current
},
// 点击按钮实现切换效果
swiperChange(index) {if (index === 0) {this.swiperSliderHeight = this.swiperSliderFeedsHeightuni.pageScrollTo({duration: 0, //过渡时间必须为0,uniapp bug,否则运行到手机会报错scrollTop: this.oldFeedsScrollTop, //滚动到目标位置})} else {this.swiperSliderHeight = this.swiperSliderNewsHeightuni.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. 请求状态控制器
<template><view class="rfeeds"><view class="one-feeds-box" v-for=" (feedsList, i) in showFeedsList " :key="i"><view v-for=" (item, k) in feedsList " :key="item.id" class="one-feed" :class="k % 6 == 0 ? ( i%2==0 ? 'feed-big-left' :'feed-big-right' ) : '' "><navigator :url=" '/subpages/feedinfo/feedinfo?id=' + item.id"><image :src="item.cover" class="feed-content" mode="aspectFill" :lazy-load="true" /></navigator></view></view><!-- 分享按钮组件 --><goto-share /></view>
</template>
<script>export default {data() {return {// 列表数据feedsList: [],// 用来展示的栅格系统的列表数据showFeedsList: [],// 记录请求 feed 状态canRequestFeeds: "yes"};},onLoad() {this.getFeeds()},// 顶部下拉刷新新数据onPullDownRefresh() {this.feedsList = []this.getFeeds()},// 滚动置底刷新请求数据onReachBottom() {this.getFeeds()},methods: {// 获取请求数据async getFeeds() {if (this.canRequestFeeds === "yes") {uni.showToast({title: "请求中...",icon: "loading",duration: 10000});// 设置状态this.canRequestFeeds = "no"let res = await this.$u.api.getFeeds()let feeds = res.feeds.map(item => {return {id: item.id,cover: this.BaseFileURL + item.images[0].file}})if (feeds.length > 5) {// 记录总列表数据this.feedsList = [...this.feedsList, ...feeds];// 在这里要处理页面的内容布局模式为栅格模式,6个元素一组let showArrList = [];for (let i = 0; i < this.feedsList.length; i++) {if (i % 6 == 0 && !!this.feedsList[i + 5]) {// 返回一个新的数组,包含从 start 到 end (不包括该元素)元素。// 该方法并不会修改数组,而是返回一个子数组。如果想删除数组中的一段元素,应该使用方法 Array.splice()。showArrList.push(this.feedsList.slice(i, i + 6))}}this.showFeedsList = showArrListthis.canRequestFeeds = "yes"} else {this.canRequestFeeds = "end"uni.showToast({title: "到底啦!",icon: "success",duration: 1000});}}// 如果请求状态到底了后,则提示到底了if (this.canRequestFeeds === "end") {uni.hideToast();uni.showToast({title: "到底啦!",icon: "success",duration: 1000}) }},},};
</script>

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;}
},
// 调用过滤器
<text>{{ feedInfo.created_at | timeFormate }} 发布</text>

3.4 资讯详情页面富文本解析功能实现

技术关键点:

  1. 顶部毛玻璃插件的引入及使用
// 引入毛玻璃组件
import picBlur from "@/components/pic-blur/pic-blur.vue";
components: {picBlur
},
<!-- 顶部毛玻璃背景图 -->
<!-- 可选值 xs s m l xl -->
<pic-blur :params="{width:'750rpx',height:'520rpx',image: newInfo.cover,blur:'xs'
}"></pic-blur>
  1. 资讯详情内容 富文本内容 深度解析
import htmlParse from "@/components/html-parse/parse.vue";<view class="info-content"><html-parse :content="newInfo.content" />
</view>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,"<img src='" +this.BaseFileURL +'$2' + "' />")let cp = res.content.replace(/@!\[.*\]\((\d*)\)/g, "<img src='" + this.BaseFileURL + '$1' + "' />")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 根据滚动动态显示
<!-- 顶部导航 -->
<uni-nav-bar backState="2000" fontColor="#FFF" :titleCenter="false" type="transparent"><view slot="left"><image class="hicon" src="/static/home.png" mode="aspectFit" @tap="goHome" /></view>
</uni-nav-bar><uni-nav-bar v-if="navBarShow" backState="1000" :titleCenter="false">{{ newInfo.cutTitle }}
</uni-nav-bar>data() {return {// 是否显示 navbarnavBarShow: false};
},
onPageScroll(res) {if (res.scrollTop > 100) {this.navBarShow = true;} else {this.navBarShow = false;}
}

3.5 评论组件评论列表展示功能实现

关键技术点:使用 props 接收到的数据可以直接渲染到页面上,但是 props 接收到的数据如果修改后,则不会动态触发 对应 dom 结构的内容更新,我们需要做一个变量去保存 props 传递的初始化值然后基于此变量更新页面结构和内容

<template><view><!-- 评论列表 --><view class="comments"><view class="title gohere" id="gohere" ref="gohere">最新评论</view><view class="no-comment" v-if="commintsList.length == 0">暂无评论</view><view v-for="(commentItem, index) in commintsList" :key="index" class="one-comment"><!-- 一级评论相关 --><view class="commenter"><view class="info"><view class="left"><u-avatar size="50" class="avatar" :src="!!commentItem.user.avatar ? commentItem.user.avatar.url : '' " /><view class="name">{{ commentItem.user.name }}</view></view></view><view class="content">{{ commentItem.body }}</view><view class="uptime">{{ commentItem.created_at | timeFormate }} 评论</view></view></view></view></view>
</template><script>import timeFrom from "@/tools/timeFrom.js";export default {props: {oneInfo: Object,type: String,},data() {return {// 当前动态评论列表详情commintsList: [],// props 传递的数据无法改变触发 DOM 更新oneInfoClone: []};},async created() {this.oneInfoClone = this.oneInfothis.getCommentsList();},methods: {// 获取评论列表async getCommentsList() {if (this.type === "feed") {// 获取当前动态评论列表信息let res = await this.$u.api.getFeedComments({id: this.oneInfo.id});this.commintsList = res.data.comments;} else {// 获取当前资讯评论列表信息let res = await this.$u.api.getNewComments({id: this.oneInfo.id});this.commintsList = res.data.comments;}}},// 过滤器filters: {timeFormate(timeDate) {let Time = new Date(timeDate);let timestemp = Time.getTime();let t = timeFrom(timestemp, "yyyy年mm月dd日");return t;},},};
</script>

四、用户注册、登陆业务功能实现

4.1 用户登陆、注册功能组件实现及触发显示

关键技术点:

  1. 获取当前微信用户的 昵称 和 头像信息
  2. 使用 u-form 实现登陆、手机注册、邮箱注册基础逻辑
  3. 使用 u-form 实现表单验证相关逻辑
<template><view class="login" :class="{show:show}"><button class="submit-btn" open-type="getUserInfo" @getuserinfo="getWechatUserInfo" v-if="getUserInfoTag"><image src="/static/wechat.png" class="wechat-img" /><text>一键获取微信信息</text></button><u-form :model="form" ref="uForm" v-show="!getUserInfoTag"><u-form-item label="账号" prop="login" label-width="150" required v-if="loginType === 'login'"><u-input v-model="form.login" placeholder='输入手机号/邮箱/昵称' /></u-form-item><u-form-item label="密码" label-width="150" required prop="password"><u-input v-model="form.password" type="password" placeholder='限4-20个字符,区分大小写' /></u-form-item><view class="btns"><u-button class="ubtn" @click="submit">提交</u-button><u-button class="ubtn" @click="cancel">取消</u-button></view><view class="type"><u-subsection active-color="#007cba" font-size="24" height="52" :list="subsectionList" :current="0" @change="sectionChange"></u-subsection></view></u-form></view></view></view>
</template><script>export default {data() {return {// 登陆组件是否显示show: false,// 登陆方式loginType: 'login',// 登陆方式选择器subsectionList: [{	name: '账号登陆'},{	name: '手机注册'	},{	name: '邮箱注册'	}],// 是否获取用户信息getUserInfoTag: true,// 用户信息输入框form: {login: '',avatar: '',name: '',phone: '',email: '',code: '',password: '',repassword: ''},// 用户输入规则校验rules: {login: [{validator: (rule, value, callback) => {if (this.loginType === 'login') {return !this.$u.test.isEmpty(value)} else {return true}},message: '必填 * 输入内容不许为空',trigger: ['change', 'blur'],}],name: [{asyncValidator: (rule, value, callback) => {this.$u.api.findUser({name: value}).then(res => {// 如果验证不通过,需要在callback()抛出new Error('错误提示信息')if (!!value && res.statusCode === 200) {callback(new Error('当前用户昵称已存在'));} else {// 如果校验通过,也要执行callback()回调callback();}})},trigger: ['blur'],}],// ... 其他验证规则}}},// 必须要在onReady生命周期设置校验规则,因为onLoad生命周期组件可能尚未创建完毕onReady() {this.$refs.uForm.setRules(this.rules);},async created() {// 每次这个组件展开,我们就去判断一下当前 token 是否可以获取新的 token 如果可以获取,咱们就关闭登陆状态let res = await this.$u.api.getUserMsg()// 点赞消息数量if (res.statusCode === 200) {this.show = falsereturn} else {this.show = true}wx.getSetting({success: res => {if (res.authSetting["scope.userInfo"]) {uni.getUserInfo({success: res => {// 如果用户授权了,则做两件事,第一件事this.form.login = res.userInfo.nickNamethis.form.name = res.userInfo.nickNamethis.form.avatar = res.userInfo.avatarUrlthis.getUserInfoTag = false},fail: () => {console.log('用户未授权!')}})}}})},methods: {// 关闭弹窗closeLogin() {this.show = false},// 打开登陆弹窗openLogin() {this.show = true},// 跳转到 H5 页面gotoWeb(url) {wx.navigateTo({url: '/pages/webview/webview?url=' + encodeURI(url)});},// 微信授权getWechatUserInfo() {uni.getUserInfo({success: res => {// 进行下一步操作this.getUserInfoTag = falsethis.form.login = res.userInfo.nickNamethis.form.name = res.userInfo.nickNamethis.form.avatar = res.userInfo.avatarUrl},fail: () => {console.log('用户未授权!')}})},// 获取验证码getCode() {uni.showModal({title: '验证码获取成功',content: '8888'})},// 取消表单输入cancel() {this.form = {login: '',name: '',phone: '',email: '',code: '',password: '',repassword: ''}},// 提交表单验证submit() {this.$refs.uForm.validate(async valid => {if (!valid) {uni.showToast({title: '请检查输入',icon: 'loading'})return false}switch (this.loginType) {case "login":let resa = await this.$u.api.userLogin({login: this.form.login,password: this.form.password})if (resa.statusCode === 200) {// 登陆成功this.loginAfter(resa.data.access_token)} else {uni.showModal({title: '登陆失败',content: resa.data.message})}break;case "phone":let resb = await this.$u.api.userRegister({// 必须,用户名name: this.form.name,// 必须,验证码发送模式。verifiable_type: 'sms',// 必须,用户收到的验证码。(教学阶段验证码统一使用 8888 )verifiable_code: '8888',// 如果 `verifiable_type` 为 `sms` 则必须, 手机号码。phone: this.form.phone,// 可选,密码,如果不输入密码,允许用户无密码注册。password: this.form.password})console.log(resb)if (resb.statusCode === 201) {// 登陆成功this.loginAfter(resb.data.token)} else {uni.showModal({title: '登陆失败',content: resb.data.message})}break;case "email":let resc = await this.$u.api.userRegister({// 必须,用户名name: this.form.name,// 必须,验证码发送模式。verifiable_type: 'mail',// 必须,用户收到的验证码。(教学阶段验证码统一使用 8888 )verifiable_code: '8888',// 如果 `verifiable_type` 为 `mail` 则必须, E-Mail。email: this.form.email,// 可选,密码,如果不输入密码,允许用户无密码注册。password: this.form.password})console.log(resc)if (resc.statusCode === 201) {// 登陆成功this.loginAfter(resc.data.token)} else {uni.showModal({title: '登陆失败',content: resc.data.message})}break;default:uni.showToast({title: '未知用户状态',icon: 'loading'})break;}});},// 注册、登陆成功后设置相关逻辑async loginAfter(token) {this.show = falseuni.setStorageSync('token', token)// 获取未读消息提示let res = await this.$u.api.getUserMsg()let name = this.form.nameif (this.loginType === 'login') {name = this.form.login}let loginInfo = {name,avatar: this.form.avatar,liked: res.data.user.liked,commented: res.data.user.commented}this.userLoginAction(loginInfo)uni.$emit('userLogin')},// 更改 登陆 注册 方式选择sectionChange(index) {switch (index) {case 1:this.loginType = "phone";break;case 2:this.loginType = "email";break;default:this.loginType = "login";break;}}}}
</script>

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 = truestate.userInfo = userInfouni.setStorageSync('loginState', 'ok')uni.setStorageSync('userInfo', JSON.stringify(userInfo))},userLogout(state) {state.loginState = falsestate.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 = 0this.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. 使用 瀑布流 插件实现排版布局
<u-waterfall v-model="feedsList" ref="waterfall"><template v-slot:left="{leftList}">...</template><template v-slot:left="{leftList}">...</template>
</u-waterfall>
  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 = 0this.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. 登陆插件引入及登陆状态判断
<!-- 登陆组件 -->
<login ref="login"></login>
// 判断当前登录状态
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) returnthis.disableSendCommentTag = trueif (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. 公共组件 发布按钮 ,注意用户未登录状态下不显示这个按钮
<template><view class="goto-share" v-if="loginState"><!-- 个人分享 选择器 --><image class="share-img" src="@/static/carm.png" @tap.stop="gotoShare()" mode="aspectFit" /></view>
</template>
<script>import {mapState} from 'vuex'export default {data() {return {};},computed: {...mapState(['loginState'])},methods: {// 底部个人分享 弹窗相关操作gotoShare() {uni.navigateTo({url: "/subpages/share/share",})}}};
</script>
  1. 构建 share 页面用来专门实现动态上传能力
<view class="share"><!-- 内容输入框 --><view class="uni-textarea"><textarea placeholder-style="color:#AAAAAA" placeholder="添加描述..." @input="bindTextAreaInput" /></view><!-- 内容发布 --><view class="pics"><view class="medias" v-for="( image, index ) in uploadPicsList" :key="index"><imageclass="img":src="image.path":data-src="image"@tap="previewImage(index)"mode="aspectFill"/><u-icon name="close" class="iclose" color="#eee" size="20" @tap="removeImage(index)" /></view><!-- 选择照片按钮 --><view class="uploadBtn" @tap="chosePicsAndUpload" v-if="uploadPicsList.length < 9"><u-icon name="plus" size="60" color="#aaa" /><view class="text">选择照片</view></view></view><view class="btns"><u-button type="primary" size="default" @click="sendFeed" :disabled="uploadStatus">发布动态</u-button></view></view>
  1. 封装 uploadFile 公共工具
import MD5 from "@/common/md5.js";
const uploadFile = async (path, size) => {let uploadUrl = 'http://47.115.83.135/api/v2'// 第一步 获取 hash 值let hash = await new Promise((resolve, reject) => {uni.getFileInfo({filePath: path,success: (result) => {resolve(result.digest);},});});// console.log(MD5.md5(hash))// 第二步 请求获取基础文件信息// let check = await uni.request({//  url: uploadUrl+'/files/uploaded/'+MD5.md5(hash),//  method: 'GET',//  header: {//    "content-type": "application/json",//    Accept: "application/json",//    Authorization: "Bearer " + uni.getStorageSync("token"),//  }// })// 如果返回的状态码是404,那说明没有上传过,继续后面的上传流程// 如果返回的是200,那就从body里取出 id,这个id就可以直接使用,后面的上传流程就可以终止了// console.log(check[1].statusCode)// 第三步 将文件以buffer流方式存入系统let ufile = await uni.uploadFile({url: uploadUrl + '/files',method: "POST",header: {'content-type': 'multipart/form-data',Authorization: "Bearer " + uni.getStorageSync("token"),},name: 'file',file: uni.getFileSystemManager().readFileSync(path),filePath: path});// console.log(JSON.parse(ufile[1].data))return JSON.parse(ufile[1].data)
};
export default uploadFile;
  1. 使用 primose.all 来确保多张照片能够顺利上传
let upStatusArr = [];
// 循环遍历上传多图
this.uploadPicsList.map((item) => {upStatusArr.push(uploadFile(item.path, item.size));
});
let images = [];
(await Promise.all(upStatusArr)).map((item) => {images.push({id : item.id});
});
let pres = await this.$u.api.postOneFeed({feed_content: this.feedInfo,feed_from: 5,feed_mark: new Date().getTime(),images
});
  1. 动态发布成功后通知 index me 页面更新
// 通知个人中心 动态更新了
uni.$emit("indexFeedsUpdate")
// 通知首页 动态更新了
uni.$emit("myFeedsUpdate");
// index 发布新的动态后,触发数据更新
uni.$on("indexFeedsUpdate", ()=>{this.currentSwiperIndex = 0this.feedsList = []this.$refs.waterfall.clear()this.getFeeds()
})// me 用户发布一条动态后触发数据更新
uni.$on('myFeedsUpdate', ()=>{this.$refs.waterfall.clear()this.getInfos()
})

六、多端打包发布

6.1 微信小程序打包发布

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

6.2 H5 端相关兼容性开发及打包发布

关键调整节点:

  1. manifest.json H5 下出现的跨域问题要进行相关配置
"h5" : {"devServer" : {"port" : 8000, //端口"disableHostCheck" : true,"proxy" : {//使用代理"/api" : {"target": "http://47.115.83.135/api/v2","changeOrigin" : true,"pathRewrite" : {"^/api" : ""}}}}
}
  1. newinfo \ feedinfo 微信分享仅仅在微信下有效
// #ifdef MP-WEIXIN
// 微信条件下分享到朋友圈、群组
wx.showShareMenu({withShareTicket: true,menus: ['shareAppMessage', 'shareTimeline']
})
// #endif
  1. comment 组件分享按钮仅仅在 微信小程序下展示,同步 css 调整
<!-- 转发次数统计 -->
<!-- #ifdef MP-WEIXIN -->
<button class="mbtn mhare" open-type="share"><image class="micon" src="/static/wx.png" mode="aspectFit" /><text class="mtext">分享</text>
</button>
<!-- #endif -->
.minput {background-color: #eee;height: 60upx;// #ifdef MP-WEIXINwidth: 300upx;// #endif// #ifndef MP-WEIXINwidth: 440upx;// #endif
}
  1. login 登陆组件获取用户信息逻辑调整
// #ifdef MP-WEIXINwx.getSetting({success: res => {if (res.authSetting["scope.userInfo"]) {uni.getUserInfo({success: res => {// 如果用户授权了,则做两件事,第一件事this.form.login = res.userInfo.nickNamethis.form.name = res.userInfo.nickNamethis.form.avatar = res.userInfo.avatarUrlthis.getUserInfoTag = false},fail: () => {console.log('用户未授权!')}})}}})
// #endif
// #ifndef MP-WEIXINthis.getUserInfoTag = false
// #endif
  1. http.api.js API 管理器 文件上传逻辑调整
// 文件上传操作
api.uploadFile = async file =>{let rfile = file// #ifdef MP-WEIXINrfile = uni.getFileSystemManager().readFileSync(file.path)// #endif// 将文件写入后台系统系统let ufile = await uni.uploadFile({url: vm.$u.http.config.baseUrl + '/files',header: {Authorization: "Bearer " + uni.getStorageSync("token"),},name: 'file',file: rfile,filePath: file.path});console.log(JSON.parse(ufile[1].data))return JSON.parse(ufile[1].data)
}

PS: 可以使用托管工具,快速发布体验,尤其需要注意的是:开发的时候配置的代理服务器,在正式环境打包的时候是不会被调用的,所以要更改对应的配置信息

6.3 安卓云端体验版本打包发布

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/635418.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

小白水平理解面试经典题目LeetCode 121 Best Time to Buy and Sell Stock

121 Best Time to Buy and Sell Stock (买卖股票的最佳时机) 你好&#xff0c;2024年的第一个月&#xff0c;又是秋风萧瑟天气凉&#xff0c;草木摇落露为霜。.。。在这个特殊的时代&#xff0c;作为我们普通的一个打工人&#xff0c;我们用这道题&#xff0c;开启对这个不符合…

2023全球边缘计算大会深圳站:核心内容与学习收获(附大会核心PPT下载)

边缘计算作为当今IT领域的热门话题&#xff0c;已经引起了全球范围内的广泛关注。本次大会汇聚了众多业界精英&#xff0c;共同探讨边缘计算的发展趋势、技术应用与创新实践。本文将围绕大会的核心内容展开讨论&#xff0c;并分析参会者从中能够学到的东西。 一、边缘计算的发…

C++PythonC# 三语言OpenCV从零开发(2):教程选择

文章目录 相关专栏前言视频教学和官方文档视频教程OpenCV 官方教程最终选择我的最终选择 相关专栏 C&Python&Csharp in OpenCV 前言 OpenCV 有官方的教程和简单的视频教程&#xff1a; OpenCV 官方教程 B站也有相关的视频教学 OpenCV4 C 快速入门视频30讲 - 系列合集 …

【Java】JDBC的使用

JDBC package jdbc_demo;import java.sql.Connection; import java.sql.DriverManager; import java.sql.Statement;public class jdbc {public static void main(String[] args)throws Exception {//1.注册驱动Class.forName("com.mysql.cj.jdbc.Driver");//2.获取…

【开源】基于JAVA语言的教学资源共享平台

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 数据中心模块2.2 课程档案模块2.3 课程资源模块2.4 课程作业模块2.5 课程评价模块 三、系统设计3.1 用例设计3.2 类图设计3.3 数据库设计3.3.1 课程档案表3.3.2 课程资源表3.3.3 课程作业表3.3.4 课程评价表 四、系统展…

逸学Docker【java工程师基础】3.4Docker安装redis

1.拉取redis docker pull redis 2.选择一个合适的redis 版本的配置文件 Redis configuration | Redis 或者这个 链接&#xff1a;https://pan.baidu.com/s/1RRdtgec4xBAgQghlhm0x1Q 提取码&#xff1a;ycyc 在1044行修改密码 3.提前在服务器建立 /data/redis 文件夹&…

【华为 ICT HCIA eNSP 习题汇总】——题目集1

1、&#xff08;多选&#xff09;根据下面所示的命令输出&#xff0c;下列描述中正确的是&#xff1f; A、GigabitEthernet0/0/1 允许VLAN1通过 B、GigabitEthernet0/0/1 不允许VLAN1通过 C、如果要把 GigabitEthernet0/0/1 变为 Access 端口&#xff0c;首先 需要使用命令“un…

2023 年,我患上了 AI 焦虑症!

【作者有话说】2023 年对我来说是神奇的一年&#xff0c;我意外地从一个程序员变成了一个 AI 资讯届的“网红”&#xff0c;到年底时我在 X 平台的阅读量超过 1 亿&#xff0c;微博上的阅读量则超过 10 亿&#xff0c;很多人通过我的微博或者 X 了解最新的 AI 资讯、教程和 Pro…

SpringMVC下半篇之整合ssm

4.ssm整合 4.1.创建表 CREATE TABLE account (id int(11) NOT NULL AUTO_INCREMENT,name varchar(20) DEFAULT NULL,money double DEFAULT NULL,PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8;4.2.创建工程 4.3.pom.xml <?xml version"1.0" encoding&…

蓝桥杯备战 每日一题 (4)

题目地址 首先我们要有一个知识储备 1 加法&#xff1a;(ab)%m(a%mb%m)%m 2 减法&#xff1a;(a-b)%m(a%m-b%m)%m 3 乘法&#xff1a;a*b%m(a%m)*(b%m)%m 我们可以每次计算就可以取余一次&#xff0c;这样就保证了最后取余的结果和一起相加再取余的结果一样 然后这个题目要怎…

【LeetCode】数学精选4题

目录 1. 二进制求和&#xff08;简单&#xff09; 2. 两数相加&#xff08;中等&#xff09; 3. 两数相除&#xff08;中等&#xff09; 4. 字符串相乘&#xff08;中等&#xff09; 1. 二进制求和&#xff08;简单&#xff09; 从字符串的右端出发向左做加法&#xff0c;…

SQLAlchemy ORM指南:简化数据库操作的最佳实践

SQLAIchemy 开发指南 背景&#xff1a; ​ SQLAlchemy是一个数据库的ORM框架&#xff0c;让我们操作数据库的时候不要再用SQL语句了&#xff0c;跟直接操作模型一样。操作十分便捷&#xff0c;其实SQLAlchemy应该是在Flask和Django应用的特别多&#xff0c;而且在flask中已经…

Oracle架构_数据库底层原理、机制 (授人以渔)

目录 系统全局区SGA 高速缓存缓冲区(数据库缓冲区) 日志缓冲区 共享池 其他结构 用户连接进程 用户进程User Process Server Process服务进程 程序全局区PGA Oracle的connect连接和session会话与User Process紧密相关 后台进程 数据库写入进程(DBWn) 检查点(CKPT)…

多维时序 | Matlab实现CNN-LSTM-Mutilhead-Attention卷积长短期记忆神经网络融合多头注意力机制多变量时间序列预测

多维时序 | Matlab实现CNN-LSTM-Mutilhead-Attention卷积长短期记忆神经网络融合多头注意力机制多变量时间序列预测 目录 多维时序 | Matlab实现CNN-LSTM-Mutilhead-Attention卷积长短期记忆神经网络融合多头注意力机制多变量时间序列预测效果一览基本介绍程序设计参考资料 效果…

“深入理解 Docker 和 Nacos 的单个部署与集成部署“

目录 引言&#xff1a;Docker Nacos 单个部署1.1 什么是 Docker&#xff1f;Docker 的概念和工作原理Docker 为什么受到广泛应用和认可 1.2 什么是 Nacos&#xff1f;Nacos 的核心功能和特点Nacos 在微服务架构中的作用 1.3 Docker 单个部署 Nacos Docker Nacos 集成部署总结&a…

【重点!!!】【背包】【回溯】518.零钱兑换II

题目 跟39.组合总数、322.零钱兑换题目很类似。 法1&#xff1a;背包DP&#xff0c;最优解法 解释如下&#xff1a; 0 1 2 3 4 5(背包容量)1 0 0 0 0 0 没有硬币的时候&#xff09; 0 1 2 3 4 5(背包容量) 1 1 1 1 1 1 1 0 1 2 3 4 5(背包容量) 1 …

Ubuntu 22.04 安装MySql

MySQL是非常常用的关系型数据库,无论是大厂还是小厂,都有它的身影。最大的优点是免费,安装起来也比较简单。 MySQL的架构 画了个简图,描述了下MySQL的架构。 其中的比较有趣的点在于连接池和存储引擎。连接池缓存了数据库和客户端的TCP连接,以减少建立连接的开销。存储引…

git中合并分支时出现了代码冲突怎么办

目录 第一章、Git代码冲突介绍1.1&#xff09;什么是Git代码冲突①git merge命令介绍②代码冲突原因 1.2&#xff09;提示代码冲突的两种情况①本地不同分支的文件有差异时&#xff1a;②本地仓库和git远程仓库的文件有差异时&#xff1a; 1.3&#xff09;解决合并时的代码冲突…

calloc与realloc和malloc的区别以及new

目录 calloc、realloc 和 malloc 三个函数的区别在于 更详细的示例代码 交叉使用 内存泄漏 悬空指针 内存重叠 new 的语法 使用 new 运算符在堆上创建学生对象的示例 new和malloc都可以用于在堆上分配内存 calloc、realloc 和 malloc 是 C/C 中用于动态内存分配的函…

Mermaid使用教程(绘制各种图)

Mermaid使用教程&#xff08;绘制各种图&#xff09; 文章目录 Mermaid使用教程&#xff08;绘制各种图&#xff09;简介饼状图简单的例子应用案例 序列图简单案例应用案例另一个应用案例 甘特图简单案例应用案例一个更为复杂的应用案例 Git图简单案例 总结 简介 本文将主要介…