uni-app入门

931 阅读12分钟

笔记来源:拉勾教育 - 大前端就业集训营

文章内容:学习过程中的笔记、感悟、和经验

提示:项目实战类文章资源无法上传,仅供参考

uni-app

基于Vue+微信小程序的语言体系,可实现跨端开发

截屏2021-08-26 13.36.38

uni-app简介

uni-app是一个使用Vue开发所有前端应用的框架,开发者编写一套代码,可发布到IOS、Android、H5以及各种小程序(微信、支付宝、百度、头条、钉钉)等多个平台,方便开发者快速交付,不需要转换开发思维和开发习惯

为什么选择uniapp?

  • 开发者、案例众多
  • 平台能力不受限,通过条件编译+平台特有API,可不影响其他平台
  • 性能体验优秀
  • 周边生态丰富
  • 学习成本低

截屏2021-08-26 13.45.26

uni-app可以调用各类端的SDK、API供给自己使用,从而可以生成多端代码

开发环境搭建 - HBuilderX

HBuilderX是通用的前端开发工具,但为了uni-app做了特别强化

这里建议安装app开发版,可开箱即用,否则在运行和发布时会提示安装插件

  • 下载HBuilderX - 安装
  • 新建项目,选择uni-app,输入项目名,创建项目
  • 选择运行即可在多端运行程序,多端可同时运行

初始化相关配置

工程目录结构

截屏2021-08-26 14.34.45

注意:static静态资源目录下面的文件不会被webpack打包,所以需要使用webpack打包的文件不能放到这里

  • 应用配置 manifest.json

    • 文件是应用的配置文件,可以以可视化方式展示吗,用于指定应用的名称、图标、权限等,我们也可以在这 里为 Vue 为H5 设置跨域拦截处理器
    • 在这里我们需要填写一下小程序appId
  • 编译配置 - vue.config.js

    • 初始化状态中没有,一个可选的配置文件,用于配制webpack编译选项,官方文档
  • 全局配置 - page.json - 官方文档

    • 对uni-app进行全局配置,决定页面文件路径、窗口样式、原生导航栏、底部原生tabbar等,类似于小程序的app.json

    • 属性类型必填描述
      globalStyleObject设置默认页面的窗口表现
      pagesObject Array设置页面路径及窗口表现
      easycomObject组件自动引入规则
      tabBarObject设置底部 tab 的表现
      conditionObject启动模式配置
      subPackagesObject Array分包加载配置
      preloadRuleObject分包预下载规则
  • 全局样式 - uni.scss - 官方文档

    • 方便整体控制应用风格,比如按钮颜色、边框风格,还预置一批CSS变量
    • 是一个特殊文件,在代码中无需 import 这个文件即可在scss代码中使用这里的样式变量
  • 主组件 - app.vue

    • 注意本身不是页面,不能书写teamplte语法
    • 这个文件的作用包括:调用应用生命周期函数、配置全局样式、配置全局的存储globalData
    • 应用生命周期仅可在App.vue中监听,在页面监听无效。
  • 入口文件 - main.js - 官方文档

    • 和vue相似,都是程序的入口文件

uni-app开发规范和资源路径

开发规范约定

  • 页面但文件和vue使用相同,从上到下依次为HTML、js、css
  • 组件标签靠近小程序规范,没有使用p、div等标签,而是使用view、text等小程序标签
  • 互连能力(JS API)靠近微信小程序规范,但需要将替换替换 wx 为 uni ,详见uni-app接口规范
  • 数据绑定及事件处理同 Vue.js 规范,同时补充了 App 和页面的生命周期
  • 为兼容多端运行,建议使用 flex 布局进行开发

资源路径说明

注意:在写uni-app代码时,引入路径都建议使用绝对路径(@/)的方式书写,不建议使用相对路径,但也不是不可以使用

  • template内约会静态资源,如 image,video 等标签的 src 属性时,可以使用相对路径或绝对路 径,形式如下:

    • <!-- 绝对路径,/static指根目录下的static目录,在cli项目中/static指src目录下的static目录 --> 
      <image class="logo" src="/static/logo.png"></image> 
      <image class="logo" src="@/static/logo.png"></image>
      
      <!-- 相对路径 --> 
      <image class="logo" src="../../static/logo.png"></image>
      
    • @ 初始的绝对路径以及相对路径会通过 base64 转换规则校验

    • 约会的静态资源在非h5 平台,均不转为 base64

    • H5平台,小于4kb的资源会被转换成base64,其余不转

  • js 文件或 script 标签内,可以使用相对路径和绝对路径,形式如下:

    • // 绝对路径,@指向项目根目录,在cli项目中@指向src目录
      import add from '@/common/add.js' 
      
      // 相对路径 
      import add from '../../common/add.js'
      
  • css 文件或 style 标签内引用的图片路径,可以使用相对路径也可以使用绝对路径,形式如下:

    • /* 绝对路径 */ 
      background-image: url(/static/logo.png); 
      background-image: url(@/static/logo.png); 
      
      /* 相对路径 */ 
      background-image: url(../../static/logo.png);
      

生命周期

首先uni-app支持完整的Vue生命周期钩子,同时还新增了应用程序和页面生命周期

应用生命周期:针对整个项目,在app.vue中

函数名说明
onLaunch当uni-app初始化完成时触发(全局只触发一次)
onShow当uni-app启动,或从后台进入前台显示
onHide当uni-app从前台进入后台
onError当uni-app报错时触发
onUniNViewMessage对nvue页面发送的数据进行监听,可参考 nvue 向 vue 通讯
onUnhandledRejection对未处理的 Promise 拒绝事件监听函数(2.8.1+)
onPageNotFound(常用 )页面不存在监听函数
onThemeChange监听系统主题变化

页面生命周期:针对页面

函数名说明
onLoad监听页面加载,其参数为上个页面传递的数据,参 数类型为Object(用于页面传参),参考示例
onShow监听页面显示。页面每次出现在屏幕上都触发,包 括从下级页面点返回露出当前页面
onReady监听页面初次渲染完成。注意如果渲染速度快,会 在页面进入动画完成前触发
onHide监听页面隐藏
onUnload监听页面卸载
onResize监听窗口尺寸变化
onPullDownRefresh监听用户下拉动作,一般用于下拉刷新,需要在pages.json中允许
onReachBottom页面滚动到底部的事件(不是scroll-view滚到底), 常用于下拉下一页数据。具体见下方注意事项
onTabItemTap点击 tab 时触发,参数为Object,具体见下方注意 事项
onShareAppMessage用户点击右上角分享
onPageScroll监听页面滚动,参数为Object
onNavigationBarButtonTap监听原生标题栏按钮点击事件,参数为Object
onBackPress监听页面返回
onNavigationBarSearchInputChanged监听原生标题栏搜索输入框输入内容变化事件
onNavigationBarSearchInputConfirmed监听原生标题栏搜索输入框搜索事件,用户点击软 键盘上的“搜索”按钮时触发。
onNavigationBarSearchInputClicked监听原生标题栏搜索输入框点击事件
onShareTimeline监听用户点击右上角转发到朋友圈
onAddToFavorites监听用户点击右上角收藏

路由配置及页面跳转

路由配置

uni-app 页面路由全部交给框架统一管理,开发者需要在pages.json里配置每个路由页面的路径及页面样式(类似小程序在 app.json 中配置页面路由)。

"pages": [
  {
    "path": "pages/index/index",
    "style": {
      "navigationBarTitleText": "uni-app"
    }
  },{
    "path": "pages/new/new",
    "style": {
      "navigationBarTitleText": "new"
    }
  },
]

路由跳转

有两种页面路由跳转方式:

  • 使用navigator组件跳转(标签式导航)
  • 调用API跳转(编 程式导航)

uni-app框架以栈的形式管理当前所有页面, 当发生路由切换的时候,页面栈的表现如下:

路由方式页面栈表现触发时机
初始化新页面入栈uni-app打开的第一个页面
打开新页 面新页面入栈调用API - uni.navigateTo、 使用组件
页面重定 向当前页面出栈,新页面入栈调用APl uni.redirectTo、 使用组件
页面返回页面不断出栈,直到目标返回页调用APl uninavigateBack、 使用组件、 用户按左上角返回按钮、安卓用户点击物理 back按键
Tab切换页面全部出栈,只留下新的Tab 页面调用APl uni.switchTab、 使用组件、 用户切换Tab
重加载页面全部出栈,只留下新的页面0调用APl uni.reLaunch、 使用组件

注意:当页面设置了tabbar的时候,其他涉及该页面的跳转uni.navigateTo都会失效,要改成uni.switchTab才能正常跳转

获取当前页面栈

getCurrentPages() 函数用于获取当前页面栈的实例,以数组形式按栈的顺序给出,第一个元素为首页,最后一个元素为当前页面。

注意:getCurrentPages() 仅用于展示页面栈的情况,请勿修改页面栈,以免造成页面状态错误。

路由传递与接受

页面传递参数:直接在跳转路由后面使用?参数名=参数值&参数名=参数值进行传递

//页面跳转并传递参数 
uni.navigateTo({
  url: 'page2?name=liy&message=Hello' 
});

页面接受参数:跳转目标页面的onLoad函数中的参数option可以获取传递过来的参数

// 页面 2 接收参数 

//option为object类型,会序列化上个页面传递的参数
onLoad: function (option) { 
  console.log(option.name); //打印出上个页面传递的参数。
  console.log(option.message); //打印出上个页面传递的参数。 
}

注意:url 有长度限制,太长的字符串会传递失败,并且不规范的字符格式也可能导致传递失败, 所以对于复杂参数建议使用 encodeURI、decodeURI 进行处理后传递

小程序路由分包配置

因小程序有体积和资源加载限制,各家小程序平台提供了分包方式,优化小程序的下载和启动速度。

所谓的主包,即放置默认启动页面及 TabBar 页面,而分包则是根据 pages.json 的配置进行划 分。

在小程序启动时,默认会下载主包并启动主包内页面,当用户进入分包内某个页面时,会把对应分 包自动下载下来,下载完成后再进行展示,此时终端界面会有等待提示。

// 分包目录只需要在第一次创建时需要手动书写,后期直接在目录下创建页面无需自己手动书写会自动添加进分包json中

// 分包配置
"subPackages": [{
   // 分包目录
  "root": "subPackages",
   // 目录下页面,写法和主包一样
  "pages": [{
    "path": "chat/chat",
    "style": {
      "navigationBarTitleText": "和知心大哥聊天",
      "enablePullDownRefresh": false
    }
  }]
}],
// 分包预下载
"preloadRule": {
  // 页面路径
  "subPackages/chat/chat": {
    // 下载条件:all - 任何网络下
    "network": "all",
    "packages": ["__APP__"]
  }
},

常用组件介绍

uni-app提供给我们一系列类似于HTML的标签元素,但是和HTML不同,而是与小程序相同,更适合手机端使用

虽然不推荐使用html标签,但是也可以使用,在变异成非h5平台时会进行自动转换,但是依旧不推荐使用

常用API

h5端运行于浏览器中,非 h5 端 Android 平台运行在 v8 引擎中,iOS 平台 运行在 iOS 自带的 jscore 引擎中。所以, uni-app 的 jsAPI 由标准 ECMAScript 的 js API 和 uni 扩展 API 这两部分组成。

ECMAScript 由 Ecma 国际管理,是基础 js 语法。浏览器基于标准 js 扩充了window、document 等 js API;Node.js 基于标准 js 扩充了 fs 等模块;小程序也基于标准 js 扩展了各种 wx.xx、 my.xx、swan.xx 的 API。

标准 ecmascript 的 API 非常多,比如:console、settimeout等等。

非 H5 端,虽然不支持 window、document、navigator 等浏览器的 js API,但也支持标准 ECMAScript。

开发者不要把浏览器里的 js 等价于标准 js。

所以 uni-app 的非 H5 端,一样支持标准 js,支持 if、for 等语法,支持字符串、数组、时间等变 量及各种处理方法,仅仅是不支持浏览器专用对象

自定义组件

自定义组件

和Vue相同,在项目的 component 目录下存放组件, uni-app 只支持 vue 单文件组件(.vue 组件)

组件可以使用「全局注册」和「页面引入」两种方式进行使用,使用分为三步:

  • 导入:import xxx from 'xxx'

  • 注册:Vue.use('xx',xx) - 全局,components:{ xxx } - 组件

  • 使用:<xx />

组件间通信

父子组件通信

  1. 父组件通过自定义属性向子组件传递数据

  2. 子组件通过 props 接收父组件传递的数据

  3. 父组件通过自定义事件标签向子组件传递事件

  4. 子组件通过触发父组件定义事件方式修改父组件数据

slot 数据分发与作用域插槽

  1. 父组件通过调用子组件内部嵌套 html 内容作为 slot 分发给子组件

  2. 子组件通过在 slot 标签上添加属性,向父组件通信数据,作用域插槽

全局事件定义及通信

  1. 在整个应用的任何地方均可以使用 uni.$on(事件名,回调函数(参数))创建一个全局事件

  2. 在整个应用的任何地方也均可以使用 uni.$emit(事件名,参数)来触发全局事件,实现多组件见的数据通 信

状态管理- vueX

  1. 概念

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的 状态,并以相应的规则保证状态以一种可预测的方式发生变化。

  1. 应用场景

Vue多个组件之间需要共享数据或状态。

  1. 关键规则
  • State:存储状态数据
  • Getter:从状态数据派生数据,相当于 State 的计算属性
  • Mutation:存储用于同步更改状态数据的方法,默认传入的参数为 state
  • Action:存储用于异步更改状态数据,但不是直接更改,而是通过触发 Mutation 方法实 现,默认参数为context
  • Module:Vuex 模块化
// 创建vuex
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)

const store = new Vuex.Store({
	state:{
		userName: uni.getStorageSync('userName') ? uni.getStorageSync('userName') : '未登录用户'
	},
	mutations:{
		MLOGIN(state, userName){
			uni.setStorageSync('userName', userName)
			state.userName = userName
		},
		MLOGOUT(state){
			uni.clearStorageSync()
			state.userName = '退出状态用户'
		}
	},
	actions:{
		login(context, userName){
			context.commit('MLOGIN', userName)
		},
		logout(context){
			context.commit('MLOGOUT')
		}
	}
})

export default store
// 使用vuex
import Vue from 'vue'
import App from './App'

Vue.config.productionTip = false

App.mpType = 'app'

// 调用 store vuex 状态管理
import store from '@/store/index.js'

if (process.env.NODE_ENV === 'development') {
	console.log('开发环境')
} else {
	console.log('生产环境')
}

const app = new Vue({
	...App,
	store
})
app.$mount()
  1. 使用方式
// mapState - 获取全部state,mapActions - 获取全部Actions
import { mapState, mapActions } from 'vuex' 
export default { 
  // 计算属性:直接解构
  computed: { ...mapState(['loginState', 'userInfo']) }, 
  // 方法 - 直接解构
  methods: { ...mapActions(['userLoginAction','userLogoutAction'])}
}

运行环境与跨端兼容

开发环境和生产环境

uni-app可通过 process.env.NODE_ENV判断当前环境是开发环境还是生产环境,一般用于连接测试服务器或生产服务器的动态切换。

在HBuilderX 中,点击「运行」编译出来的代码是开发环境,点击「发行」编译出来的代码是生产 环境

if(process.env.NODE_ENV === 'development'){
  console.log('开发环境') 
}else{
  console.log('生产环境') 、
}

判断平台

平台判断有2种场景,一种是在编译期判断,一种是在运行期判断。

(推荐)编译期判断编译期判断,即条件编译,不同平台在编译出包后已经是不同的代码

// #ifdef H5 
alert("只有h5平台才有alert方法") 
// #endif 

// 如上代码只会编译到H5的发行包里,其他平台的包不会包含如上代码。

运行期判断 运行期判断是指代码已经打入包中,仍然需要在运行期判断平台,此时可使用 uni.getSystemInfoSync().platform 判断客户端环境是 Android、iOS 还是小程序开发工具

switch(uni.getSystemInfoSync().platform){
  case 'android':
    console.log('运行Android上') 
    break; 
  case 'ios':
    console.log('运行iOS上') 
    break; 
  default:
    console.log('运行在开发者工具上') 
    break; 
}

跨端兼容

uni-app 已将常用的组件、JS API 封装到框架中,开发者按照 uni-app 规范开发即可保证多平台兼 容,大部分业务均可直接满足,但每个平台有自己的一些特性,因此会存在一些无法跨平台的情况。

  • 大量写 if - else,会造成代码执行性能低下和管理混乱。
  • 编译到不同的工程后二次修改,会让后续升级变的很麻烦。

在 C 语言中,通过 #ifdef、#ifndef 的方式,为 windows、mac 等不同 os 编译不同的代码。 uni-app 参考这个思路,为 uni-app 提供了条件编译手段,在一个工程里优雅的完成了平台个性化实现。

条件编译是用特殊的注释作为标记,在编译时根据这些特殊的注释,将注释里面的代码编译到不同 平台。

写法:以 #ifdef 或 #ifndef 加 %PLATFORM% 开头,以 #endif 结尾。

  • \#ifdef:if defined 仅在某平台存在
  • \#ifndef :if not defined 除了某平台均存在
  • %PLATFORM%:平台名称

%PLATFORM% 可取值如下:

平台
APP-PLUSApp
APP-PLUS-NVUEApp nvue
H5H5
MP-WEIXIN微信小程序
MP-ALIPAY支付宝小程序
MP-BAIDU百度小程序
MP-TOUTIAO字节跳动小程序
MP-QQQQ小程序
MP-360360小程序
MP微信小程序/支付宝小程序/百度小程序/字节跳动小程序/QQ小程 序/360小程序
QUICKAPP-WEBVIEW快应用通用(包含联盟、华为)
QUICKAPP-WEBVIEW- UNION快应用联盟
QUICKAPP-WEBVIEW- HUAWEI快应用华为

知心大哥项目练习

  • 首页可以更改vuex数据
  • 知心大哥页面可实现对话功能
{
	"pages": [
		{
			"path": "pages/index/index",
			"style": {
				"navigationBarTitleText": "uni-app"
			}
		}, {
			"path": "pages/me/me",
			"style": {
				"navigationBarTitleText": "",
				"enablePullDownRefresh": false
			}

		}, {
			"path": "pages/404/404",
			"style": {
				"navigationBarTitleText": "",
				"enablePullDownRefresh": false
			}

		}, {
			"path": "pages/list/list",
			"style": {
				"navigationBarTitleText": "",
				"enablePullDownRefresh": false
			}

		}, {
			"path": "pages/find/find",
			"style": {
				"navigationBarTitleText": "",
				"enablePullDownRefresh": false
			}

		}, {
			"path": "subpages/chat/chat",
			"style": {
				"navigationBarTitleText": "",
				"enablePullDownRefresh": false
			}

		}
	],
  // 分包页面
	"subPackages": [{
		"root": "subPackages",
		"pages": [{
      // 知心大哥页面
			"path": "chat/chat",
			"style": {
				"navigationBarTitleText": "和知心大哥聊天",
				"enablePullDownRefresh": false
			}
		}, {
			"path": "new/new",
			"style": {
				"navigationBarTitleText": "",
				"enablePullDownRefresh": false
			}

		}]
	}],
  // 预加载
	"preloadRule": {
		"subPackages/chat/chat": {
			"network": "all",
			"packages": ["__APP__"]
		}
	},
	"globalStyle": {
		"navigationBarTextStyle": "black",
		"navigationBarTitleText": "uni-app",
		"navigationBarBackgroundColor": "#F8F8F8",
		"backgroundColor": "#F8F8F8"
	},
	"tabBar": {
		"color": "#7A7E83",
		"selectedColor": "#3cc51f",
		"borderStyle": "black",
		"backgroundColor": "#ffffff",
		"list": [{
			"pagePath": "pages/index/index",
			"iconPath": "static/tabbar-icons/wx.png",
			"selectedIconPath": "static/tabbar-icons/wx_s.png",
			"text": "微信"
		}, {
			"pagePath": "pages/list/list",
			"iconPath": "static/tabbar-icons/list.png",
			"selectedIconPath": "static/tabbar-icons/list_s.png",
			"text": "列表"
		}, {
			"pagePath": "pages/find/find",
			"iconPath": "static/tabbar-icons/find.png",
			"selectedIconPath": "static/tabbar-icons/find_s.png",
			"text": "发现"
		}, {
			"pagePath": "pages/me/me",
			"iconPath": "static/tabbar-icons/me.png",
			"selectedIconPath": "static/tabbar-icons/me_s.png",
			"text": "我"
		}]
	}
}

<template>
	<view class="content">
		<!-- 路由跳转 - 到和知心大哥聊天页面 -->
		<navigator url="/subPackages/chat/chat">去和和知心大哥聊天</navigator>
		<!-- 绑定vuex -->
		<text>用户名:{{userName}}</text>
		<!-- 按钮,实现登录和退出,直接调用vuex解构的方法 -->
		<button type="default" @click="login('cwn')">登录</button>
		<button type="default" @click="logout">退出</button>
	</view>
</template>

<script>
	// 调用vuex
	import {mapState, mapActions} from 'vuex'
	export default {
		data() {
			return {
			}
		},
		// 方法,从vuex解构出登录和退出两个方法
		methods: {
			...mapActions(['login','logout'])
		},
		// 计算属性,解构出数据
		computed: {
			...mapState(['userName'])
		}
	}
</script>

<style>
	.content {
		display: flex;
		flex-direction: column;
		align-items: center;
		justify-content: center;
	}

	.logo {
		height: 200rpx;
		width: 200rpx;
		margin-top: 200rpx;
		margin-left: auto;
		margin-right: auto;
		margin-bottom: 50rpx;
	}

	.text-area {
		display: flex;
		justify-content: center;
	}

	.title {
		font-size: 36rpx;
		color: #8f8f94;
	}
</style>
// 使用vuex

import App from './App'
// 引入创建的vuex实例
import store from '@/store/index.js'

// #ifndef VUE3
import Vue from 'vue'
Vue.config.productionTip = false
App.mpType = 'app'
const app = new Vue({
    ...App,
		// 使用vuex
		store
})
app.$mount()
// #endif

// #ifdef VUE3
import { createSSRApp } from 'vue'
export function createApp() {
  const app = createSSRApp(App)
  return {
    app
  }
}
// #endif
// 引入vue和vuex
import Vue from 'vue'
import Vuex from 'vuex'

// 全局注册vuex
Vue.use(Vuex)

// 创建实例
const store = new Vuex.Store({
	// 数据
	state: {
		// 从本地数据中获取
		userName: uni.getStorageSync('userName') ? uni.getStorageSync('userName') : '未登录用户'
	},
	// 修改方法
	mutations: {
		// 登录
		TOIN(state, userName) {
			state.userName = userName
			uni.setStorageSync('userName', userName)
		},
		// 退出
		TOOUT(state) {
			state.userName = '未登录'
			uni.setStorageSync('userName')
		},
	},
	// 异步方法
	actions: {
		// 登录
		login(context, userName) {
			context.commit('TOIN',userName)
		},
		// 退出
		logout(context) {
			context.commit('TOOUT')
		},
	}
})

export default store
<template>
	<view class="container">
		<!-- 聊天列表 -->
		<view class="chat-body">
			<!-- 创建一个虚拟标签,block不会渲染出来,然后遍历全部对话数据 -->
			<block v-for="(item,index) in chatList" :key='index'>
				<!-- 知心大哥结构,绑定数据 -->
				<view class="chat-one" v-if="!item.isme">
					<image class="chat-face" src="@/static/faces/1-thump.jpg" />
					<view class="chat-box">
						<view class="chat-sender">知心大哥</view>
						<view class="chat-content" v-if="item.type === 'txt'">{{item.content}}</view>
						<image class="chat-img" :src="item.content" mode="widthFix" v-else></image>
					</view>
				</view>
				<!-- 我的结构,绑定数据 -->
				<view class="chat-one chat-one-mine" v-else>
					<view class="chat-box">
						<view class="chat-content" v-if="item.type === 'txt'">{{item.content}}</view>
						<image class="chat-img" :src="item.content" mode="widthFix" v-else></image>
					</view>
					<image class="chat-face" src="@/static/faces/6-thump.jpg" />
				</view>
			</block>
		</view>
		<!-- 聊天输入 -->
		<view class="chat-footer">
			<!-- 输入框 -->
			<input class="msg-input" type="text" cursor-spacing="16" v-model="myInput"/>
			<!-- 选择图片啊扭 -->
			<image class="img-chose" src="@/static/img.png" @click="choseImgAndSend" />
			<!-- 发送按钮 -->
			<view class="send-btn" @click="sendMsg">发送</view>
		</view>
	</view>
</template>

<script>
	export default {
		data() {
			return {
				// 双向绑定的输入框数据
				myInput: '',
				// 对话数据
				chatList: [{
					isme: false,
					type: 'txt',
					content: '你好,我是可爱的知心大哥,请问你想和我聊什么呢?'
				}, {
					isme: false,
					type: 'img',
					content: '/static/faces/6-thump.jpg'
				}, {
					isme: true,
					type: 'txt',
					content: '哇,你好漂亮!!!!'
				}, {
					isme: true,
					type: 'img',
					content: '/static/faces/6-thump.jpg'
				}, ]
			}
		},
		// 页面显示时触发 - 钩子函数
		onShow() {
			// 判断是否可以读取到本地存储的对话数据
			if (uni.getStorageSync('chatList')) {
				// 替换数据
				this.chatList = JSON.parse(uni.getStorageSync('chatList'))
				// 滑动到底部
				setTimeout(() => {
					uni.pageScrollTo({
						scrollTop: 9999999,
						duration: 0
					})
				}, 50)
			}

		},
		methods: {
			// 发送图片事件
			choseImgAndSend() {
				// 调用API选择图片
				uni.chooseImage({
					// 仅1张
					count: 1,
					// 高清、压缩
					sizeType: ['original', 'compressed'],
					// 本地、相机拍照
					sourceType: ['album', 'camera'],
					// 成功后调用
					success: (res) => {
						// 添加数据 - 我说的话
						this.chatList.push({
							isme: true,
							type: 'img',
							content: res.tempFilePaths[0]
						})
						// 添加数据 - 知心大哥
						this.chatList.push({
							isme: false,
							type: 'img',
							content: res.tempFilePaths[0]
						})
						// 翻动到底部
						setTimeout(() => {
							uni.pageScrollTo({
								scrollTop: 9999999,
								duration: 100
							})
						}, 5)
						// 保存到本地
						uni.setStorageSync('chatList', JSON.stringify(this.chatList));
					}
				})
			},
			// 发送文字事件函数
			sendMsg() {
				// 判断是否输入
				if(this.myInput !== '') {
					// 添加数据
					this.chatList.push({
						isme: true,
						type: 'txt',
						content: this.myInput
					})
					this.chatList.push({
						isme: false,
						type: 'txt',
						content: this.myInput
					})
					// 翻动到底部
					setTimeout(() => {
						uni.pageScrollTo({
							scrollTop: 9999999,
							duration: 100
						})
					}, 5)
					// 保存数据
					uni.setStorageSync('chatList', JSON.stringify(this.chatList));
					// 重置输入
					this.myInput = ''
				}
			}
		}
	}
</script>

<style lang="scss" scoped>
	.container {
		background-color: #f1f1f1;
		min-height: 100vh;
	}

	.chat-body {
		padding-bottom: 178upx;

		.chat-time {
			text-align: center;
			color: #888;
			padding: 40upx 0 0;
		}

		.chat-one {
			display: flex;
			flex-direction: row;
			flex-wrap: wrap;
			justify-content: flex-start;
			align-items: flex-start;
			padding: 20upx 0;
		}

		.chat-one-begin {
			padding: 40upx 0 0;
		}

		.chat-one-mine {
			justify-content: flex-end;
		}

		.chat-face {
			width: 84upx;
			height: 84upx;
			border-radius: 10upx;
			margin-left: 40upx;
		}

		.chat-one-mine .chat-face {
			margin-left: 0;
			margin-right: 40upx;
		}

		.chat-box {
			max-width: calc(100% - 290upx);
			margin-left: 20upx;
			font-size: 30upx;
		}

		.chat-one-mine .chat-box {
			margin-right: 20upx;
		}

		.chat-sender {
			color: #888;
			font-size: 28upx;
			margin-top: -8upx;
			margin-bottom: 10upx;
		}

		.chat-content {
			background-color: #fff;
			border-radius: 5px;
			padding: 10px;

			.micon {
				margin-right: 20upx;
				color: #666;
			}
		}

		.chat-img {
			float: left;
			max-width: 60%;
			border-radius: 5px;
		}

		.chat-one-mine .chat-img {
			float: right;
		}
	}

	.chat-footer {
		width: 670upx;
		padding: 0 40upx;
		height: 120upx;
		position: fixed;
		bottom: 0;
		left: 0;
		background-color: #f1f1f1;
		display: flex;
		flex-direction: row;
		flex-wrap: wrap;
		justify-content: space-between;
		align-items: center;
		align-content: center;
		border-top: 1upx solid #ddd;

		.msg-input {
			background-color: #fff;
			width: calc(100% - 300upx);
			height: 70upx;
			line-height: 70upx;
			font-size: 30upx;
			border-radius: 10upx;
			padding: 0 20upx;
		}

		.img-chose {
			height: 70upx;
			width: 70upx;
		}

		.send-btn {
			height: 60upx;
			line-height: 60upx;
			width: 120upx;
			text-align: center;
			background-color: green;
			color: #FFFFFF;
			border-radius: 12upx;
		}
	}
</style>