前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >uni-app实战之社区交友APP(5)搜索和发布页开发

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

作者头像
cutercorley
发布2021-02-02 17:13:51
2.5K0
发布2021-02-02 17:13:51
举报

文章目录

前言

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

一、搜索页开发

1.搜索页面搭建

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

代码语言:javascript
复制
{
    "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中增加监听导航栏搜索框生命周期,如下:

代码语言:javascript
复制
// 监听导航栏搜索框
onNavigationBarSearchInputClicked() {
    uni.navigateTo({
        url: '../search/search'
    })
},

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

显示:

uniapp social app search develop first
uniapp social app search develop first

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

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

代码语言:javascript
复制
<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如下:

代码语言:javascript
复制
/* 内外边距 */
.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;
}

显示:

uniapp social app search develop history keyword
uniapp social app search develop history keyword

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

2.搜索结果显示和优化

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

代码语言:javascript
复制
<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()收起软键盘,同时获取数据。

显示:

uniapp social app search develop result
uniapp social app search develop result

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

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

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

显示:

uniapp social app search develop loading
uniapp social app search develop loading

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

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

代码语言:javascript
复制
<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();
}

显示:

uniapp social app search develop history click
uniapp social app search develop history click

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

二、发布页开发

1.自定义导航栏开发

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

代码语言:javascript
复制
// 监听导航按钮点击事件
onNavigationBarButtonTap() {
    uni.navigateTo({
        url: '../add-input/add-input'
    })
},

pages.json配置如下:

代码语言:javascript
复制
{
    "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如下:

代码语言:javascript
复制
<template>
	<view>		
		<uni-nav-bar left-icon="back" title="Community Dating">1</uni-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>

显示:

uniapp social app post develop self navigation first
uniapp social app post develop self navigation first

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

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

代码语言:javascript
复制
<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中。

显示:

uniapp social app post develop self navigation complete
uniapp social app post develop self navigation complete

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

2.文本域组件使用

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

代码语言:javascript
复制
<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>

显示:

uniapp social app post develop textarea
uniapp social app post develop textarea

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

3.底部操作条组件开发

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

add-input.vue如下:

代码语言:javascript
复制
<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如下:

代码语言:javascript
复制
/* 内外边距 */
.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;
}

显示:

uniapp social app post develop bottom first
uniapp social app post develop bottom first

底部已有大概的轮廓。

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

代码语言:javascript
复制
<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>

显示:

uniapp social app post develop bottom animate
uniapp social app post develop bottom animate

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

4.多图上传功能开发

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

代码语言:javascript
复制
<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}}/9</view>
						</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组件,如下:

代码语言:javascript
复制
<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>

显示:

uniapp social app post develop upload image first
uniapp social app post develop upload image first

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

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

代码语言:javascript
复制
<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}}/9</view>
			</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如下:

代码语言:javascript
复制
<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>

显示:

uniapp social app post develop upload image info send
uniapp social app post develop upload image info send

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

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

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

代码语言:javascript
复制
<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中。

显示:

uniapp social app post develop delete image view
uniapp social app post develop delete image view

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

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

代码语言:javascript
复制
<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}}/9</view>
			</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修改如下:

代码语言:javascript
复制
<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>

显示:

uniapp social app post develop delete image finish
uniapp social app post develop delete image finish

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

6.保存草稿功能开发

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

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

代码语言:javascript
复制
// 监听返回
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;
	}
}

显示:

uniapp social app post develop save draft demo
uniapp social app post develop save draft demo

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

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

代码语言:javascript
复制
// 监听返回
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;
			}
		}
	})
},

显示:

uniapp social app post develop save draft text
uniapp social app post develop save draft text

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

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

代码语言:javascript
复制
<upload-image :list='imageList' @change="changeImage"></upload-image>

unpload-image.vue如下:

代码语言:javascript
复制
<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}}/9</view>
			</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>

显示:

uniapp social app post develop save draft image
uniapp social app post develop save draft image

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

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

代码语言:javascript
复制
<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>

显示:

uniapp social app post develop save draft back
uniapp social app post develop save draft back

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

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

如下:

代码语言:javascript
复制
<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如下:

代码语言:javascript
复制
<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}}/9</view>
			</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>

显示:

uniapp social app post develop save draft hide
uniapp social app post develop save draft hide

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

总结

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

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2021-01-28 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 文章目录
  • 前言
  • 一、搜索页开发
    • 1.搜索页面搭建
      • 2.搜索结果显示和优化
      • 二、发布页开发
        • 1.自定义导航栏开发
          • 2.文本域组件使用
            • 3.底部操作条组件开发
              • 4.多图上传功能开发
                • 5.删除选中图片功能实现
                  • 6.保存草稿功能开发
                  • 总结
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档