uni-app实战之社区交友APP(5)搜索和发布页开发

文章目录

  • 前言
  • 一、搜索页开发
    • 1.搜索页面搭建
    • 2.搜索结果显示和优化
  • 二、发布页开发
    • 1.自定义导航栏开发
    • 2.文本域组件使用
    • 3.底部操作条组件开发
    • 4.多图上传功能开发
    • 5.删除选中图片功能实现
    • 6.保存草稿功能开发
  • 总结

小编目前在做毕业设计,主题为“高考志愿信息交流平台”,面向高中生和大学生,辛苦各位读者大佬朋友们填下问卷,点击链接https://www.wjx.cn/jq/98944127.aspx或扫描二维码、微信小程序码均可,希望各位能提供一些调查数据,先在这里谢过各位了(*^_^*)
问卷1
问卷微信小程序码

前言

本文先介绍了搜索页的开发,包括页面的搭建(搜索框、搜索历史和搜索结果)和搜索逻辑的优化;
再重点介绍了发布页的开发:自定义导航栏的实现,文本输入框的实现,底部操作条图标和按钮的实现,图品上传和删除的实现以及编辑保存草稿的实现。

一、搜索页开发

1.搜索页面搭建

搜索页可以根据关键字搜索。
pages下新建搜索页search.vue(需要创建同名目录,以后创建页面默认会创建同名目录),并在pages.json中配置搜索页导航栏,如下:

{
     
    "path" : "pages/search/search",
    "style" :                                                                                    
    {
     
        "app-plus": {
     
            // 导航栏配置
            "titleNView": {
     
                // 搜索框配置
                "searchInput": {
     
                    "align":"center",
                    "backgroundColor":"#F5F4F2",
                    "borderRadius":"4px",
                    "placeholder": "搜索帖子",
                    "placeholderColor": "#6D6C67"
                },
                // 按钮设置
                "buttons": [
                    {
     
                        "color":"#333333",
                        "colorPressed":"#FD597C",
                        "float":"right",
                        "fontSize":"14px",
                        "text": "搜索"
                    }
                ]
            }
        }
    }    
}

从本文开始,提供的代码一般只提供增量部分(包括增加和修改的部分),以减少文章篇幅、优化文章结构。

index.vue中增加监听导航栏搜索框生命周期,如下:

// 监听导航栏搜索框
onNavigationBarSearchInputClicked() {
     
    uni.navigateTo({
     
        url: '../search/search'
    })
},

其中,onNavigationBarSearchInputClicked用于监听原生标题栏搜索输入框点击事件,接口uni.navigateTo(OBJECT)用于保留当前页面、跳转到应用内的某个页面,即跳转到搜索页。

显示:

可以看到,点击搜索栏,跳转到了搜索页。

现进一步完成搜索历史,search.vue如下:

<template>
	<view>
		
		<view class="py-2 font-md px-2">搜索历史view>
		<view class="flex flex-wrap">
			<view class="border rounded font mx-2 my-1 px-2" hover-class="bg-light" v-for="(item, index) in list" :key="index">{
    {item}}view>
		view>
	view>
template>

<script>
	export default {
      
		data() {
      
			return {
      
				list: [
					'uni-app实战之社区交友APP',
					'uni-app入门教程',
					'面试之算法基础系列',
					'Python全栈',
					'商业数据分析从入门到入职',
					'Python数据分析实战',
					'Django+Vue开发生鲜电商平台'
				]
			}
		},
		methods: {
      

		}
	}
script>

<style>

style>

base.css如下:

/* 内外边距 */
.p-2 {
     
	padding: 20rpx;
}

/* flex布局 */
.flex {
     
	/* #ifndef APP-APP-PLUS-NVUE */
	display: flex;
	/* #endif */
	flex-direction: row;
}
.flex-wrap {
     
	flex-wrap: wrap;
}
.flex-column {
     
	flex-direction: column;
}

.align-center {
     
	align-items: center;
}

.justify-between {
     
	justify-content: space-between;
}

.justify-center {
     
	justify-content: center;
}

.flex-1 {
     
	flex: 1;
}

/* 圆角 */
.rounded-circle {
     
	border-radius: 100%;
}

.rounded {
     
	border-radius: 8rpx;
}

/* margin */
.mr-2 {
     
	margin-right: 20rpx;
}

.my-1 {
     
	margin-top: 10rpx;
	margin-bottom: 10rpx;
}

.mx-2 {
     
	margin-left: 20rpx;
	margin-right: 20rpx;
}

/* padding */
.px-5 {
     
	padding-left: 50rpx;
	padding-right: 50rpx;
}

.px-3 {
     
	padding-left: 30rpx;
	padding-right: 30rpx;
}

.px-2 {
     
	padding-left: 20rpx;
	padding-right: 20rpx;
}

.py-3 {
     
	padding-top: 30rpx;
	padding-bottom: 30rpx;
}

.py-2 {
     
	padding-top: 20rpx;
	padding-bottom: 20rpx;
}

.pt-7 {
     
	padding-top: 70rpx;
}

/* 边框 */
.border {
     
	border-width: 1rpx;
	border-style: solid;
	border-color: #DEE2E6;
}

/* 字体 */
.font-lg {
     
	font-size: 40rpx;
}

.font-md {
     
	font-size: 35rpx;
}

.font {
     
	font-size: 30rpx;
}

.font-sm {
     
	font-size: 25rpx;
}

.font-weight-bold {
     
	font-weight: bold;
}

/* 文字颜色 */
.text-white {
     
	color: #FFFFFF;
}

.text-light-muted {
     
	color: #A9A5A0;
}

/* 宽度 */
/* #ifndef APP-PLUS-NVUE */
.w-100 {
     
	width: 100%;
}

/* #endif */

/* scroll-view */
/* #ifndef APP-PLUS-NVUE */
.scroll-row {
     
	width: 100%;
	white-space: nowrap;
}

.scroll-row-item {
     
	display: inline-block !important;
}

/* #endif */

/* 背景 */
.bg-light {
     
	background-color: #F8F9FA;
}

显示:
uni-app实战之社区交友APP(5)搜索和发布页开发_第1张图片

可以看到,实现了搜索关键词的显示和点击。

2.搜索结果显示和优化

先通过监听获取数据,如下:

<template>
	<view>
		<template v-if="searchList.length === 0">
			
			<view class="py-2 font-md px-2">搜索历史view>
			<view class="flex flex-wrap">
				<view class="border rounded font mx-2 my-1 px-2" hover-class="bg-light" v-for="(item, index) in list" :key="index">{
    {item}}view>
			view>
		template>
		<template v-else>
			
			<block v-for="(item, index) in searchList" :key="index">
				<common-list :item="item" :index="index">common-list>
			block>
		template>
	view>
template>

<script>
	// 测试数据
	const test_data = [{
      
			username: "Corley",
			userpic: "/static/img/userpic/12.jpg",
			newstime: "2021-01-24 上午11:30",
			isFollow: false,
			title: "uni-app入门教程",
			titlepic: "/static/img/datapic/42.jpg",
			support: {
      
				type: "support", // 顶
				support_count: 1,
				unsupport_count: 2
			},
			comment_count: 2,
			share_count: 2
		},
		{
      
			username: "Brittany",
			userpic: "/static/img/userpic/16.jpg",
			newstime: "2021-01-24 下午14:00",
			isFollow: false,
			title: "商业数据分析从入门到入职",
			support: {
      
				type: "unsupport", // 踩
				support_count: 2,
				unsupport_count: 3
			},
			comment_count: 5,
			share_count: 1
		},
		{
      
			username: "Ashley",
			userpic: "/static/img/userpic/20.jpg",
			newstime: "2021-01-24 下午18:20",
			isFollow: true,
			title: "uni-app实战之社区交友APP",
			titlepic: "/static/img/datapic/30.jpg",
			support: {
      
				type: "support",
				support_count: 5,
				unsupport_count: 1
			},
			comment_count: 3,
			share_count: 0
		}
	];
	import commonList from '@/components/common/common-list.vue';
	export default {
      
		data() {
      
			return {
      
				list: [
					'uni-app实战之社区交友APP',
					'uni-app入门教程',
					'面试之算法基础系列',
					'Python全栈',
					'商业数据分析从入门到入职',
					'Python数据分析实战',
					'Django+Vue开发生鲜电商平台'
				],
				searchText: '',
				// 搜索结果
				searchList: []
			}
		},
		components: {
      
			commonList
		},
		// 监听导航栏搜索框输入
		onNavigationBarSearchInputChanged(e) {
      
			console.log(e);
			this.searchText = e.text;
		},
		// 监听点击导航栏搜索按钮
		onNavigationBarButtonTap(e) {
      
			console.log(e);
			if (e.index === 0) {
      
				this.searchEvent();
			}
		},
		methods: {
      
			// 搜索事件
			searchEvent() {
      
				// 收起键盘
				uni.hideKeyboard();
				// 请求搜索
				setTimeout(()=>{
      
					this.searchList = test_data;
				}, 2500)
			}
		}
	}
script>

<style>

style>

其中,onNavigationBarSearchInputChanged()生命周期用于监听原生标题栏搜索输入框输入内容变化事件,onNavigationBarButtonTap()用于监听原生标题栏按钮点击事件;
搜索结果使用之前封装的common-list组件实现;
触发搜索事件时,调用接口uni.hideKeyboard()收起软键盘,同时获取数据。

显示:

可以看到,已经模拟出了搜索的效果。

再添加搜索过程中的加载状态loading,使用的是uni.showLoading()接口,search.vue如下:

searchEvent() {
     
    // 收起键盘
    uni.hideKeyboard();
    // 显示loading状态
    uni.showLoading({
     
        title: '加载中...',
        mask: false
    });
    // 请求搜索
    setTimeout(()=>{
     
        this.searchList = test_data;
        // 隐藏loading状态
        uni.hideLoading();
    }, 2500)
}

显示:

可以看到,模拟出了正在加载的效果。

再实现点击搜索历史进行搜索,search.vue如下:

<view class="flex flex-wrap">
    <view class="border rounded font mx-2 my-1 px-2" hover-class="bg-light" v-for="(item, index) in list" :key="index"
    @click="clickSearchHistory(item)">{
    {item}}view>
view>

// 点击搜索历史
clickSearchHistory(text) {
    this.searchText = text;
    this.searchEvent();
}

显示:

可以看到,实现了点击搜索历史进行搜索。

二、发布页开发

1.自定义导航栏开发

创建新页面add-input,index.vue增加发布页入口,通过监听导航按钮点击事件实现,如下:

// 监听导航按钮点击事件
onNavigationBarButtonTap() {
     
    uni.navigateTo({
     
        url: '../add-input/add-input'
    })
},

pages.json配置如下:

{
     
    "path" : "pages/add-input/add-input",
    "style" :                                                                                    
    {
     
        "app-plus": {
     
            "titleNView": false
        }
    }            
}

在components目录下新建uni-ui,用于保存uni-app官方提供的组件,将之前创建的uni-app模板项目hello_uniapp中uni-app提供的官方组件uni-iconsuni-nav-baruni-status-bar(位于components目录下)连同目录拷贝到uni-ui下,add-input.vue如下:

<template>
	<view>		
		<uni-nav-bar left-icon="back" title="Community Dating">1uni-nav-bar>
	view>
template>

<script>
	import uniNavBar from '@/components/uni-ui/uni-nav-bar/uni-nav-bar.vue';
	export default {
      
		data() {
      
			return {
      
				
			}
		},
		components: {
      
			uniNavBar
		},
		methods: {
      
			
		}
	}
script>

<style>

style>

显示:
uni-app实战之社区交友APP(5)搜索和发布页开发_第2张图片

可以看到,实现了基本的导航栏展示。

再自定义导航栏标题,如下:

<uni-nav-bar left-icon="back" :statusBar="true" :border="false">
	<view class="flex justify-center align-center w-100">
		所有人可见<text class="iconfont icon-shezhi">text>
	view>
uni-nav-bar>

需要在https://www.iconfont.cn/中选择设置图标并添加至项目,下载解压后,将iconfont.css更新至common/icon.css中。

显示:
uni-app实战之社区交友APP(5)搜索和发布页开发_第3张图片

可以看到,自定义出了导航栏。

2.文本域组件使用

文本输入框使用文本域组件,即textarea,如下:

<template>
	<view>
		
		<uni-nav-bar left-icon="back" :statusBar="true" :border="false">
			<view class="flex justify-center align-center w-100">
				所有人可见<text class="iconfont icon-shezhi">text>
			view>
		uni-nav-bar>
		
		<textarea v-model="content" placeholder="说一句话吧~" class="uni-textarea px-2" />
	view>
template>

<script>
	import uniNavBar from '@/components/uni-ui/uni-nav-bar/uni-nav-bar.vue';
	export default {
      
		data() {
      
			return {
      
				content: ''
			}
		},
		components: {
      
			uniNavBar
		},
		methods: {
      
			
		}
	}
script>

<style>

style>

显示:
uni-app实战之社区交友APP(5)搜索和发布页开发_第4张图片

可以看到,输入框定义完成。

3.底部操作条组件开发

底部操作条包括选择分类、添加话题、选择图片和发布按钮等,位于底部。
需要在https://www.iconfont.cn/中菜单话题图片等图标,并更新icon.css。

add-input.vue如下:

<view class="fixed-bottom bg-white flex align-center" style="height: 85rpx;">
	<view class="iconfont icon-caidan">view>
	<view class="iconfont icon-huati">view>
	<view class="iconfont icon-tupian">view>
	<view class="bg-main text-white ml-auto">发送view>
view>

base.css如下:

/* 内外边距 */
.p-2 {
     
	padding: 20rpx;
}

/* flex布局 */
.flex {
     
	/* #ifndef APP-APP-PLUS-NVUE */
	display: flex;
	/* #endif */
	flex-direction: row;
}

.flex-wrap {
     
	flex-wrap: wrap;
}

.flex-column {
     
	flex-direction: column;
}

.align-center {
     
	align-items: center;
}

.justify-between {
     
	justify-content: space-between;
}

.justify-center {
     
	justify-content: center;
}

.flex-1 {
     
	flex: 1;
}

/* 圆角 */
.rounded-circle {
     
	border-radius: 100%;
}

.rounded {
     
	border-radius: 8rpx;
}

/* margin */
.mr-2 {
     
	margin-right: 20rpx;
}

.my-1 {
     
	margin-top: 10rpx;
	margin-bottom: 10rpx;
}

.mx-2 {
     
	margin-left: 20rpx;
	margin-right: 20rpx;
}

.ml-auto {
     
	margin-left: auto;
}

/* padding */
.px-5 {
     
	padding-left: 50rpx;
	padding-right: 50rpx;
}

.px-3 {
     
	padding-left: 30rpx;
	padding-right: 30rpx;
}

.px-2 {
     
	padding-left: 20rpx;
	padding-right: 20rpx;
}

.py-3 {
     
	padding-top: 30rpx;
	padding-bottom: 30rpx;
}

.py-2 {
     
	padding-top: 20rpx;
	padding-bottom: 20rpx;
}

.pt-7 {
     
	padding-top: 70rpx;
}

/* 边框 */
.border {
     
	border-width: 1rpx;
	border-style: solid;
	border-color: #DEE2E6;
}

/* 字体 */
.font-lg {
     
	font-size: 40rpx;
}

.font-md {
     
	font-size: 35rpx;
}

.font {
     
	font-size: 30rpx;
}

.font-sm {
     
	font-size: 25rpx;
}

.font-weight-bold {
     
	font-weight: bold;
}

/* 文字颜色 */
.text-white {
     
	color: #FFFFFF;
}

.text-light-muted {
     
	color: #A9A5A0;
}

/* 宽度 */
/* #ifndef APP-PLUS-NVUE */
.w-100 {
     
	width: 100%;
}

/* #endif */

/* scroll-view */
/* #ifndef APP-PLUS-NVUE */
.scroll-row {
     
	width: 100%;
	white-space: nowrap;
}

.scroll-row-item {
     
	display: inline-block !important;
}

/* #endif */

/* 背景 */
.bg-light {
     
	background-color: #F8F9FA;
}

.bg-white {
     
	background-color: #FFFFFF;
}

/* 定位 */
.position-relative {
     
	position: relative;
}

.position-absolute {
     
	position: absolute;
}

.position-fixed {
     
	position: fixed;
}

/* 定位-固定顶部 */
.fixed-top {
     
	position: fixed;
	top: 0;
	right: 0;
	left: 0;
	z-index: 1030;
}

/* 定位-固定底部 */
.fixed-bottom {
     
	position: fixed;
	right: 0;
	bottom: 0;
	left: 0;
	z-index: 1030;
}

.top-0 {
     
	top: 0;
}

.left-0 {
     
	left: 0;
}

.right-0 {
     
	right: 0;
}

.bottom-0 {
     
	bottom: 0;
}

显示:
uni-app实战之社区交友APP(5)搜索和发布页开发_第5张图片

底部已有大概的轮廓。

进一步完善尺寸和布局,如下:

<template>
	<view>
		
		<uni-nav-bar left-icon="back" :statusBar="true" :border="false">
			<view class="flex justify-center align-center w-100">
				所有人可见<text class="iconfont icon-shezhi">text>
			view>
		uni-nav-bar>
		
		<textarea v-model="content" placeholder="说一句话吧~" class="uni-textarea px-2" />
		
		<view class="fixed-bottom bg-white flex align-center" style="height: 85rpx;">
			<view class="iconfont icon-caidan footer-btn animate__animated" hover-class="animate__jello">view>
			<view class="iconfont icon-huati footer-btn animate__animated" hover-class="animate__jello">view>
			<view class="iconfont icon-tupian footer-btn animate__animated" hover-class="animate__jello">view>
			<view class="bg-main text-white ml-auto flex align-center justify-center rounded mr-2 animate__animated" hover-class="animate__jello" style="width: 140rpx; height: 60rpx;">发送view>
		view>
	view>
template>

<script>
	import uniNavBar from '@/components/uni-ui/uni-nav-bar/uni-nav-bar.vue';
	export default {
      
		data() {
      
			return {
      
				content: ''
			}
		},
		components: {
      
			uniNavBar
		},
		methods: {
      
			
		}
	}
script>

<style>
	.footer-btn {
      
		width: 86rpx;
		height: 86rpx;
		display: flex;
		justify-content: center;
		align-items: center;
		font-size: 50rpx;
	}
style>

显示:

可以看到,不仅调了尺寸,而且还添加了动画效果。

4.多图上传功能开发

uni-app官方模板hello_uniapp项目提供了多图上传接口,位于pages/API/image目录下,可以将image.vue拷贝到components/common下并重命名为unpload-image.vue,再稍作修改即可使用,如下:

<template>
	<view>
		<view class="uni-common-mt">
			<view class="uni-list list-pd">
				<view class="uni-list-cell cell-pd">
					<view class="uni-uploader">
						<view class="uni-uploader-head">
							<view class="uni-uploader-title">点击可预览选好的图片view>
							<view class="uni-uploader-info">{
    {imageList.length}}/9view>
						view>
						<view class="uni-uploader-body">
							<view class="uni-uploader__files">
								<block v-for="(image,index) in imageList" :key="index">
									<view class="uni-uploader__file">
										<image class="uni-uploader__img" :src="image" :data-src="image" @tap="previewImage">image>
									view>
								block>
								<view class="uni-uploader__input-box">
									<view class="uni-uploader__input" @tap="chooseImage">view>
								view>
							view>
						view>
					view>
				view>
			view>
		view>
	view>
template>
<script>
	import permision from "@/common/permission.js"
	var sourceType = [
		['camera'],
		['album'],
		['camera', 'album']
	]
	var sizeType = [
		['compressed'],
		['original'],
		['compressed', 'original']
	]
	export default {
      
		data() {
      
			return {
      
				title: 'choose/previewImage',
				imageList: [],
				sourceTypeIndex: 2,
				sourceType: ['拍照', '相册', '拍照或相册'],
				sizeTypeIndex: 2,
				sizeType: ['压缩', '原图', '压缩或原图'],
				countIndex: 8,
				count: [1, 2, 3, 4, 5, 6, 7, 8, 9]
			}
		},
		onUnload() {
      
			this.imageList = [],
				this.sourceTypeIndex = 2,
				this.sourceType = ['拍照', '相册', '拍照或相册'],
				this.sizeTypeIndex = 2,
				this.sizeType = ['压缩', '原图', '压缩或原图'],
				this.countIndex = 8;
		},
		methods: {
      
			chooseImage: async function() {
      
				// #ifdef APP-PLUS
				// TODO 选择相机或相册时 需要弹出actionsheet,目前无法获得是相机还是相册,在失败回调中处理
				if (this.sourceTypeIndex !== 2) {
      
					let status = await this.checkPermission();
					if (status !== 1) {
      
						return;
					}
				}
				// #endif

				if (this.imageList.length === 9) {
      
					let isContinue = await this.isFullImg();
					console.log("是否继续?", isContinue);
					if (!isContinue) {
      
						return;
					}
				}
				uni.chooseImage({
      
					sourceType: sourceType[this.sourceTypeIndex],
					sizeType: sizeType[this.sizeTypeIndex],
					count: this.imageList.length + this.count[this.countIndex] > 9 ? 9 - this.imageList.length : this.count[this.countIndex],
					success: (res) => {
      
						this.imageList = this.imageList.concat(res.tempFilePaths);
					},
					fail: (err) => {
      
						// #ifdef APP-PLUS
						if (err['code'] && err.code !== 0 && this.sourceTypeIndex === 2) {
      
							this.checkPermission(err.code);
						}
						// #endif
						// #ifdef MP
						uni.getSetting({
      
							success: (res) => {
      
								let authStatus = false;
								switch (this.sourceTypeIndex) {
      
									case 0:
										authStatus = res.authSetting['scope.camera'];
										break;
									case 1:
										authStatus = res.authSetting['scope.album'];
										break;
									case 2:
										authStatus = res.authSetting['scope.album'] && res.authSetting['scope.camera'];
										break;
									default:
										break;
								}
								if (!authStatus) {
      
									uni.showModal({
      
										title: '授权失败',
										content: 'Hello uni-app需要从您的相机或相册获取图片,请在设置界面打开相关权限',
										success: (res) => {
      
											if (res.confirm) {
      
												uni.openSetting()
											}
										}
									})
								}
							}
						})
						// #endif
					}
				})
			},
			isFullImg: function() {
      
				return new Promise((res) => {
      
					uni.showModal({
      
						content: "已经有9张图片了,是否清空现有图片?",
						success: (e) => {
      
							if (e.confirm) {
      
								this.imageList = [];
								res(true);
							} else {
      
								res(false)
							}
						},
						fail: () => {
      
							res(false)
						}
					})
				})
			},
			previewImage: function(e) {
      
				var current = e.target.dataset.src
				uni.previewImage({
      
					current: current,
					urls: this.imageList
				})
			},
			async checkPermission(code) {
      
				let type = code ? code - 1 : this.sourceTypeIndex;
				let status = permision.isIOS ? await permision.requestIOS(sourceType[type][0]) :
					await permision.requestAndroid(type === 0 ? 'android.permission.CAMERA' :
						'android.permission.READ_EXTERNAL_STORAGE');

				if (status === null || status === 1) {
      
					status = 1;
				} else {
      
					uni.showModal({
      
						content: "没有开启权限",
						confirmText: "设置",
						success: function(res) {
      
							if (res.confirm) {
      
								permision.gotoAppSetting();
							}
						}
					})
				}

				return status;
			}
		}
	}
script>

<style>
	.cell-pd {
      
		padding: 22rpx 30rpx;
	}

	.list-pd {
      
		margin-top: 50rpx;
	}
style>

在add_input.vue中导入并使用upload-image组件,如下:

<template>
	<view>
		
		<uni-nav-bar left-icon="back" :statusBar="true" :border="false">
			<view class="flex justify-center align-center w-100">
				所有人可见<text class="iconfont icon-shezhi">text>
			view>
		uni-nav-bar>
		
		<textarea v-model="content" placeholder="说一句话吧~" class="uni-textarea px-2" />
		
		<upload-image>upload-image>
		
		<view class="fixed-bottom bg-white flex align-center" style="height: 85rpx;">
			<view class="iconfont icon-caidan footer-btn animate__animated" hover-class="animate__jello">view>
			<view class="iconfont icon-huati footer-btn animate__animated" hover-class="animate__jello">view>
			<view class="iconfont icon-tupian footer-btn animate__animated" hover-class="animate__jello">view>
			<view class="bg-main text-white ml-auto flex align-center justify-center rounded mr-2 animate__animated" hover-class="animate__jello" style="width: 140rpx; height: 60rpx;">发送view>
		view>
	view>
template>

<script>
	import uniNavBar from '@/components/uni-ui/uni-nav-bar/uni-nav-bar.vue';
	import uploadImage from '../../components/common/upload-image.vue';
	export default {
      
		data() {
      
			return {
      
				content: ''
			}
		},
		components: {
      
			uniNavBar,
			uploadImage
		},
		methods: {
      
			
		}
	}
script>

<style>
	.footer-btn {
      
		width: 86rpx;
		height: 86rpx;
		display: flex;
		justify-content: center;
		align-items: center;
		font-size: 50rpx;
	}
style>

显示:

可以看到,实现了上传图片,还可以进行预览。

现进一步调整样式,unload-image.vue完善如下:

<template>
	<view class="px-2">
		<view class="uni-uploader">
			<view class="uni-uploader-head">
				<view class="uni-uploader-title">点击预览view>
				<view class="uni-uploader-info">{
    {imageList.length}}/9view>
			view>
			<view class="uni-uploader-body">
				<view class="uni-uploader__files">
					<block v-for="(image,index) in imageList" :key="index">
						<view class="uni-uploader__file">
							<image class="uni-uploader__img rounded" :src="image" :data-src="image" mode="aspectFill" @tap="previewImage">image>
						view>
					block>
					<view class="uni-uploader__input-box rounded">
						<view class="uni-uploader__input" @tap="chooseImage">view>
					view>
				view>
			view>
		view>
	view>
template>
<script>
	import permision from "@/common/permission.js"
	var sourceType = [
		['camera'],
		['album'],
		['camera', 'album']
	]
	var sizeType = [
		['compressed'],
		['original'],
		['compressed', 'original']
	]
	export default {
      
		data() {
      
			return {
      
				title: 'choose/previewImage',
				imageList: [],
				sourceTypeIndex: 2,
				sourceType: ['拍照', '相册', '拍照或相册'],
				sizeTypeIndex: 2,
				sizeType: ['压缩', '原图', '压缩或原图'],
				countIndex: 8,
				count: [1, 2, 3, 4, 5, 6, 7, 8, 9]
			}
		},
		onUnload() {
      
			this.imageList = [],
				this.sourceTypeIndex = 2,
				this.sourceType = ['拍照', '相册', '拍照或相册'],
				this.sizeTypeIndex = 2,
				this.sizeType = ['压缩', '原图', '压缩或原图'],
				this.countIndex = 8;
		},
		methods: {
      
			chooseImage: async function() {
      
				// #ifdef APP-PLUS
				// TODO 选择相机或相册时 需要弹出actionsheet,目前无法获得是相机还是相册,在失败回调中处理
				if (this.sourceTypeIndex !== 2) {
      
					let status = await this.checkPermission();
					if (status !== 1) {
      
						return;
					}
				}
				// #endif

				if (this.imageList.length === 9) {
      
					let isContinue = await this.isFullImg();
					console.log("是否继续?", isContinue);
					if (!isContinue) {
      
						return;
					}
				}
				uni.chooseImage({
      
					sourceType: sourceType[this.sourceTypeIndex],
					sizeType: sizeType[this.sizeTypeIndex],
					count: this.imageList.length + this.count[this.countIndex] > 9 ? 9 - this.imageList.length : this.count[this.countIndex],
					success: (res) => {
      
						this.imageList = this.imageList.concat(res.tempFilePaths);
						this.$emit('choose', this.imageList);
					},
					fail: (err) => {
      
						// #ifdef APP-PLUS
						if (err['code'] && err.code !== 0 && this.sourceTypeIndex === 2) {
      
							this.checkPermission(err.code);
						}
						// #endif
						// #ifdef MP
						uni.getSetting({
      
							success: (res) => {
      
								let authStatus = false;
								switch (this.sourceTypeIndex) {
      
									case 0:
										authStatus = res.authSetting['scope.camera'];
										break;
									case 1:
										authStatus = res.authSetting['scope.album'];
										break;
									case 2:
										authStatus = res.authSetting['scope.album'] && res.authSetting['scope.camera'];
										break;
									default:
										break;
								}
								if (!authStatus) {
      
									uni.showModal({
      
										title: '授权失败',
										content: 'Hello uni-app需要从您的相机或相册获取图片,请在设置界面打开相关权限',
										success: (res) => {
      
											if (res.confirm) {
      
												uni.openSetting()
											}
										}
									})
								}
							}
						})
						// #endif
					}
				})
			},
			isFullImg: function() {
      
				return new Promise((res) => {
      
					uni.showModal({
      
						content: "已经有9张图片了,是否清空现有图片?",
						success: (e) => {
      
							if (e.confirm) {
      
								this.imageList = [];
								res(true);
							} else {
      
								res(false)
							}
						},
						fail: () => {
      
							res(false)
						}
					})
				})
			},
			previewImage: function(e) {
      
				var current = e.target.dataset.src
				uni.previewImage({
      
					current: current,
					urls: this.imageList
				})
			},
			async checkPermission(code) {
      
				let type = code ? code - 1 : this.sourceTypeIndex;
				let status = permision.isIOS ? await permision.requestIOS(sourceType[type][0]) :
					await permision.requestAndroid(type === 0 ? 'android.permission.CAMERA' :
						'android.permission.READ_EXTERNAL_STORAGE');

				if (status === null || status === 1) {
      
					status = 1;
				} else {
      
					uni.showModal({
      
						content: "没有开启权限",
						confirmText: "设置",
						success: function(res) {
      
							if (res.confirm) {
      
								permision.gotoAppSetting();
							}
						}
					})
				}

				return status;
			}
		}
	}
script>

<style>
	.cell-pd {
      
		padding: 22rpx 30rpx;
	}

	.list-pd {
      
		margin-top: 50rpx;
	}
style>

在完善样式的同时向父组件传递图片列表,实现消息传递。

add-input.vue如下:

<template>
	<view>
		
		<uni-nav-bar left-icon="back" :statusBar="true" :border="false">
			<view class="flex justify-center align-center w-100">
				所有人可见<text class="iconfont icon-shezhi">text>
			view>
		uni-nav-bar>
		
		<textarea v-model="content" placeholder="说一句话吧~" class="uni-textarea px-2" />
		
		<upload-image @choose="choose">upload-image>
		
		<view class="fixed-bottom bg-white flex align-center" style="height: 85rpx;">
			<view class="iconfont icon-caidan footer-btn animate__animated" hover-class="animate__jello">view>
			<view class="iconfont icon-huati footer-btn animate__animated" hover-class="animate__jello">view>
			<view class="iconfont icon-tupian footer-btn animate__animated" hover-class="animate__jello">view>
			<view class="bg-main text-white ml-auto flex align-center justify-center rounded mr-2 animate__animated" hover-class="animate__jello" style="width: 140rpx; height: 60rpx;">发送view>
		view>
	view>
template>

<script>
	import uniNavBar from '@/components/uni-ui/uni-nav-bar/uni-nav-bar.vue';
	import uploadImage from '../../components/common/upload-image.vue';
	export default {
      
		data() {
      
			return {
      
				content: '',
				imageList: []
			}
		},
		components: {
      
			uniNavBar,
			uploadImage
		},
		methods: {
      
			choose(e) {
      
				console.log(e);
				this.imageList = e;
			}
		}
	}
script>

<style>
	.footer-btn {
      
		width: 86rpx;
		height: 86rpx;
		display: flex;
		justify-content: center;
		align-items: center;
		font-size: 50rpx;
	}
style>

显示:

可以看到,add-input页面中获取到了上传的图片路径列表,可用于后面上传到服务器。

5.删除选中图片功能实现

删除选中图片需要在图片右上角添加删除图标,即需要改写upload-imgae.vue,如下:

<block v-for="(image,index) in imageList" :key="index">
	<view class="uni-uploader__file position-relative">
		<image class="uni-uploader__img rounded" :src="image" :data-src="image" mode="aspectFill" @tap="previewImage">image>
		<view class="position-absolute top-0 right-0 rounded" style="padding: 0 15rpx; background-color: rgba(0, 0, 0, 0.5);">
			<text class="iconfont icon-shanchu text-white">text>
		view>
	view>
block>

需要在https://www.iconfont.cn/中选择删除图标并添加至项目,下载解压后,将iconfont.css更新至common/icon.css中。

显示:
uni-app实战之社区交友APP(5)搜索和发布页开发_第6张图片

可以看到,已经在图片右上角显示出删除图标。

现增加点击事件、实现删除,upload-image.vue如下:

<template>
	<view class="px-2">
		<view class="uni-uploader">
			<view class="uni-uploader-head">
				<view class="uni-uploader-title">点击预览view>
				<view class="uni-uploader-info">{
    {imageList.length}}/9view>
			view>
			<view class="uni-uploader-body">
				<view class="uni-uploader__files">
					<block v-for="(image,index) in imageList" :key="index">
						<view class="uni-uploader__file position-relative">
							<image class="uni-uploader__img rounded" :src="image" :data-src="image" mode="aspectFill" @tap="previewImage">image>
							<view class="position-absolute top-0 right-0 rounded" style="padding: 0 15rpx; background-color: rgba(0, 0, 0, 0.5);" @click.stop="deleteImage(index)">
								<text class="iconfont icon-shanchu text-white">text>
							view>
						view>
					block>
					<view class="uni-uploader__input-box rounded">
						<view class="uni-uploader__input" @tap="chooseImage">view>
					view>
				view>
			view>
		view>
	view>
template>
<script>
	import permision from "@/common/permission.js"
	var sourceType = [
		['camera'],
		['album'],
		['camera', 'album']
	]
	var sizeType = [
		['compressed'],
		['original'],
		['compressed', 'original']
	]
	export default {
      
		data() {
      
			return {
      
				title: 'choose/previewImage',
				imageList: [],
				sourceTypeIndex: 2,
				sourceType: ['拍照', '相册', '拍照或相册'],
				sizeTypeIndex: 2,
				sizeType: ['压缩', '原图', '压缩或原图'],
				countIndex: 8,
				count: [1, 2, 3, 4, 5, 6, 7, 8, 9]
			}
		},
		onUnload() {
      
			this.imageList = [],
				this.sourceTypeIndex = 2,
				this.sourceType = ['拍照', '相册', '拍照或相册'],
				this.sizeTypeIndex = 2,
				this.sizeType = ['压缩', '原图', '压缩或原图'],
				this.countIndex = 8;
		},
		methods: {
      
			chooseImage: async function() {
      
				// #ifdef APP-PLUS
				// TODO 选择相机或相册时 需要弹出actionsheet,目前无法获得是相机还是相册,在失败回调中处理
				if (this.sourceTypeIndex !== 2) {
      
					let status = await this.checkPermission();
					if (status !== 1) {
      
						return;
					}
				}
				// #endif

				if (this.imageList.length === 9) {
      
					let isContinue = await this.isFullImg();
					console.log("是否继续?", isContinue);
					if (!isContinue) {
      
						return;
					}
				}
				uni.chooseImage({
      
					sourceType: sourceType[this.sourceTypeIndex],
					sizeType: sizeType[this.sizeTypeIndex],
					count: this.imageList.length + this.count[this.countIndex] > 9 ? 9 - this.imageList.length : this.count[this.countIndex],
					success: (res) => {
      
						this.imageList = this.imageList.concat(res.tempFilePaths);
						this.$emit('change', this.imageList);
					},
					fail: (err) => {
      
						// #ifdef APP-PLUS
						if (err['code'] && err.code !== 0 && this.sourceTypeIndex === 2) {
      
							this.checkPermission(err.code);
						}
						// #endif
						// #ifdef MP
						uni.getSetting({
      
							success: (res) => {
      
								let authStatus = false;
								switch (this.sourceTypeIndex) {
      
									case 0:
										authStatus = res.authSetting['scope.camera'];
										break;
									case 1:
										authStatus = res.authSetting['scope.album'];
										break;
									case 2:
										authStatus = res.authSetting['scope.album'] && res.authSetting['scope.camera'];
										break;
									default:
										break;
								}
								if (!authStatus) {
      
									uni.showModal({
      
										title: '授权失败',
										content: 'Hello uni-app需要从您的相机或相册获取图片,请在设置界面打开相关权限',
										success: (res) => {
      
											if (res.confirm) {
      
												uni.openSetting()
											}
										}
									})
								}
							}
						})
						// #endif
					}
				})
			},
			isFullImg: function() {
      
				return new Promise((res) => {
      
					uni.showModal({
      
						content: "已经有9张图片了,是否清空现有图片?",
						success: (e) => {
      
							if (e.confirm) {
      
								this.imageList = [];
								res(true);
							} else {
      
								res(false)
							}
						},
						fail: () => {
      
							res(false)
						}
					})
				})
			},
			previewImage: function(e) {
      
				var current = e.target.dataset.src
				uni.previewImage({
      
					current: current,
					urls: this.imageList
				})
			},
			async checkPermission(code) {
      
				let type = code ? code - 1 : this.sourceTypeIndex;
				let status = permision.isIOS ? await permision.requestIOS(sourceType[type][0]) :
					await permision.requestAndroid(type === 0 ? 'android.permission.CAMERA' :
						'android.permission.READ_EXTERNAL_STORAGE');

				if (status === null || status === 1) {
      
					status = 1;
				} else {
      
					uni.showModal({
      
						content: "没有开启权限",
						confirmText: "设置",
						success: function(res) {
      
							if (res.confirm) {
      
								permision.gotoAppSetting();
							}
						}
					})
				}

				return status;
			},
			deleteImage(index) {
      
				uni.showModal({
      
					title: '删除提示',
					content: '是否要删除该图片?',
					showCancel: true,
					cancelText: '不删除',
					confirmText: '删除',
					success: res => {
      
						if (res.confirm) {
      
							this.imageList.splice(index, 1);
							this.$emit('change', this.imageList);
						}
					},
					fail: () => {
      },
					complete: () => {
      }
				});
			}
		}
	}
script>

<style>
	.cell-pd {
      
		padding: 22rpx 30rpx;
	}

	.list-pd {
      
		margin-top: 50rpx;
	}
style>

add-input.vue修改如下:

<template>
	<view>
		
		<uni-nav-bar left-icon="back" :statusBar="true" :border="false">
			<view class="flex justify-center align-center w-100">
				所有人可见<text class="iconfont icon-shezhi">text>
			view>
		uni-nav-bar>
		
		<textarea v-model="content" placeholder="说一句话吧~" class="uni-textarea px-2" />
		
		<upload-image @change="changeImage">upload-image>
		
		<view class="fixed-bottom bg-white flex align-center" style="height: 85rpx;">
			<view class="iconfont icon-caidan footer-btn animate__animated" hover-class="animate__jello">view>
			<view class="iconfont icon-huati footer-btn animate__animated" hover-class="animate__jello">view>
			<view class="iconfont icon-tupian footer-btn animate__animated" hover-class="animate__jello">view>
			<view class="bg-main text-white ml-auto flex align-center justify-center rounded mr-2 animate__animated" hover-class="animate__jello" style="width: 140rpx; height: 60rpx;">发送view>
		view>
	view>
template>

<script>
	import uniNavBar from '@/components/uni-ui/uni-nav-bar/uni-nav-bar.vue';
	import uploadImage from '../../components/common/upload-image.vue';
	export default {
      
		data() {
      
			return {
      
				content: '',
				imageList: []
			}
		},
		components: {
      
			uniNavBar,
			uploadImage
		},
		methods: {
      
			changeImage(e) {
      
				console.log(e);
				this.imageList = e;
			}
		}
	}
script>

<style>
	.footer-btn {
      
		width: 86rpx;
		height: 86rpx;
		display: flex;
		justify-content: center;
		align-items: center;
		font-size: 50rpx;
	}
style>

显示:

可以看到,实现了删除功能,并且在删除前会给出提示。

6.保存草稿功能开发

一般编辑时,为了友好性和更好的体验,一般会将草稿保存下来,原理是使用页面生命周期onBackPress

先模拟保存草稿,演示如下:

// 监听返回
onBackPress() {
     
	if ((this.content !== '' || this.imageList.length > 0) && !this.showBack) {
     
		uni.showModal({
     
			title: '返回提示',
			content: '是否要保存为草稿?',
			showCancel: true,
			cancelText: '不保存',
			confirmText: '保存',
			success: res => {
     
				// 点击确认
				if (re.confirm) {
     
					console.log('保存');
				}
				// 手动执行返回
				uni.navigateBack({
     
					delta: 1
				});
			}
		});
		this.showBack = true;
		return true;
	}
}

显示:

显然,模拟出了保存的效果。

现进一步实现保存编辑内容,如下:

// 监听返回
onBackPress() {
     
	if ((this.content !== '' || this.imageList.length > 0) && !this.showBack) {
     
		uni.showModal({
     
			title: '返回提示',
			content: '是否要保存为草稿?',
			showCancel: true,
			cancelText: '不保存',
			confirmText: '保存',
			success: res => {
     
				// 点击确认
				if (res.confirm) {
     
					this.store();
				}
				// 手动执行返回
				uni.navigateBack({
     
					delta: 1
				});
			}
		});
		this.showBack = true;
		return true;
	}
},
// 页面加载
onLoad() {
     
	uni.getStorage({
     
		key: 'add-input',
		success: (res) => {
     
			console.log(res);
			if (res.data) {
     
				let result = JSON.parse(res.data);
				this.content = result.content;
				this.imageList = result.imageList;
			}
		}
	})
},

显示:

可以看到,已经可以保存文本了。

现在实现保存图片,需要父组件(add-input)向子组件(upload-image)传递消息,add-input.vue如下:

<upload-image :list='imageList' @change="changeImage">upload-image>

unpload-image.vue如下:

<template>
	<view class="px-2">
		<view class="uni-uploader">
			<view class="uni-uploader-head">
				<view class="uni-uploader-title">点击预览view>
				<view class="uni-uploader-info">{
    {imageList.length}}/9view>
			view>
			<view class="uni-uploader-body">
				<view class="uni-uploader__files">
					<block v-for="(image,index) in imageList" :key="index">
						<view class="uni-uploader__file position-relative">
							<image class="uni-uploader__img rounded" :src="image" :data-src="image" mode="aspectFill" @tap="previewImage">image>
							<view class="position-absolute top-0 right-0 rounded" style="padding: 0 15rpx; background-color: rgba(0, 0, 0, 0.5);"
							 @click.stop="deleteImage(index)">
								<text class="iconfont icon-shanchu text-white">text>
							view>
						view>
					block>
					<view class="uni-uploader__input-box rounded">
						<view class="uni-uploader__input" @tap="chooseImage">view>
					view>
				view>
			view>
		view>
	view>
template>
<script>
	import permision from "@/common/permission.js"
	var sourceType = [
		['camera'],
		['album'],
		['camera', 'album']
	]
	var sizeType = [
		['compressed'],
		['original'],
		['compressed', 'original']
	]
	export default {
      
		props: ['list'],
		data() {
      
			return {
      
				title: 'choose/previewImage',
				imageList: [],
				sourceTypeIndex: 2,
				sourceType: ['拍照', '相册', '拍照或相册'],
				sizeTypeIndex: 2,
				sizeType: ['压缩', '原图', '压缩或原图'],
				countIndex: 8,
				count: [1, 2, 3, 4, 5, 6, 7, 8, 9]
			}
		},
		onUnload() {
      
			this.imageList = [],
				this.sourceTypeIndex = 2,
				this.sourceType = ['拍照', '相册', '拍照或相册'],
				this.sizeTypeIndex = 2,
				this.sizeType = ['压缩', '原图', '压缩或原图'],
				this.countIndex = 8;
		},
		mounted() {
      
			console.log(this.list);
			this.imageList = this.list;
		},
		methods: {
      
			chooseImage: async function() {
      
				// #ifdef APP-PLUS
				// TODO 选择相机或相册时 需要弹出actionsheet,目前无法获得是相机还是相册,在失败回调中处理
				if (this.sourceTypeIndex !== 2) {
      
					let status = await this.checkPermission();
					if (status !== 1) {
      
						return;
					}
				}
				// #endif

				if (this.imageList.length === 9) {
      
					let isContinue = await this.isFullImg();
					console.log("是否继续?", isContinue);
					if (!isContinue) {
      
						return;
					}
				}
				uni.chooseImage({
      
					sourceType: sourceType[this.sourceTypeIndex],
					sizeType: sizeType[this.sizeTypeIndex],
					count: this.imageList.length + this.count[this.countIndex] > 9 ? 9 - this.imageList.length : this.count[this.countIndex],
					success: (res) => {
      
						this.imageList = this.imageList.concat(res.tempFilePaths);
						this.$emit('change', this.imageList);
					},
					fail: (err) => {
      
						// #ifdef APP-PLUS
						if (err['code'] && err.code !== 0 && this.sourceTypeIndex === 2) {
      
							this.checkPermission(err.code);
						}
						// #endif
						// #ifdef MP
						uni.getSetting({
      
							success: (res) => {
      
								let authStatus = false;
								switch (this.sourceTypeIndex) {
      
									case 0:
										authStatus = res.authSetting['scope.camera'];
										break;
									case 1:
										authStatus = res.authSetting['scope.album'];
										break;
									case 2:
										authStatus = res.authSetting['scope.album'] && res.authSetting['scope.camera'];
										break;
									default:
										break;
								}
								if (!authStatus) {
      
									uni.showModal({
      
										title: '授权失败',
										content: 'Hello uni-app需要从您的相机或相册获取图片,请在设置界面打开相关权限',
										success: (res) => {
      
											if (res.confirm) {
      
												uni.openSetting()
											}
										}
									})
								}
							}
						})
						// #endif
					}
				})
			},
			isFullImg: function() {
      
				return new Promise((res) => {
      
					uni.showModal({
      
						content: "已经有9张图片了,是否清空现有图片?",
						success: (e) => {
      
							if (e.confirm) {
      
								this.imageList = [];
								res(true);
							} else {
      
								res(false)
							}
						},
						fail: () => {
      
							res(false)
						}
					})
				})
			},
			previewImage: function(e) {
      
				var current = e.target.dataset.src
				uni.previewImage({
      
					current: current,
					urls: this.imageList
				})
			},
			async checkPermission(code) {
      
				let type = code ? code - 1 : this.sourceTypeIndex;
				let status = permision.isIOS ? await permision.requestIOS(sourceType[type][0]) :
					await permision.requestAndroid(type === 0 ? 'android.permission.CAMERA' :
						'android.permission.READ_EXTERNAL_STORAGE');

				if (status === null || status === 1) {
      
					status = 1;
				} else {
      
					uni.showModal({
      
						content: "没有开启权限",
						confirmText: "设置",
						success: function(res) {
      
							if (res.confirm) {
      
								permision.gotoAppSetting();
							}
						}
					})
				}

				return status;
			},
			deleteImage(index) {
      
				uni.showModal({
      
					title: '删除提示',
					content: '是否要删除该图片?',
					showCancel: true,
					cancelText: '不删除',
					confirmText: '删除',
					success: res => {
      
						if (res.confirm) {
      
							this.imageList.splice(index, 1);
							this.$emit('change', this.imageList);
						}
					},
					fail: () => {
      },
					complete: () => {
      }
				});
			}
		}
	}
script>

<style>
	.cell-pd {
      
		padding: 22rpx 30rpx;
	}

	.list-pd {
      
		margin-top: 50rpx;
	}
style>

显示:

可以看到,实现了保存图片到草稿。

这时候还可能存在一个问题,当编辑之后,返回所如果选择不保存、之前保存到缓存中的内容还可能会存在,因此需要在点击时删除该数据缓存
同时,需要实现点击左上角返回按钮,可以正常返回,此时需要父组件与子组件进行事件传递。
add-input.vue如下:

<template>
	<view>
		
		<uni-nav-bar left-icon="back" :statusBar="true" :border="false" @clickLeft="goBack()">
			<view class="flex justify-center align-center w-100">
				所有人可见<text class="iconfont icon-shezhi">text>
			view>
		uni-nav-bar>
		
		<textarea v-model="content" placeholder="说一句话吧~" class="uni-textarea px-2" />
		
		<upload-image :list='imageList' @change="changeImage">upload-image>
		
		<view class="fixed-bottom bg-white flex align-center" style="height: 85rpx;">
			<view class="iconfont icon-caidan footer-btn animate__animated" hover-class="animate__jello">view>
			<view class="iconfont icon-huati footer-btn animate__animated" hover-class="animate__jello">view>
			<view class="iconfont icon-tupian footer-btn animate__animated" hover-class="animate__jello">view>
			<view class="bg-main text-white ml-auto flex align-center justify-center rounded mr-2 animate__animated" hover-class="animate__jello" style="width: 140rpx; height: 60rpx;">发送view>
		view>
	view>
template>

<script>
	import uniNavBar from '@/components/uni-ui/uni-nav-bar/uni-nav-bar.vue';
	import uploadImage from '../../components/common/upload-image.vue';
	export default {
      
		data() {
      
			return {
      
				content: '',
				imageList: [],
				// 是否已经弹出提示框
				showBack: false
			}
		},
		components: {
      
			uniNavBar,
			uploadImage
		},
		// 监听返回
		onBackPress() {
      
			if ((this.content !== '' || this.imageList.length > 0) && !this.showBack) {
      
				uni.showModal({
      
					title: '返回提示',
					content: '是否要保存为草稿?',
					showCancel: true,
					cancelText: '不保存',
					confirmText: '保存',
					success: res => {
      
						// 点击确认
						if (res.confirm) {
      
							this.store();
						}
						// 点击取消
						else {
      
							uni.removeStorage({
      
								key: 'add-input'
							});
						}
						// 手动执行返回
						uni.navigateBack({
      
							delta: 1
						});
					}
				});
				this.showBack = true;
				return true;
			}
		},
		// 页面加载
		onLoad() {
      
			uni.getStorage({
      
				key: 'add-input',
				success: (res) => {
      
					console.log(res);
					if (res.data) {
      
						let result = JSON.parse(res.data);
						this.content = result.content;
						this.imageList = result.imageList;
					}
				}
			})
		},
		methods: {
      
			changeImage(e) {
      
				console.log(e);
				this.imageList = e;
			},
			// 保存草稿
			store() {
      
				let obj = {
      
						content: this.content,
						imageList: this.imageList
				};
				// 保存为本地存储
				uni.setStorage({
      
					key: 'add-input',
					data: JSON.stringify(obj)
				})
			},
			// 返回上一步
			goBack() {
      
				uni.navigateBack({
      
					delta: 1
				})
			}
		}
	}
script>

<style>
	.footer-btn {
      
		width: 86rpx;
		height: 86rpx;
		display: flex;
		justify-content: center;
		align-items: center;
		font-size: 50rpx;
	}
style>

显示:

显然,已实现了预期的效果。

再实现隐藏图片添加预览区域、点击图标上传图片后再显示添加图片区域,原理是点击add-input组件的icon-tupian图标,触发子组件upload-image的chooseImage()方法,此时通过ref属性实现,同时还会用到计算属性等特性。

如下:

<template>
	<view>
		
		<uni-nav-bar left-icon="back" :statusBar="true" :border="false" @clickLeft="goBack()">
			<view class="flex justify-center align-center w-100">
				所有人可见<text class="iconfont icon-shezhi">text>
			view>
		uni-nav-bar>
		
		<textarea v-model="content" placeholder="说一句话吧~" class="uni-textarea px-2" />
		
		<upload-image :show="show" ref="uploadImage" :list='imageList' @change="changeImage">upload-image>
		
		<view class="fixed-bottom bg-white flex align-center" style="height: 85rpx;">
			<view class="iconfont icon-caidan footer-btn animate__animated" hover-class="animate__jello">view>
			<view class="iconfont icon-huati footer-btn animate__animated" hover-class="animate__jello">view>
			<view class="iconfont icon-tupian footer-btn animate__animated" hover-class="animate__jello" @click="iconClickEvent('uploadImage')">view>
			<view class="bg-main text-white ml-auto flex align-center justify-center rounded mr-2 animate__animated" hover-class="animate__jello" style="width: 140rpx; height: 60rpx;">发送view>
		view>
	view>
template>

<script>
	import uniNavBar from '@/components/uni-ui/uni-nav-bar/uni-nav-bar.vue';
	import uploadImage from '../../components/common/upload-image.vue';
	export default {
      
		data() {
      
			return {
      
				content: '',
				imageList: [],
				// 是否已经弹出提示框
				showBack: false
			}
		},
		components: {
      
			uniNavBar,
			uploadImage
		},
		computed: {
      
			show() {
      
				return this.imageList.length > 0; 
			}
		},
		// 监听返回
		onBackPress() {
      
			if ((this.content !== '' || this.imageList.length > 0) && !this.showBack) {
      
				uni.showModal({
      
					title: '返回提示',
					content: '是否要保存为草稿?',
					showCancel: true,
					cancelText: '不保存',
					confirmText: '保存',
					success: res => {
      
						// 点击确认
						if (res.confirm) {
      
							this.store();
						}
						// 点击取消
						else {
      
							uni.removeStorage({
      
								key: 'add-input'
							});
						}
						// 手动执行返回
						uni.navigateBack({
      
							delta: 1
						});
					}
				});
				this.showBack = true;
				return true;
			}
		},
		// 页面加载
		onLoad() {
      
			uni.getStorage({
      
				key: 'add-input',
				success: (res) => {
      
					console.log(res);
					if (res.data) {
      
						let result = JSON.parse(res.data);
						this.content = result.content;
						this.imageList = result.imageList;
					}
				}
			})
		},
		methods: {
      
			changeImage(e) {
      
				console.log(e);
				this.imageList = e;
			},
			// 保存草稿
			store() {
      
				let obj = {
      
						content: this.content,
						imageList: this.imageList
				};
				// 保存为本地存储
				uni.setStorage({
      
					key: 'add-input',
					data: JSON.stringify(obj)
				})
			},
			// 返回上一步
			goBack() {
      
				uni.navigateBack({
      
					delta: 1
				})
			},
			// 底部图标点击事件
			iconClickEvent(e) {
      
				switch (e){
      
					case 'uploadImage':
					this.$refs.uploadImage.chooseImage();
					break;
				}
			}
		}
	}
script>

<style>
	.footer-btn {
      
		width: 86rpx;
		height: 86rpx;
		display: flex;
		justify-content: center;
		align-items: center;
		font-size: 50rpx;
	}
style>

upload-image.vue如下:

<template>
	<view class="px-2">
		<view class="uni-uploader" v-if="show">
			<view class="uni-uploader-head">
				<view class="uni-uploader-title">点击预览view>
				<view class="uni-uploader-info">{
    {imageList.length}}/9view>
			view>
			<view class="uni-uploader-body">
				<view class="uni-uploader__files">
					<block v-for="(image,index) in imageList" :key="index">
						<view class="uni-uploader__file position-relative">
							<image class="uni-uploader__img rounded" :src="image" :data-src="image" mode="aspectFill" @tap="previewImage">image>
							<view class="position-absolute top-0 right-0 rounded" style="padding: 0 15rpx; background-color: rgba(0, 0, 0, 0.5);"
							 @click.stop="deleteImage(index)">
								<text class="iconfont icon-shanchu text-white">text>
							view>
						view>
					block>
					<view class="uni-uploader__input-box rounded">
						<view class="uni-uploader__input" @tap="chooseImage">view>
					view>
				view>
			view>
		view>
	view>
template>
<script>
	import permision from "@/common/permission.js"
	var sourceType = [
		['camera'],
		['album'],
		['camera', 'album']
	]
	var sizeType = [
		['compressed'],
		['original'],
		['compressed', 'original']
	]
	export default {
      
		props: {
      
			list: Array,
			show: {
      
				type: Boolean, 
				default: true
			}
		},
		data() {
      
			return {
      
				title: 'choose/previewImage',
				imageList: [],
				sourceTypeIndex: 2,
				sourceType: ['拍照', '相册', '拍照或相册'],
				sizeTypeIndex: 2,
				sizeType: ['压缩', '原图', '压缩或原图'],
				countIndex: 8,
				count: [1, 2, 3, 4, 5, 6, 7, 8, 9]
			}
		},
		onUnload() {
      
			this.imageList = [],
				this.sourceTypeIndex = 2,
				this.sourceType = ['拍照', '相册', '拍照或相册'],
				this.sizeTypeIndex = 2,
				this.sizeType = ['压缩', '原图', '压缩或原图'],
				this.countIndex = 8;
		},
		mounted() {
      
			console.log(this.list);
			this.imageList = this.list;
		},
		methods: {
      
			chooseImage: async function() {
      
				// #ifdef APP-PLUS
				// TODO 选择相机或相册时 需要弹出actionsheet,目前无法获得是相机还是相册,在失败回调中处理
				if (this.sourceTypeIndex !== 2) {
      
					let status = await this.checkPermission();
					if (status !== 1) {
      
						return;
					}
				}
				// #endif

				if (this.imageList.length === 9) {
      
					let isContinue = await this.isFullImg();
					console.log("是否继续?", isContinue);
					if (!isContinue) {
      
						return;
					}
				}
				uni.chooseImage({
      
					sourceType: sourceType[this.sourceTypeIndex],
					sizeType: sizeType[this.sizeTypeIndex],
					count: this.imageList.length + this.count[this.countIndex] > 9 ? 9 - this.imageList.length : this.count[this.countIndex],
					success: (res) => {
      
						this.imageList = this.imageList.concat(res.tempFilePaths);
						this.$emit('change', this.imageList);
					},
					fail: (err) => {
      
						// #ifdef APP-PLUS
						if (err['code'] && err.code !== 0 && this.sourceTypeIndex === 2) {
      
							this.checkPermission(err.code);
						}
						// #endif
						// #ifdef MP
						uni.getSetting({
      
							success: (res) => {
      
								let authStatus = false;
								switch (this.sourceTypeIndex) {
      
									case 0:
										authStatus = res.authSetting['scope.camera'];
										break;
									case 1:
										authStatus = res.authSetting['scope.album'];
										break;
									case 2:
										authStatus = res.authSetting['scope.album'] && res.authSetting['scope.camera'];
										break;
									default:
										break;
								}
								if (!authStatus) {
      
									uni.showModal({
      
										title: '授权失败',
										content: 'Hello uni-app需要从您的相机或相册获取图片,请在设置界面打开相关权限',
										success: (res) => {
      
											if (res.confirm) {
      
												uni.openSetting()
											}
										}
									})
								}
							}
						})
						// #endif
					}
				})
			},
			isFullImg: function() {
      
				return new Promise((res) => {
      
					uni.showModal({
      
						content: "已经有9张图片了,是否清空现有图片?",
						success: (e) => {
      
							if (e.confirm) {
      
								this.imageList = [];
								res(true);
							} else {
      
								res(false)
							}
						},
						fail: () => {
      
							res(false)
						}
					})
				})
			},
			previewImage: function(e) {
      
				var current = e.target.dataset.src
				uni.previewImage({
      
					current: current,
					urls: this.imageList
				})
			},
			async checkPermission(code) {
      
				let type = code ? code - 1 : this.sourceTypeIndex;
				let status = permision.isIOS ? await permision.requestIOS(sourceType[type][0]) :
					await permision.requestAndroid(type === 0 ? 'android.permission.CAMERA' :
						'android.permission.READ_EXTERNAL_STORAGE');

				if (status === null || status === 1) {
      
					status = 1;
				} else {
      
					uni.showModal({
      
						content: "没有开启权限",
						confirmText: "设置",
						success: function(res) {
      
							if (res.confirm) {
      
								permision.gotoAppSetting();
							}
						}
					})
				}

				return status;
			},
			deleteImage(index) {
      
				uni.showModal({
      
					title: '删除提示',
					content: '是否要删除该图片?',
					showCancel: true,
					cancelText: '不删除',
					confirmText: '删除',
					success: res => {
      
						if (res.confirm) {
      
							this.imageList.splice(index, 1);
							this.$emit('change', this.imageList);
						}
					},
					fail: () => {
      },
					complete: () => {
      }
				});
			}
		}
	}
script>

<style>
	.cell-pd {
      
		padding: 22rpx 30rpx;
	}

	.list-pd {
      
		margin-top: 50rpx;
	}
style>

显示:

显然,已经实现了预期的效果。

总结

首页导航栏的实现主要包括搜索页和贴子发布页的实现,这是开发的基础功能搜索页展示话题和帖子、发布页发布帖子,也包含了很多功能细节,需要一一实现。

你可能感兴趣的:(移动应用开发实战,uni-app实战,社区交友APP,搜索和发布页开发)