
【实战】uni-app阿里直播实战项目开发
基于Vue + 阿里直播实战开发直播APP,涉及直播一系列完整的流程,从主播开播到阿里直播自动生成推流地址和播(拉)流地址,用户在APP上点击进行观看直播。
或:
https://www.studyfox.cn/487.html
课程目录:
第1讲_项目介绍
第2讲_uniapp前端项目搭建
第3讲_引入全局样式和图标库
第4讲_引入全局样式和图标库
第5讲_底部导航设计
第6讲_底部导航设计
第7讲_底部导航切换
第8讲_创建直播列表模拟数据
第9讲_首页直播列表显示
第10讲_阿里云视频直播设置
第11讲_阿里云视频直播推流播流地址说明
第12讲_管理后台搭建
第13讲_vscode远程连接服务器
第14讲_阿里云视频直播SDK在线安装
第15讲_阿里云视频直播调用API设计
第16讲_阿里云视频直播调用API设计
第17讲_阿里云直播创建签名方法
第18讲_阿里云直播创建推流地址
第19讲_阿里云直播创建拉流地址
第20讲_创建直播数据表
第21讲_通过API接口将推流拉流地址写入数据库
第22讲_管理后台直播列表显示
第23讲_管理后台直播列表显示
第24讲_管理后台直播列表显示
第25讲_uniapp前端公共方法封装
第26讲_uniapp前端公共方法封装
第27讲_API接口获取直播列表
第28讲_uniapp首页获取直播列表
第29讲_uniapp直播功能
第30讲_uniapp直播功能
第31讲_uniapp直播调试
第32讲_uniapp直播页面添加顶部导航
第33讲_uniapp直播页面顶部导航完善
第34讲_uniapp直播真机调试
第35讲_uniapp直播真机调试
第36讲_uniapp观看直播页面设计
第37讲_uniapp观看直播页面设计
第38讲_获取直播在线人数
第39讲_定时检测直播状态
第40讲_uniapp首页直播状态显示
基于Vue + 阿里直播实战开发直播APP,涉及直播一系列完整的流程,从主播开播到阿里直播自动生成推流地址和播(拉)流地址,用户在APP上点击进行观看直播。
或:
https://www.studyfox.cn/487.html
课程目录:
第1讲_项目介绍
第2讲_uniapp前端项目搭建
第3讲_引入全局样式和图标库
第4讲_引入全局样式和图标库
第5讲_底部导航设计
第6讲_底部导航设计
第7讲_底部导航切换
第8讲_创建直播列表模拟数据
第9讲_首页直播列表显示
第10讲_阿里云视频直播设置
第11讲_阿里云视频直播推流播流地址说明
第12讲_管理后台搭建
第13讲_vscode远程连接服务器
第14讲_阿里云视频直播SDK在线安装
第15讲_阿里云视频直播调用API设计
第16讲_阿里云视频直播调用API设计
第17讲_阿里云直播创建签名方法
第18讲_阿里云直播创建推流地址
第19讲_阿里云直播创建拉流地址
第20讲_创建直播数据表
第21讲_通过API接口将推流拉流地址写入数据库
第22讲_管理后台直播列表显示
第23讲_管理后台直播列表显示
第24讲_管理后台直播列表显示
第25讲_uniapp前端公共方法封装
第26讲_uniapp前端公共方法封装
第27讲_API接口获取直播列表
第28讲_uniapp首页获取直播列表
第29讲_uniapp直播功能
第30讲_uniapp直播功能
第31讲_uniapp直播调试
第32讲_uniapp直播页面添加顶部导航
第33讲_uniapp直播页面顶部导航完善
第34讲_uniapp直播真机调试
第35讲_uniapp直播真机调试
第36讲_uniapp观看直播页面设计
第37讲_uniapp观看直播页面设计
第38讲_获取直播在线人数
第39讲_定时检测直播状态
第40讲_uniapp首页直播状态显示
收起阅读 »
下拉刷新新人踩坑经验
发帖背景:项目需要在用户登录后如果出现数据未加载的情况允许用户下拉刷新,重新获取数据。
官方文档对下拉刷新的说明:https://uniapp.dcloud.io/api/ui/pulldown.html#onpulldownrefresh
踩坑1:(个人问题)

可以看到用户需要开启下拉刷新,但是由于我需要刷新的是tabbar页面,所以我非常愚钝的设置在了(均为pages.json文件内)tabBar列表而不是pages(忘记了)从而导致无法开启下拉刷新
踩坑2:Error: [xxx]: :require(...).main is not a function
这种情况大家参加这篇文章:https://bobbyhadz.com/blog/javascript-typeerror-require-is-not-a-function
我会触发这个报错的原因是我在vue页面使用下拉刷新调用了云端函数重新拉取数据,也就是在云函数引用 const path = require('path');时结尾没有加 ;分号,补上重新运行就好了
最后附上完整函数,写在 script 即可
async onPullDownRefresh () {
console.log('触发下拉刷新了');
await userService.afterLogin(); //这里是我用于登录后拉取数据的函数,没有异步函数的话不需要写 async 和 await
setTimeout(()=>{
uni.stopPullDownRefresh(); //停止刷新
},2000)
}
发帖背景:项目需要在用户登录后如果出现数据未加载的情况允许用户下拉刷新,重新获取数据。
官方文档对下拉刷新的说明:https://uniapp.dcloud.io/api/ui/pulldown.html#onpulldownrefresh
踩坑1:(个人问题)
可以看到用户需要开启下拉刷新,但是由于我需要刷新的是tabbar页面,所以我非常愚钝的设置在了(均为pages.json文件内)tabBar列表而不是pages(忘记了)从而导致无法开启下拉刷新
踩坑2:Error: [xxx]: :require(...).main is not a function
这种情况大家参加这篇文章:https://bobbyhadz.com/blog/javascript-typeerror-require-is-not-a-function
我会触发这个报错的原因是我在vue页面使用下拉刷新调用了云端函数重新拉取数据,也就是在云函数引用 const path = require('path');时结尾没有加 ;分号,补上重新运行就好了
最后附上完整函数,写在 script 即可
async onPullDownRefresh () {
console.log('触发下拉刷新了');
await userService.afterLogin(); //这里是我用于登录后拉取数据的函数,没有异步函数的话不需要写 async 和 await
setTimeout(()=>{
uni.stopPullDownRefresh(); //停止刷新
},2000)
}

交友盲盒源码下载及搭建
网络约会业务是互联网的未来。互联网已经极大地改变了我们的互动方式,如今超过 85% 的人口正在使用约会网络应用程序与朋友保持联系或结识新朋友。现在,人们甚至在去酒吧与其他人会面之前首先使用互联网。使用互联网与某人约会的人数每年都在增加。约会业务已成为网络上第二大最赚钱的市场。 所有领先的约会业务每年都在增加越来越多的收入,而您可能是下一个!
交友盲盒演示:m.ymzan.top
交友盲盒是一个原生应用程序源代码,具有时尚的设计和完整功能以及额外的高级功能。即使您不是编码专家,也可以使用我们的应用程序源代码。获取应用程序源代码并开始构建,或者我们为您提供最物有所值的定制包。
交友盲盒源码是使用最新网络技术的最强大的现成约会网站生成器,包括创建成功的约会网络应用程序所需的所有约会功能
简单、快速和智能:交友盲盒源码的构建非常快速、安全,并集成了隐形智能垃圾邮件检测系统。最后,交友盲盒源码基于当今可用的最新 Web 技术。
非常有趣:我们的软件对您和您的访客来说非常有趣和简单!
约会服务和社区网站:社交约会 CMS 非常适合约会网站和社交社区门户。
多主题和无限定制:借助我们的 CMS 架构,在创纪录的时间内构建独特的社交/约会主题。
国际化系统:我们的社区约会软件可以被轻松翻译成任何语言。
借助集成到软件中的会员模块,让人们无需花费一美元就可以为您的网站做广告!
为数据库、模板文件、静态文件(HTML、CSS、JS)、字符串内容等提供强大的智能缓存系统。缓存系统还会压缩内容以节省服务器上的带宽并降低其成本。
针对寻求专业且高度可定制的软件的经验丰富的开发人员和公司进行了优化。
继续使用 100% 的“RSS 源”:所有内容模块都包含在 RSS 源输出中,以便您在任何不同的应用程序、站点、小部件等中获取和集成数据,甚至向合作伙伴、开发人员或用户。
完整的 API: 交友盲盒源码提供了一个 RESTful API(和一个检索数据类帮助程序),以便您可以创建 iOS(iPhone、iPad、Apple Watch)、Android 或任何其他类型的 Web 或电话应用程序您网站的用户数据。
你的数据是你的!在您想要的任何地方托管您的网络服务,控制您的数据,并且不依赖任何人!
完全控制数据,最终完全控制您的业务。
美丽的代码:非常彻底地评论了整个 PHP 代码中发生的事情,漂亮的缩进和可读性,即使对于非程序员也是如此。
交友盲盒源码的社交/约会功能
强大的反垃圾邮件系统(并检测重复的内容)。
SEO 友好(标题,内容,代码,...),站点地图模块,...
Schema.org 标记和微数据,您的社交约会网络应用程序将大大提高搜索引擎结果的排名,并启用强大的功能!
支持多种语言、国际化和本地化。
多语言 URL无论是哪种语言,所有 URL 都可以翻译,以提高您约会平台的 SEO。
动态用户字段管理
内置的基本统计和分析工具。
使用内置 RandomUser.me API 创建高质量配置文件的简单方法
使用管理面板的 IP 限制阻止访问/
管理员/用户/附属机构的两步验证选项。
智取审核工具
附近的人技术
相关配置文件功能(更容易匹配)
强大的反垃圾邮件系统(并检测重复的内容)。
内置会员系统,鼓励品牌影响者和大使推广您的约会服务。
会员和会员注册表格的国家限制
国际时间和日期格式
使用交友盲盒源码,任何人都可以在一天左右的时间内启动像其他社交约会网络应用程序,无需任何技术知识交友盲盒源码是最强大的现成约会网站生成器,使用最新的网络技术并包含您需要的所有约会功能创建一个类似或克隆的 Web 应用程序。如果您是开发人员/程序员,该软件的代码经过特别编码以便始终被理解,并包含每个操作的解释性注释,以防您想要进行一些修改。
我们提供三个高质量和专业的约会模板,高度可定制,并且 100% 兼容所有现代和流行的移动设备(以及桌面设备)。
与其他人不同,我们不会创建数百个约会模板。您可能已经看到提供 10、50 甚至数百个约会主题的其他竞争对手只是在撒谎。他们只是在快速测试一个模板是否适用于每个版本,而其他模板有很多错误,与每个设备/浏览器不兼容,错误并且不能与新功能正常工作。这就是为什么在 交友盲盒源码中,重要的是质量而不是数量!
与任何其他竞争对手不同,交友盲盒源码不仅是一个“约会软件提供商”,而且是一个真正的约会业务解决方案。
通过选择交友盲盒源码,您可以获得完整的源代码(没有加密!),让您(或为您工作的开发人员)有权进行任何修改和/或添加任何新功能。
与竞争对手不同,交友盲盒源码的构建首先考虑了安全性,它现在是当今市场上最安全的产品。交友盲盒源码也是开源的,非常适合想要构建严肃的社交约会网站/应用程序的开发人员。
交友盲盒源码是 100%基于模板的方法, 这意味着您可以轻松 自定义其设计或创建新模板。
与我们所有的竞争对手不同,在 交友盲盒源码,我们之前都参与过在线社交/约会应用程序 ,并且我们会不断了解领先约会网站使用的最新“技术和资料”。是的,我们真的很喜欢这个主题,我们绝对想让 交友盲盒源码成为行业中最好的!
交友盲盒源码使用 GetText 来翻译页面。如今,GetText 是翻译应用程序的最佳方式。比简单的 PHP 语言文件快得多,因为 GetText 文件针对高流量在线 Web 应用程序进行了编译和高度优化。
通过选择 交友盲盒源码,您可以获得 MIT 许可下的完整源代码(没有加密!),让您(或您的开发人员)有权对软件进行任何更改。
“交友盲盒源码”社交约会软件包括几个模块,可以分散用户的注意力,让他们一次又一次地返回您的网站,使其成为病毒式的和有趣的的!
社交友好,它还提供微信、QQ、微博等集成、聊天、爱情计算器、即时通讯工具、、聊天室……
借助强大的交互式内置通信工具,您的用户可以随时随地表达他们的意见、分享他们的想法并抓住他们难忘的时刻,并与世界各地的其他用户和朋友直接、即时地互动!
自托管
你的数据是你的!在您想要的任何地方托管您的网络服务,控制您的数据,并且不依赖任何人!为什么您的企业必须受制于他人?使用 交友盲盒源码,您可以选择您自己的服务器、您自己的数据和您的应用程序文件。通过选择 交友盲盒源码,您终于可以完全掌控您的业务了!
基于高度安全的框架;包括垃圾邮件保护系统
该软件基于“pCO8 Security pH7Framework”,它提供针对垃圾邮件、暴力攻击、重复内容、大量消息、大量评论/帖子的最高保护之一,并具有针对垃圾邮件的独特算法保护系统,有助于您与最不受欢迎的用户作斗争。
您还可以阻止整个国家/地区访问您的网站。
最后,它完全防止 SQL 注入、XSS、CSRF、会话固定、身份验证黑客、蛮力登录、反诈骗,甚至可以防止一些DDoS攻击!
网络约会业务是互联网的未来。互联网已经极大地改变了我们的互动方式,如今超过 85% 的人口正在使用约会网络应用程序与朋友保持联系或结识新朋友。现在,人们甚至在去酒吧与其他人会面之前首先使用互联网。使用互联网与某人约会的人数每年都在增加。约会业务已成为网络上第二大最赚钱的市场。 所有领先的约会业务每年都在增加越来越多的收入,而您可能是下一个!
交友盲盒演示:m.ymzan.top
交友盲盒是一个原生应用程序源代码,具有时尚的设计和完整功能以及额外的高级功能。即使您不是编码专家,也可以使用我们的应用程序源代码。获取应用程序源代码并开始构建,或者我们为您提供最物有所值的定制包。
交友盲盒源码是使用最新网络技术的最强大的现成约会网站生成器,包括创建成功的约会网络应用程序所需的所有约会功能
简单、快速和智能:交友盲盒源码的构建非常快速、安全,并集成了隐形智能垃圾邮件检测系统。最后,交友盲盒源码基于当今可用的最新 Web 技术。
非常有趣:我们的软件对您和您的访客来说非常有趣和简单!
约会服务和社区网站:社交约会 CMS 非常适合约会网站和社交社区门户。
多主题和无限定制:借助我们的 CMS 架构,在创纪录的时间内构建独特的社交/约会主题。
国际化系统:我们的社区约会软件可以被轻松翻译成任何语言。
借助集成到软件中的会员模块,让人们无需花费一美元就可以为您的网站做广告!
为数据库、模板文件、静态文件(HTML、CSS、JS)、字符串内容等提供强大的智能缓存系统。缓存系统还会压缩内容以节省服务器上的带宽并降低其成本。
针对寻求专业且高度可定制的软件的经验丰富的开发人员和公司进行了优化。
继续使用 100% 的“RSS 源”:所有内容模块都包含在 RSS 源输出中,以便您在任何不同的应用程序、站点、小部件等中获取和集成数据,甚至向合作伙伴、开发人员或用户。
完整的 API: 交友盲盒源码提供了一个 RESTful API(和一个检索数据类帮助程序),以便您可以创建 iOS(iPhone、iPad、Apple Watch)、Android 或任何其他类型的 Web 或电话应用程序您网站的用户数据。
你的数据是你的!在您想要的任何地方托管您的网络服务,控制您的数据,并且不依赖任何人!
完全控制数据,最终完全控制您的业务。
美丽的代码:非常彻底地评论了整个 PHP 代码中发生的事情,漂亮的缩进和可读性,即使对于非程序员也是如此。
交友盲盒源码的社交/约会功能
强大的反垃圾邮件系统(并检测重复的内容)。
SEO 友好(标题,内容,代码,...),站点地图模块,...
Schema.org 标记和微数据,您的社交约会网络应用程序将大大提高搜索引擎结果的排名,并启用强大的功能!
支持多种语言、国际化和本地化。
多语言 URL无论是哪种语言,所有 URL 都可以翻译,以提高您约会平台的 SEO。
动态用户字段管理
内置的基本统计和分析工具。
使用内置 RandomUser.me API 创建高质量配置文件的简单方法
使用管理面板的 IP 限制阻止访问/
管理员/用户/附属机构的两步验证选项。
智取审核工具
附近的人技术
相关配置文件功能(更容易匹配)
强大的反垃圾邮件系统(并检测重复的内容)。
内置会员系统,鼓励品牌影响者和大使推广您的约会服务。
会员和会员注册表格的国家限制
国际时间和日期格式
使用交友盲盒源码,任何人都可以在一天左右的时间内启动像其他社交约会网络应用程序,无需任何技术知识交友盲盒源码是最强大的现成约会网站生成器,使用最新的网络技术并包含您需要的所有约会功能创建一个类似或克隆的 Web 应用程序。如果您是开发人员/程序员,该软件的代码经过特别编码以便始终被理解,并包含每个操作的解释性注释,以防您想要进行一些修改。
我们提供三个高质量和专业的约会模板,高度可定制,并且 100% 兼容所有现代和流行的移动设备(以及桌面设备)。
与其他人不同,我们不会创建数百个约会模板。您可能已经看到提供 10、50 甚至数百个约会主题的其他竞争对手只是在撒谎。他们只是在快速测试一个模板是否适用于每个版本,而其他模板有很多错误,与每个设备/浏览器不兼容,错误并且不能与新功能正常工作。这就是为什么在 交友盲盒源码中,重要的是质量而不是数量!
与任何其他竞争对手不同,交友盲盒源码不仅是一个“约会软件提供商”,而且是一个真正的约会业务解决方案。
通过选择交友盲盒源码,您可以获得完整的源代码(没有加密!),让您(或为您工作的开发人员)有权进行任何修改和/或添加任何新功能。
与竞争对手不同,交友盲盒源码的构建首先考虑了安全性,它现在是当今市场上最安全的产品。交友盲盒源码也是开源的,非常适合想要构建严肃的社交约会网站/应用程序的开发人员。
交友盲盒源码是 100%基于模板的方法, 这意味着您可以轻松 自定义其设计或创建新模板。
与我们所有的竞争对手不同,在 交友盲盒源码,我们之前都参与过在线社交/约会应用程序 ,并且我们会不断了解领先约会网站使用的最新“技术和资料”。是的,我们真的很喜欢这个主题,我们绝对想让 交友盲盒源码成为行业中最好的!
交友盲盒源码使用 GetText 来翻译页面。如今,GetText 是翻译应用程序的最佳方式。比简单的 PHP 语言文件快得多,因为 GetText 文件针对高流量在线 Web 应用程序进行了编译和高度优化。
通过选择 交友盲盒源码,您可以获得 MIT 许可下的完整源代码(没有加密!),让您(或您的开发人员)有权对软件进行任何更改。
“交友盲盒源码”社交约会软件包括几个模块,可以分散用户的注意力,让他们一次又一次地返回您的网站,使其成为病毒式的和有趣的的!
社交友好,它还提供微信、QQ、微博等集成、聊天、爱情计算器、即时通讯工具、、聊天室……
借助强大的交互式内置通信工具,您的用户可以随时随地表达他们的意见、分享他们的想法并抓住他们难忘的时刻,并与世界各地的其他用户和朋友直接、即时地互动!
自托管
你的数据是你的!在您想要的任何地方托管您的网络服务,控制您的数据,并且不依赖任何人!为什么您的企业必须受制于他人?使用 交友盲盒源码,您可以选择您自己的服务器、您自己的数据和您的应用程序文件。通过选择 交友盲盒源码,您终于可以完全掌控您的业务了!
基于高度安全的框架;包括垃圾邮件保护系统
该软件基于“pCO8 Security pH7Framework”,它提供针对垃圾邮件、暴力攻击、重复内容、大量消息、大量评论/帖子的最高保护之一,并具有针对垃圾邮件的独特算法保护系统,有助于您与最不受欢迎的用户作斗争。
您还可以阻止整个国家/地区访问您的网站。
最后,它完全防止 SQL 注入、XSS、CSRF、会话固定、身份验证黑客、蛮力登录、反诈骗,甚至可以防止一些DDoS攻击! 收起阅读 »

easyinput 添加readonly
input 什么时候可以添加readonly属性,我看到19年就有人提这个需求了,现在还是没有添加
input 什么时候可以添加readonly属性,我看到19年就有人提这个需求了,现在还是没有添加

WebDAV client for uni-app
https://github.com/kytrun/webdav-client-uniapp
Fork 自 webdav-client,添加了 uni.request 作为 axios adaper,目前基本可用,没有测试全部功能。需要注意的是坚果云提供的 WebDAV 并非标准的 WebDAV,部分接口有问题可能在其他服务上表现正常。
下载 https://github.com/kytrun/webdav-client-uniapp/blob/master/dist/uniapp/webdav.js 文件在 uni-app 项目中以 ES module 导入使用。
坚果云 WebDAV 示例:
import {
AuthType,
createClient
} from "@/deps/webdav"
const client = createClient(
"https://dav.jianguoyun.com/dav/test/", {
authType: AuthType.Password,
username: `${username}`,
password: `${password}`
})
client.getFileContents("test.txt", {
format: "text"
})
.then(data => console.log(data))
.catch(e => console.log(e))
更多接口见项目文档。
https://github.com/kytrun/webdav-client-uniapp
Fork 自 webdav-client,添加了 uni.request 作为 axios adaper,目前基本可用,没有测试全部功能。需要注意的是坚果云提供的 WebDAV 并非标准的 WebDAV,部分接口有问题可能在其他服务上表现正常。
下载 https://github.com/kytrun/webdav-client-uniapp/blob/master/dist/uniapp/webdav.js 文件在 uni-app 项目中以 ES module 导入使用。
坚果云 WebDAV 示例:
import {
AuthType,
createClient
} from "@/deps/webdav"
const client = createClient(
"https://dav.jianguoyun.com/dav/test/", {
authType: AuthType.Password,
username: `${username}`,
password: `${password}`
})
client.getFileContents("test.txt", {
format: "text"
})
.then(data => console.log(data))
.catch(e => console.log(e))
更多接口见项目文档。
收起阅读 »
语音播报-前台后台离线推送语音播报、到账xx元、收款播报、自定义推送铃(ios)
语音播报-前台后台离线推送语音播报、到账xx元、收款播报、自定义推送铃(ios):https://ext.dcloud.net.cn/plugin?id=8452
语音播报-前台后台离线推送语音播报、到账xx元、收款播报、自定义推送铃(ios):https://ext.dcloud.net.cn/plugin?id=8452

DCloud平台用户信息修改,插件作者未更新
更新用户信息后,为什么发布的插件作者那里没有变,点击之后还提示找不到此用户
更新用户信息后,为什么发布的插件作者那里没有变,点击之后还提示找不到此用户

希望可以在js里面输入未导入的变量时可以自动导入
希望可以在js里面输入未导入的变量时可以自动导入,像webstorm一样,,不然第一次写的都只能去复制,这样可以省去更多的时间;
并且希望可以在scss文件里面也可以使用格式化功能,现在无法使用格式化功能,只能自己手动排版,非常麻烦!
希望可以在js里面输入未导入的变量时可以自动导入,像webstorm一样,,不然第一次写的都只能去复制,这样可以省去更多的时间;
并且希望可以在scss文件里面也可以使用格式化功能,现在无法使用格式化功能,只能自己手动排版,非常麻烦!

迫于新版HX诸多问题,放一个我现在一直在用的3.3.13版本
迫于新版HX诸多问题,看到有不少同学在找历史版本,在这放一个现在一直在用的3.3.13版本吧

图片在容器下自由缩放旋转拖动Demo
先实现如下效果,图片可自由缩放旋转删除移动。通过两个点控制。
实现效果Demo如下:
这是Demo代码,可直接复制到项目中运行看效果。
支持小程序、APP。
一开始考虑使用的movable-area+movable-view。但因为需要先禁用移动,当点击的是右下角旋转按钮时才允许移动,所以需要在touchstart里面disabled改为false,然后再touchend中再次修改为禁止移动。
但movable-view不支持在touchstart中修改disabled立刻生效,只能在下次点击时生效,所以使用view+css实现。
<template>
<view class="page-body">
<view class="move-area">
<view v-if="!isHide" class="move-box" style="transform-origin:center;"
@touchmove="itemMove" @touchstart="itemtouch" @touchend="itemend"
:style="{transform: 'translate(' + itemX + 'px, ' + itemY +'px) rotate('+tmpRotate+'deg) scale('+tmpScale+')'}">
<view class="opt-icon del-icon" :style="{transform: 'scale(' + (1 / tmpScale) + ')'}" @touchstart="startMode(2)"></view>
<view class="opt-icon edit-icon" :style="{transform: 'scale(' + (1 / tmpScale) + ')'}" @touchstart="startMode(1)"></view>
<view class="move-body" @touchstart="startMode(0)">这是内容</view>
</view>
</view>
</view>
</template>
<script>
const nearDg = 7; // 用于控制当近似垂直或水平时,视为垂直或水平的容差。
let lastX = 0, lastY = 0;
export default {
data() {
return {
itemX: 0,
itemY: 0,
realCenterX: 0,
realCenterY: 0,
halfWidth: 75, // px
halfHeight: 75, // px
realWidth: 0,
realHeight: 0,
offset: 12.5, // px
touchMode: 0, // 0移动 1缩放旋转 2删除
startX: 0,
startY: 0,
startAngle: 0,
startDist: 0,
tmpRotate: 0,
initRotate: 0,
tmpScale: 1,
initScale: 1,
isHide: false,
}
},
onLoad:function(){
this.itemX = 70;
this.itemY = 100;
},
mounted:function(){
let view = uni.createSelectorQuery().in(this).select(".move-box");
view.boundingClientRect(data => {
this.realWidth = data.width;
this.realHeight = data.height;
this.realCenterX = data.right - data.width / 2;
this.realCenterY = data.bottom - data.height / 2;
}).exec();
},
methods: {
itemMove: function(e){
let curX = e.touches[0].clientX;
let curY = e.touches[0].clientY;
if(this.touchMode === 0){
this.itemX += curX - lastX;
this.itemY += curY - lastY;
this.realCenterX += curX - lastX;
this.realCenterY += curY - lastY;
lastX = curX;
lastY = curY;
}
if(this.touchMode !== 1)return;
let angle = this.angle(this.realCenterX, this.realCenterY, curX, curY);
if(this.startAngle === 0){
this.startX = curX;
this.startY = curY;
this.startAngle = angle;
this.startDist = Math.hypot(this.startX - this.realCenterX, this.startY - this.realCenterY);
}
else{
let rotate = this.initRotate + Math.round(angle - this.startAngle);
let leftDg = Math.abs(rotate % 90);
if(leftDg < nearDg || 90 - leftDg < nearDg){
rotate = Math.round(rotate / 90) * 90;
}
this.tmpRotate = rotate;
let curDist = Math.hypot(curX - this.realCenterX, curY - this.realCenterY);
this.tmpScale = this.initScale * curDist / this.startDist;
}
},
itemtouch: function(e){
lastX = e.touches[0].clientX;
lastY = e.touches[0].clientY;
},
itemend: function(e){
this.startAngle = 0;
this.initRotate = this.tmpRotate;
this.initScale = this.tmpScale;
},
startMode: function(mode){
this.touchMode = mode;
if(this.touchMode === 2){
this.isHide = true;
}
},
angle: function(cx, cy, ex, ey){
var dy = ey - cy;
var dx = ex - cx;
var theta = Math.atan2(dy, dx); // range (-PI, PI]
theta *= 180 / Math.PI; // rads to degs, range (-180, 180]
if (theta < 0) theta = 360 + theta; // range [0, 360)
return theta;
}
}
}
</script>
<style lang="scss">
.page-body{
padding: 5px;
}
.move-area{
width: 365px;
height: 500px;
}
.move-box{
position: relative;
width: 175px;
height: 175px;
padding: 1px;
}
.opt-icon{
position: absolute;
width: 25px;
height: 25px;
border-radius: 50%;
}
.del-icon{
background-color: red;
left: 0;
top: 0;
}
.edit-icon{
background-color: green;
right: 0;
bottom: 0;
}
.move-body{
margin: 12.5px;
width: 150px;
height: 150px;
border: 2px solid green;
}
</style>
先实现如下效果,图片可自由缩放旋转删除移动。通过两个点控制。
实现效果Demo如下:
这是Demo代码,可直接复制到项目中运行看效果。
支持小程序、APP。
一开始考虑使用的movable-area+movable-view。但因为需要先禁用移动,当点击的是右下角旋转按钮时才允许移动,所以需要在touchstart里面disabled改为false,然后再touchend中再次修改为禁止移动。
但movable-view不支持在touchstart中修改disabled立刻生效,只能在下次点击时生效,所以使用view+css实现。
<template>
<view class="page-body">
<view class="move-area">
<view v-if="!isHide" class="move-box" style="transform-origin:center;"
@touchmove="itemMove" @touchstart="itemtouch" @touchend="itemend"
:style="{transform: 'translate(' + itemX + 'px, ' + itemY +'px) rotate('+tmpRotate+'deg) scale('+tmpScale+')'}">
<view class="opt-icon del-icon" :style="{transform: 'scale(' + (1 / tmpScale) + ')'}" @touchstart="startMode(2)"></view>
<view class="opt-icon edit-icon" :style="{transform: 'scale(' + (1 / tmpScale) + ')'}" @touchstart="startMode(1)"></view>
<view class="move-body" @touchstart="startMode(0)">这是内容</view>
</view>
</view>
</view>
</template>
<script>
const nearDg = 7; // 用于控制当近似垂直或水平时,视为垂直或水平的容差。
let lastX = 0, lastY = 0;
export default {
data() {
return {
itemX: 0,
itemY: 0,
realCenterX: 0,
realCenterY: 0,
halfWidth: 75, // px
halfHeight: 75, // px
realWidth: 0,
realHeight: 0,
offset: 12.5, // px
touchMode: 0, // 0移动 1缩放旋转 2删除
startX: 0,
startY: 0,
startAngle: 0,
startDist: 0,
tmpRotate: 0,
initRotate: 0,
tmpScale: 1,
initScale: 1,
isHide: false,
}
},
onLoad:function(){
this.itemX = 70;
this.itemY = 100;
},
mounted:function(){
let view = uni.createSelectorQuery().in(this).select(".move-box");
view.boundingClientRect(data => {
this.realWidth = data.width;
this.realHeight = data.height;
this.realCenterX = data.right - data.width / 2;
this.realCenterY = data.bottom - data.height / 2;
}).exec();
},
methods: {
itemMove: function(e){
let curX = e.touches[0].clientX;
let curY = e.touches[0].clientY;
if(this.touchMode === 0){
this.itemX += curX - lastX;
this.itemY += curY - lastY;
this.realCenterX += curX - lastX;
this.realCenterY += curY - lastY;
lastX = curX;
lastY = curY;
}
if(this.touchMode !== 1)return;
let angle = this.angle(this.realCenterX, this.realCenterY, curX, curY);
if(this.startAngle === 0){
this.startX = curX;
this.startY = curY;
this.startAngle = angle;
this.startDist = Math.hypot(this.startX - this.realCenterX, this.startY - this.realCenterY);
}
else{
let rotate = this.initRotate + Math.round(angle - this.startAngle);
let leftDg = Math.abs(rotate % 90);
if(leftDg < nearDg || 90 - leftDg < nearDg){
rotate = Math.round(rotate / 90) * 90;
}
this.tmpRotate = rotate;
let curDist = Math.hypot(curX - this.realCenterX, curY - this.realCenterY);
this.tmpScale = this.initScale * curDist / this.startDist;
}
},
itemtouch: function(e){
lastX = e.touches[0].clientX;
lastY = e.touches[0].clientY;
},
itemend: function(e){
this.startAngle = 0;
this.initRotate = this.tmpRotate;
this.initScale = this.tmpScale;
},
startMode: function(mode){
this.touchMode = mode;
if(this.touchMode === 2){
this.isHide = true;
}
},
angle: function(cx, cy, ex, ey){
var dy = ey - cy;
var dx = ex - cx;
var theta = Math.atan2(dy, dx); // range (-PI, PI]
theta *= 180 / Math.PI; // rads to degs, range (-180, 180]
if (theta < 0) theta = 360 + theta; // range [0, 360)
return theta;
}
}
}
</script>
<style lang="scss">
.page-body{
padding: 5px;
}
.move-area{
width: 365px;
height: 500px;
}
.move-box{
position: relative;
width: 175px;
height: 175px;
padding: 1px;
}
.opt-icon{
position: absolute;
width: 25px;
height: 25px;
border-radius: 50%;
}
.del-icon{
background-color: red;
left: 0;
top: 0;
}
.edit-icon{
background-color: green;
right: 0;
bottom: 0;
}
.move-body{
margin: 12.5px;
width: 150px;
height: 150px;
border: 2px solid green;
}
</style>
收起阅读 »