精华内容
下载资源
问答
  • 使用平台windows7python3.6MIX2手机代码原理手机屏幕内容同步到pc端对问题截图对截图文字分析用浏览器自动搜索文本使用教程1、使用Airdroid 将手机屏幕显示在电脑屏幕上。也可使用360手机助手实现。不涉及任何代码。...

    用python搭建百万答题、自动百度搜索答案。

    使用平台

    windows7

    python3.6

    MIX2手机

    代码原理

    手机屏幕内容同步到pc端

    对问题截图

    对截图文字分析

    用浏览器自动搜索文本

    使用教程

    1、使用Airdroid 将手机屏幕显示在电脑屏幕上。也可使用360手机助手实现。不涉及任何代码。实现效果如图:

    2018116133838734.jpg?2018016133848

    2、在提问出现时,运行python程序,将问题部分截图。

    2018116133858267.jpg?201801613397

    这里要用到两个函数:

    get_point() #采集要截图的坐标,以及图片的高度宽度

    window_capture() #截图

    def get_point():

    '''''采集坐标,并返回w,h,x,y。 作为window_capture() 函数使用'''

    try:

    print('正在采集坐标1,请将鼠标移动到该点')

    # print(3)

    # time.sleep(1)

    print(2)

    time.sleep(1)

    print(1)

    time.sleep(1)

    x1,y1 = pag.position() #返回鼠标的坐标

    print('采集成功,坐标为:',(x1,y1))

    print('')

    # time.sleep(2)

    print('正在采集坐标2,请将鼠标移动到该点')

    print(3)

    time.sleep(1)

    print(2)

    time.sleep(1)

    print(1)

    time.sleep(1)

    x2, y2 = pag.position() # 返回鼠标的坐标

    print('采集成功,坐标为:',(x2,y2))

    #os.system('cls')#清除屏幕

    w = abs(x1 - x2)

    h = abs(y1 - y2)

    x = min(x1, x2)

    y = min(y1, y2)

    return (w,h,x,y)

    except KeyboardInterrupt:

    print('获取失败')

    def window_capture(result,filename):

    '''''获取截图'''

    #宽度w

    #高度h

    #左上角截图的坐标x,y

    w,h,x,y=result

    hwnd = 0

    hwndDC = win32gui.GetWindowDC(hwnd)

    mfcDC = win32ui.CreateDCFromHandle(hwndDC)

    saveDC = mfcDC.CreateCompatibleDC()

    saveBitMap = win32ui.CreateBitmap()

    MoniterDev = win32api.EnumDisplayMonitors(None,None)

    #w = MoniterDev[0][2][2]

    # #h = MoniterDev[0][2][3]

    # w = 516

    # h = 514

    saveBitMap.CreateCompatibleBitmap(mfcDC,w,h)

    saveDC.SelectObject(saveBitMap)

    saveDC.BitBlt((0,0),(w,h),mfcDC,(x,y),win32con.SRCCOPY)

    saveBitMap.SaveBitmapFile(saveDC,filename)

    运行后截图如下

    2018116134020525.jpg?2018016134030

    3.对图片文字分析提取

    代码部分:

    def orc_pic():

    #识别中文

    text=pytesseract.image_to_string(Image.open('jietu.jpg'),lang='chi_sim')

    #识别英文

    # text=pytesseract.image_to_string(Image.open('jietu.jpg'))

    text = ''.join(text.split())

    return text

    4.对文本进行搜索

    #浏览器搜索

    url = 'http://www.baidu.com/s?wd=%s' % text

    webbrowser.open(url)

    所有代码如下:

    #coding:'utf-8'

    import win32gui, win32ui, win32con, win32api

    from PIL import Image

    import pytesseract

    import webbrowser

    #先下载pyautogui库,pip install pyautogui

    import os,time

    import pyautogui as pag

    #获取sdk http://ai.baidu.com/。

    #获取aip pip install git+https://github.com/Baidu-AIP/python-sdk.git@master

    from aip import AipOcr

    import json

    status=0

    """ 你的 APPID AK SK """

    APP_ID = '****'

    API_KEY = '***'

    SECRET_KEY = '***'

    client = AipOcr(APP_ID, API_KEY, SECRET_KEY)

    """ 读取图片 """

    def get_question(path):

    '''百度识别图片文字'''

    with open(path, 'rb') as fp:

    image=fp.read()

    res = client.basicGeneral(image)

    words = res['words_result']

    lines = [item['words'] for item in words]

    question = ''.join(lines)

    if question[1] == '.':

    question = question[2:]

    elif question[2] == '.':

    question = question[3:]

    return question.replace('?', ' ')

    #采集坐标

    def get_point():

    '''采集坐标,并返回w,h,x,y。 作为window_capture() 函数使用'''

    try:

    print('正在采集坐标1,请将鼠标移动到该点')

    # print(3)

    # time.sleep(1)

    print(2)

    time.sleep(1)

    print(1)

    time.sleep(1)

    x1,y1 = pag.position() #返回鼠标的坐标

    print('采集成功,坐标为:',(x1,y1))

    print('')

    # time.sleep(2)

    print('正在采集坐标2,请将鼠标移动到该点')

    print(3)

    time.sleep(1)

    print(2)

    time.sleep(1)

    print(1)

    time.sleep(1)

    x2, y2 = pag.position() # 返回鼠标的坐标

    print('采集成功,坐标为:',(x2,y2))

    #os.system('cls')#清除屏幕

    w = abs(x1 - x2)

    h = abs(y1 - y2)

    x = min(x1, x2)

    y = min(y1, y2)

    return (w,h,x,y)

    except KeyboardInterrupt:

    print('获取失败')

    #获取截图

    def window_capture(result,filename):

    '''获取截图'''

    #宽度w

    #高度h

    #左上角截图的坐标x,y

    w,h,x,y=result

    hwnd = 0

    hwndDC = win32gui.GetWindowDC(hwnd)

    mfcDC = win32ui.CreateDCFromHandle(hwndDC)

    saveDC = mfcDC.CreateCompatibleDC()

    saveBitMap = win32ui.CreateBitmap()

    MoniterDev = win32api.EnumDisplayMonitors(None,None)

    #w = MoniterDev[0][2][2]

    # #h = MoniterDev[0][2][3]

    # w = 516

    # h = 514

    saveBitMap.CreateCompatibleBitmap(mfcDC,w,h)

    saveDC.SelectObject(saveBitMap)

    saveDC.BitBlt((0,0),(w,h),mfcDC,(x,y),win32con.SRCCOPY)

    saveBitMap.SaveBitmapFile(saveDC,filename)

    def get_point_txt(status):

    #如果status=y,则重新获取坐标

    '''如果存在point.txt,则询问是否重新采集,删除point.txt;如果不存在txt,则直接采集。'''

    if not os.path.isfile('point.txt') :

    result = get_point()

    with open('point.txt', 'w') as f:

    f.write(str(result))

    return result

    else:

    if status=='y':

    result = get_point()

    with open('point.txt', 'w') as f:

    f.write(str(result))

    return result

    else:

    with open('point.txt', 'r') as f:

    result = f.readline()

    result = eval(result)

    return result

    def orc_pic():

    #识别中文

    text=pytesseract.image_to_string(Image.open('jietu.jpg'),lang='chi_sim')

    #识别英文

    # text=pytesseract.image_to_string(Image.open('jietu.jpg'))

    text = ''.join(text.split())

    return text

    #百度识别

    def orc_baidu():

    text=get_question('jietu.jpg')

    return text

    status='y'

    start = time.time()

    result=get_point_txt(status)

    for i in range(10):

    window_capture(result,'jietu.jpg')

    # text=orc_baidu()

    text=orc_pic()

    print(text)

    #浏览器搜索

    url = 'http://www.baidu.com/s?wd=%s' % text

    webbrowser.open(url)

    # url2='https://www.google.com/search?q=%s' % text

    # webbrowser.open(url2)

    end = time.time()

    time=end-start

    print('此次耗时%.1f秒' % time)

    以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

    展开全文
  • 最近需要给HTML5的WebAPP在页面上实现一个...在微信浏览器内是很容易实现长按文本激发系统菜单,高亮全选文本内容的。但是在其他浏览器的表现就不是很一致了。包括模拟focus input,JavaScript Selection, 使用a标签尝
  • 发布页需要自定义导航栏设置权限,需要使用uni-app官方提供组件;文本输入框使用textarea;底部操作条包括选择分类、添加话题、选择图片和发布按钮,需要设置尺寸和添加动画效果;图片上传用官方模板提供上传...

    如需查看本项目实际运行效果,可点击uni-app实战之社区交友APP(1)项目介绍和环境搭建(免费试读)进行浏览。
    如需本项目完整前端uni-app代码和资源文件,可以点击https://download.csdn.net/download/CUFEECR/15316002下载,或者订阅本专栏uni-app社区交友APP开发实战,即可在本专栏下序号为5的整数倍的博客(例如uni-app实战之社区交友APP(5)搜索和发布页开发uni-app实战之社区交友APP(10)登录、个人空间开发和动画优化)文末获取百度网盘链接和提取码。同时为了感谢各位读者的支持,订阅本专栏的各位小伙伴还可以获得uni-app入门视频和本专栏同步视频作为额外奖励。
    小编目前在做毕业设计,主题为“高考志愿信息交流平台”,面向高中生和大学生,辛苦各位读者大佬朋友们填下问卷,点击链接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)用于保留当前页面、跳转到应用内的某个页面,即跳转到搜索页。

    显示:
    uniapp social app search develop first

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

    现进一步完成搜索历史,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;
    }
    

    显示:
    uniapp social app search develop history keyword

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

    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()收起软键盘,同时获取数据。

    显示:
    uniapp social app search develop result

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

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

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

    显示:
    uniapp social app search develop loading

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

    再实现点击搜索历史进行搜索,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();
    }
    

    显示:
    uniapp social app search develop history click

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

    二、发布页开发

    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">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

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

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

    <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

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

    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>
    
    

    显示:
    uniapp social app post develop textarea

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

    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;
    }
    
    

    显示:
    uniapp social app post develop bottom first

    底部已有大概的轮廓。

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

    <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

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

    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}}/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组件,如下:

    <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

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

    现进一步调整样式,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}}/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如下:

    <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

    可以看到,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中。

    显示:
    uniapp social app post develop delete image view

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

    现增加点击事件、实现删除,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}}/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修改如下:

    <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

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

    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;
    	}
    }
    

    显示:
    uniapp social app post develop save draft demo

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

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

    // 监听返回
    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

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

    现在实现保存图片,需要父组件(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}}/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

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

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

    显示:
    uniapp social app post develop save draft back

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

    再实现隐藏图片添加预览区域、点击图标上传图片后再显示添加图片区域,原理是点击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}}/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

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

    总结

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

    本项目完整前端uni-app代码和资源文件网盘链接为https://pan.baidu.com/s/1eGQJblhN0QH_MTnhPFMHtg,提取码eq7x
    uni-app基础视频教程网盘链接为https://pan.baidu.com/s/1VMUk6XxHjUeNFLuB7TkrPg,提取码4rfg
    uni-app社区交友APP实战同步视频教程链接为https://pan.baidu.com/s/19CbKHsBJy4D2vQfZvluVtw,提取码2vuk
    欢迎各位读者大佬的支持和鼓励,请及时保存文件资源 ,防止链接被和谐

    展开全文
  • 用户可以搜索歌手或乐队。 输入搜索内容后,程序将为即将举行音乐会(日期,地点等)返回一些匹配项。 Spotify这首歌 该功能允许用户通过Spotify进行歌曲搜索。 系统会返回一些最接近匹配项,并提供名称,相册和...
  • 最近需要给html5WebAPP在页面上实现一个复制功能:用户点击长按文本会全选文字并弹出系统“复制”菜单,用户可以点击“复制”进行复制操作,然后粘贴到AppStore搜索对应应用。之所以不是采用链接形式直接跳转到...

    最近需要给html5的WebAPP在页面上实现一个复制功能:用户点击长按文本会全选文字并弹出系统“复制”菜单,用户可以点击“复制”进行复制操作,然后粘贴到AppStore搜索对应的应用。之所以不是采用链接形式直接跳转到AppStore对应应用是为了通过用户的主动输入关键词搜索给推广的企业App增加权重。所以这一个“复制”功能对用户的体验至关重要。

    尝试了一些做法,在安卓/iOS平台上的兼容性都不是很好。在微信浏览器内是很容易实现长按文本激发系统菜单,高亮全选文本内容的。但是在其他浏览器的表现就不是很一致了。包括模拟focus input,JavaScript Selection, 使用a标签尝试激活系统菜单。这些方法都存在兼容的缺陷。

    1)虽然使用带有href属性的a标签在uc浏览器和百度浏览器上长按文本会出现“自由复制”/“选择文本”菜单,选择该菜单后会出现“全选/复制”的菜单,但是在一些安卓手机的系统浏览器和iPhone中却被视为纯链接,只弹出“复制链接”,没有“复制文本”菜单。况且即使只考虑少部分浏览器可行,这样也给用户操作多了一步,增加了复杂度。所以该方案不可取。

    2)借助selection和range的方法需要考虑到不同浏览器的兼容性,代码如下:
    function selectText(element) {
      var doc = document,
          text = doc.getElementById(element),
          range,
          selection;
    
      if (doc.body.createTextRange) {
          range = document.body.createTextRange();
          range.moveToElementText(text);
          range.select();
      } else if (window.getSelection) {
          selection = window.getSelection();        
          range = document.createRange();
          range.selectNodeContents(text);
          selection.removeAllRanges();
          selection.addRange(range);
          /*if(selection.setBaseAndExtent){
              selection.setBaseAndExtent(text, 0, text, 1);
          }*/
      }else{
          alert("none");
      }
    }


    遗憾的是在iphone Safari上依然无法通过点击或长按高亮选中所有文本(既然也支持window.getSelection,为何在Safari浏览器addRange操作后文本不能默认选中,知道原因的请留言指教)。因此,该方式存在缺陷。主动选中文本区域的方法后面后附上。

    3)iPhone用户可能知道,长按某一文本选区内文字周围的空白区域,Safari会自动将该选区内的文本高亮全选(目标文本需要放在独立的div块级容器内)。根据这一特性,用CSS margin修饰一下,利用这个特点,正好可以解决上述第二种方法在ios设备的不兼容。经过测试,无论安卓和ios平台,一般手机自带的系统浏览器都是可以兼容的。至于uc浏览器、百度浏览器等其他厂家的移动端产品,由于有不同的机制,只能使用这些浏览器菜单提供的“自由复制”功能。

    所以,我综合了第二种和第三种方式,使用jquery mobile中的taphold事件来模拟longtap操作激发手机系统的复制菜单,这样基本上可以做到在所有移动设备浏览器上都能实现长按文本区域来高亮选中所有文本内容。再提一句,taphold的兼容bug这里就不详细附解决方法了,如果你的项目要求精益求精,你可以自行搜索解决方案。

    下面列出我的解决方案。具体代码如下:

    HTML代码:

    			<div class=" para requirement">
    				<div class="tips tips-t">
    					1、必须首次下载才生效<br/>
    					2、不能从排行榜下载哦
    				</div>
    				<div class="cparea">
    					<div class="kwd" id="kwd"><span>三国艳义手机优化大师</span></div>					
    				</div>
    				<div class="cparea">
    					<span class="kdes"><b>★</b>长按虚线框,拷贝关键词</span>
    				</div>
    				<a href="https://itunes.apple.com/cn/" data-role="button" class="downlink">去AppStore搜索下载</a>
    			</div>

    JavaScript代码:

    	<script type="text/javascript">
    
    	$("#kwd").bind("taphold", function(){ //不支持iPhone/iTouch/iPad Safari
    	    var doc = document, 
    	    	text = doc.getElementById("kwd"),
    	    	range, 
    	    	selection;
    	    if (doc.body.createTextRange) {
    	        range = document.body.createTextRange();
    	        range.moveToElementText(text);
    	        range.select();
    	    } else if (window.getSelection) {
    	        selection = window.getSelection();        
    	        range = document.createRange();
    	        range.selectNodeContents(text);
    	        selection.removeAllRanges();
    	        selection.addRange(range); 
    	    }else{
    	    	alert("浏览器不支持长按复制功能");
    	    }		
    	});
    
    	</script>

    关键的CSS代码:

    .cparea{
    	text-align: center;
    	font-family: Microsoft Yahei;
    	margin: -2em 0 0;
    }
    .kwd{
    	display: inline-block;
    	color: #272727;
    	background-color: #fff;
    	font-size: 1.1875em;
    	font-size: 1.1875em;
    	padding: .75em 1em;
    	border: 1px dashed #e60012;
    	-webkit-user-select:element; 
    	margin: 2em;
    }
    .kwd span{
    	display: block; 
    	border: 1px solid #fff;
    }
    .kdes{
    	display: inline-block;
    	color: #212121;
    	font-size: .875em;
    	padding-top: 0;
    }
    .kdes b{
    	color: #ed5353;
    	font-size: 1.25em;
    	padding-right: .1em;
    }

    说明:这里的margin:2em正是为了实现Safari浏览器上的长按全选功能,为了尊重还原设计稿效果,父容器.cparea又使用了负边距来抵消这个2em的外边距。最终,不仅视觉上和设计图保持了一致,也实现了长按全选激发系统菜单。


    最后再补充一下支持Safari下的完整方法:

    	$("#kwd").bind("taphold", function(){
    	    var doc = document, 
    	    	text = doc.getElementById("kwd"),
    	    	range, 
    	    	selection;
    	    if (doc.body.createTextRange) {	//IE
    	        range = document.body.createTextRange();
    	        range.moveToElementText(text);
    	        range.select();
    
    	    } else if (window.getSelection) {	//FF CH SF
    	        selection = window.getSelection();        
    	        range = document.createRange();
    	        range.selectNodeContents(text);
    	        selection.removeAllRanges();
    	        selection.addRange(range);
    
    	        //测试
    	        console.log(text.textContent);
    	        text.innerText && console.log(text.innerText);	//FireFox不支持innerText
    	        console.log(text.textContent.length);
    	        text.innerText && console.log(text.innerText.length);	//在Chrome下长度比IE/FF下多1
    	        console.log(text.firstChild.textContent.length);
    	        text.innerText && console.log(text.firstChild.innerText.length);
    	        console.log(text.firstChild.innerHTML.length);
    
    	        //注意IE9-不支持textContent
    	        makeSelection(0, text.firstChild.textContent.length, 0, text.firstChild);
            	/*
    			if(selection.setBaseAndExtent){
    	        	selection.selectAllChildren(text);
    	        	selection.setBaseAndExtent(text, 0, text, 4);
    	        }
    	        */
    	    }else{
    	    	alert("浏览器不支持长按复制功能");
    	    }
    	
    	});
    	function makeSelection(start, end, child, parent) {
    		var range = document.createRange();
    		//console.log(parent.childNodes[child]);
    		range.setStart(parent.childNodes[child], start);
    		range.setEnd(parent.childNodes[child], end);
    
    		var sel = window.getSelection();
    		sel.removeAllRanges();
    		sel.addRange(range); 
    	}


    转载请注明来自于CSDN freshlover的空间。



    展开全文
  • 经常会有朋友想知道有哪些 Apps 或 服务 是值得付费来使用,或者有哪些产品是可以提升生活品质, 于是创建了 BestApp 项目,旨在让推荐变得有章可循 总目录 欢迎大家推荐好 App/产品 加入,请Star、Pull ...
  • 简化对话应用程序 精简对话应用程序 结构 api是使用Express构建 simplifiedConversations是一个react-app 技术栈以及我为什么选择它们 React-轻松重用组件 打字稿-易于输入一些要求...集成测试搜索文本距离)。
  • App测试

    2018-09-21 21:00:00
    APP界面元素四大类: 各种栏: 状态栏,导航栏,标签栏,工具栏,搜索栏,范围栏 内容视图:列表视图,卡片试图,集合试图,图片视图,文本视图 控制元素:用于控制产品行为或显示信息 临时视图:警告...

    一 APP界面元素的四大类:

    各种栏:

    状态栏,导航栏,标签栏,工具栏,搜索栏,范围栏

    内容视图:列表视图,卡片试图,集合试图,图片视图,文本视图

    控制元素:用于控制产品行为或显示的信息

    临时视图:警告视图,操作列表,toast,模态视图

    2.各种栏

    状态栏:用来呈现信号,时间,电量等信息,Andriod系统还会显示未读信息的提示

    导航楼:也称标题栏,一般会显示标题,也可以放搜索,分段式控件或者其他功能入口,位于状态栏下方

    标签栏:让用户在不同的子任务,视图或模式中进行快速切换,标签栏上一般有三到五个图标,若超过5个,可以将第五个图标用更多提示,主菜单栏

    工具栏:工具栏上放置着用于操作当前页面各对象的控件,位于APP最底部

    3.内容视图

    列表形式,卡片形式,集合视图形式,图片形式,文本形式

    4.控制元素

    5.临时视图

    二 测试过程中容易忽视的点

    1.首次启动——功能介绍或引导图

    2.初始状态或空数据状态——空页面处理(是否有提示,比如你朋友圈从没发布时候,提醒你快去发一条朋友圈吧)

    3.app端不会一次性加载完数据,分多次加载——分页处理(上下滑动)

    4.消息推送——角标,数字或红点,提示未读信息数

    5.图标,按钮,常态,选中态,不可点击态——不同颜色显示

    三 APP测试范围

    界面测试:核对切图/效果图(UI设计)

    功能测试:安装卸载升级,消息推送,离线,对照产品给与的需求进行相关测试

    兼容性测试:操作系统(Andriod和IOS),屏幕尺寸,分辨率——真机二手,公司已有的都要适配,市场上主流用的手机(百度流量可显示目前市场主流),云测试平台局限

    弱网测试:(存在关键性数据提交)可用Fiddle设置(比如充值功能,弱网络多次提交只能被执行一次

    中断测试(断网重连,断电,低电量,来电话,听音乐。。。。)

    数据实时交换(重点功能):比如微信(群里你一直发送,别人也在发

    压力测试,稳定性测试,安全测试(有些功能页面未登录的时候操作不了)

     

    转载于:https://www.cnblogs.com/wz123/p/9688452.html

    展开全文
  • “飞花令”APP(简单收索软件)

    千次阅读 2017-03-04 23:51:06
    UI 界面中有基本元素如按钮,搜索框,文本输出框等。 在收索框中随意输入一个汉字可以以此判断是否存在唐诗三百首诗词当中,若存在,则 输出全部含有该汉字诗句;若没有,则不会输出任何内容。 二. 设计方案1. ...
  • 求学之路上,汗水总是与你相伴,但合适的App定能帮你减轻不少负担。MacW为努力的你精选了 些好工具,快把它们收入囊中。...你还能用印象笔记来存储和整理堆积成山的笔记,它的搜索功能可以帮你轻松找到需要的内容出色演
  • 我不直接搜索 因为搜索结果参差不齐 而小猿拍照搜题结果是经过优化 无广告无富文本 有哪位大神可以帮帮我?感激不尽。 个人对这一领域了解不多 本人设备:已越狱iPad 一台,已root Android手机一部,电脑...
  • 您可能要讲的内容: Ruby版本 系统依赖 配置 数据库创建 数据库初始化 如何运行测试套件 服务(作业队列,缓存服务器,搜索引擎等) 部署说明 … 如果您不打算运行rake doc:app请随意使用其他标记语言。
  • 文档结构和内容设计根据: : 我们使用 Android 4.x 进行开发 任务: 必须创建一个程序,从智能手机相机拍摄照片并在显示屏上实时显示。 应该可以在显示之前修改图像数据。 RGB 颜色空间将被量化。 基于文献搜索...
  • 8.对文章内容的全文搜索。 9.小程序页面的分享和转发。 10.WordPress 插件的配套功能。 11.文章浏览数显示及更新。 12.文章微信用户点赞及点赞的微信用户头像显示。 13.通过微信支付对文章赞赏。 14.分享到朋友圈 15...
  • impfWidget 该项目将于2021年3月9日停产 免费约会 没有约会 详细视图 免费日期通知 没有约会 一般 这个为Scriptable.app编写小部件显示了本地疫苗接种... 用复制的文本替换大括号之间所有内容 const CENTER = {
  • 智能表格阅读器应用程序项目深蓝色 非政府组织大多数运行计划中,辅导员/档案工作者必须访问基础设施... 市场上有各种各样OCR软件,它们可以将文档扫描图像转换为可搜索的文本。 当图像中的内容为印刷形式时,
  • 除了基本开发标签语言及表达式(参见同一下载包中开发手册)外,轻开平台特别针对移动App最常用json文本格式开发对应规则:  文件扩展名须为json(如:one.json)或用JspEasy扩展  文件内容须为闭合json...
  • OCR专业版 OCR pro是使用Google Apps脚本编写免费网络应用程序... OCR处理完成后,您将能够复制粘贴或编辑该图像的文本内容。 它是如何工作? 该脚本使用了GoogleOCR技术,您知道可以,然后将文件上传到Google云
  • 《BBEdit》堪称文本编辑器中神器:无论你是编辑源代码、创作小说还是建设网站,这款 App可以为你节省不少时间和力气。 例如,《BBEdit》拥有卓越查找替换功能:它不但能在单一文档中进行查找,还能同时在多个...
  •  2.App内容 ...抓包主要运用于获取App数据,把PC和手机设置同一个局域网内,在电脑上安装好抓包软件,把ip地址记录下来,在手机的网络地址里设置代理,再打开手机的App进行操作,如果有数据发送请求
  • 作为搜索替换文本的强大工具,正则表达式(通常被称为“Grep”)可能会让初学者望而生畏——...《BBEdit》的互动式“Pattern Playground”功能为你提供搜索与替换内容的实时预览,你可以利用它来轻松学习正则表达式。
  • 作为搜索替换文本的强大工具,正则表达式(通常被称为“Grep”)可能会让初学者望而生畏...《BBEdit》的互动式“Pattern Playground”功能为你提供搜索与替换内容的实时预览,你可以利用它来轻松学习正则表达式。 下面
  • 在屏幕中间向下滑动即可打开该项功能,你可以搜索文本、邮件、应用、歌曲、联系人等内容。  你可以通过设置让IOS 7自动帮你更新应用,更新期间无需打开App Store应用商店。具体设置是:打开“设置”应用 >iTunes...
  • 可以使用它来搜索文本,图像,视频,音频或带有任何类型数据。 jinabox.js在行动 启动Jina Docker映像 jinabox.js是前端。 您将需要启动Jina后端以获取搜索结果。 根据您要搜索的内容,运行我们示例Jina...
  • 1. 利用grep ,wc命令统计某个请求或字符串出现次数 比如统计GET /app/kevinContent接口在某天调用次数,则可以使用如下命令: ...其中cat用来读取日志内容,grep进行匹配的文本搜索,wc则进行最终统计。 当...

空空如也

空空如也

1 2 3 4 5 ... 7
收藏数 130
精华内容 52
关键字:

可以搜索app文本内容的app