1
0
mirror of https://github.com/ialley-workshop-open/uni-halo.git synced 2026-07-27 20:40:41 +08:00

v1.0.0-beta 源码正式开源

This commit is contained in:
小莫唐尼
2022-12-06 15:08:29 +08:00
commit 636ae7b169
461 changed files with 116817 additions and 0 deletions
+12
View File
@@ -0,0 +1,12 @@
full.scss
full.min.css
full.css
mian.css
mian.scss
scss/fonts
scss/colors.scss
scss/fuzhu.scss
scss/fuzhu-full.scss
scss/theme.scss
package.json
readme.md
+530
View File
@@ -0,0 +1,530 @@
## 2.0.02022-6-7
* tm-card修复点击操作按钮时返回的参数非index,而是项目标题。修复动态配置一些属性不生效的问题。
* tm-dropDownMenu 优化,当重置项时,默认将使用你初始的选中数据来进行填充,默认选中。
* tm-slidertm-sliders修复当默认初次使用值等于最大值时,功能失效。
* tm-album 添加了model模式,以便自定图片缩放模式
## 1.2.30 (2022-3-27)
* tm-images增加出错插槽error,修改出错时提示的样式。
* 修复tm-tree设置为单选模式后,第一级项目(非dir)单选失败。
* tm-search新增点击整个组件发出的click事件。
* tm-pickerView修复子级出现空数组时被计算为有效的三级。
* 修复tm-listitem自定背景色失败,优化tm-sheet自定背景的逻辑。我不赞成使用这bgColor两个属性。
* 修复tm-flotbutton在不同位置时,由于渲染的延迟,导致位置的闪现,影响美观。
* [新增]tm-uploadfile文件上传组件,用于任意文件的上传和预览,仅支持小程序和h5。
* tm-upload新增width属性,以便更好的控制组件宽度。
* tm-slider和tm-sliders新增了width宽度属性。
* 修复tm-monthCalendar在ios系统获取日期错误。
* 修改tm-card插槽数据返回方式为对象。
## 1.2.299 (2022-3-6)
* tm-setpper调整后删除最小宽度
* css库增加flex-[x]伸缩布局的盒子。
* tm-upload新增model,图片缩放模式。
* tm-images修复不支持临时文件路径的问题
* tm-dropDownMenu修复重置的情况下对:单选列表,多选列表,级联,日期选择等失效的bug.
* tm-swiper当有视频播放时,轮播将强制停止。等视频结束时如果启用了自动轮播,将继续轮播。
* tm-listitem调整了默认的样式属性。
* 修复了一些文档上的描述错误
* tm-calendar修复一个样式上的判断错误。
* tm-switch新增一些特性,允许修改组件的宽和高、以及关闭和开启的主题色。
* tm-button新增在微信端授权失败的显示的属性,userProfileError,具体见文档。
* 修复tm-pickersDateView未正常显示时间的中文后缀。
* 修复tm-calendarView在范围选择状态下的bug
* tm-icons图标修改了样式。
* tm-checkbox和tm-radio修改了样式。
* tm-countdown新增精确到毫秒
* demo示例大量更新,及大量组件样式的优化
* 主题库更新以支持上下渐变主题类,原有渐变如:bg-gradient-orange-accent,加了-b:bg-gradient-orange-accent-b即为上下渐变。
* 主题工具更新以支持生成上下渐变的主题类[主题工具](https://jx2d.cn/themetool/#/)
* 模板库正式上线[模板库](https://jx2d.cn/themetool/#/templete)
## 1.2.298 (2022-2-25)
* tm-grid调整了下划线的出现逻辑。
* tm-button完整的添加了原生的属性,之前版本有些属性被精简了
* tm-bottomnavigation 修复当使用自定16进制颜色背景时,底部安全距离的颜色未跟随tabar颜色。
* tm-tabs修复在超过一屏时,第一个元素在左侧时,点击第一个底部条会有不同程序的偏移
* tm-upload优化,当图片加载出错时,提示感叹号,以便感知当前加载状况。
* tm-tree树状组件,新增了一个单选模式,默认为多选详见文档。
* tm-album优化了组件在其它子组件内获取宽度失败的bug.
* tm-weekbar修复日期带10进制和不带10进制时,选中失败的问题。
* tm-steppe调整了步进器的禁用状态和宽高设置,以及一个兼容性的异步回调问题。
* tm-input调整优化了样式
* tm-radio和tm-checkbox调整优化了禁用下的样式状态
* tm-button调整优化了样式
* tm-groupButton调整了按钮组的样式
* tm-card优化了卡片组件在暗黑模式的表现样式。
* 其它更多组件的优化。
## 1.2.297 (2022-2-12)
* [新增]tm-card卡片组件。
* 修复tm-propressRound当模式为半圆进度条时减少到0(含)以下时变成了全圆
* tm-actionSheetMenu调整了默认圆角的样式,修复了某些问题。
* tm-flotbutton修复了上两个版本引出的定位问题(居中)
* tm-dialog优化了样式,默认不显示标题边线,间距调小,圆角默认改为8
* tm-keyborad优化了默认字号样式,加大了字号。
* 修复tm-bottomnavigation因测试red未删除,导致自定颜色主题被覆盖。
* tm-images增加加载错误时的提醒占位符
* 文档网站组件库进行了字母排序a-Z排序。
* tm-menubars修改标题为超出一行自动省略
* tm-stepper新增了相关属性功能:圆角分离按钮,小数位支持,整数支持,范围输入,长按增减,异步增减。
* tm-swiper新增标题显示,视频轮播显示,详细请阅读文档。
## 1.2.296 (2022-1-28)
* 优化tm-flowLayout瀑布流组件的宽高计算的准确度。避免偶尔的计算错误,导致左右列不致。
* tm-actionSheetMenu修复在弹出后如果在原生tabar模式下,h5中会被遮挡部分按钮的问题。
* tm-echarts图表,新增一个ref函数resize如果外围改变了父元素宽度后,可调用 此函数进行重新绘制图表。
* tm-upload新增:responseStu服务器响应数据的字段映射表,见文档说明
* tm-treeSelete修复了一个错误
* tm-dragGrid修复返回变化的列表数据,没有按照排序时的顺序返回。
* 修复文档说明中的问题
* css库增加一个flex-reverse,用于行内容的反转(对聊天对话作用较大)。
* tm-input因hbx更新导致默认校验函数异常。现已修复。
## 1.2.295 (2022-1-20)
* tm-stepper增加了change事件
* tm-signBoard修复在h5下面保存失败的问题。
* tm-grouplist修复在h5平台下,当列表设置为group模式时,点击展开列表内容的index有误。
* tm-dialog修复返回事件的拼写错误,老的不影响,错误的拼写也会正常工作。原concel改成了cancel。两个都会正常触发事件(兼容旧版本)
* tm-propressRound圆环进度,现在在appvue,h5,微信,支付宝下面是同层渲染,可以被其它元素覆盖。
* tm-calendar对属性变量以及范围属性变更为双向绑定,使用时记得添加.sync
* tm-propressRound 新增属性:semicircle是否显示半圆进度圈
## 1.2.294 (2022-1-14)
* tm-poup修复关闭时会触发两次change事件。
* 优化了部分组件暗黑模式下的配色表现。
* tm-album优化了在appvue和浏览器模拟下断行的问题。(真机无此问题)
* 优化tm-switch在appvue上的表现。
* 修复tm-message组件被注销后,定时器未被跟随注销。
* tm-calendar和tm-monthCalendar增加了inline属性(是否内联,默认true),方便独占一行或者内联表单布局展示日历。
* 修复tm-bottomnavigation,如果在同一页时,点击图标重复跳转,修复后,如果同一页面点击图标将不再跳转,也不触发事件。
* tm-groupcheckbox和tm-groupradio新增了customClass属性,方便定义子选择项的布局。
* tm-echarts版本为5.2.1,优化同层渲染显示,默认为2d渲染,现在如果在h5,weixin,alipay端下,默认是同层渲染,即渲染的图表不会跑到其它元素的上方。
* tm-calendar和tm-calendarView范围选择,确认触发的回调函数中,返回的参数,调整为数组即开始和结束的日期,不再返回所有日期。
* 优化,tm-table,tm-grid增加了maxGrid用于特殊列布局数量,比如一行5个,一行7个。maxGrid是指布局的最大列数默认是12列布局。想要一行5个可以设置为10
* 优化,tm-col增加了maxCol作用同上。
* tm-table,新增 fixedHeader,是否固定表头,默认不固定。注意能不开启尽量不开启,体验不是很好,但勉强可用,同时新增了:rowHeight单元格高,rowAlign单元格对齐,也可通过头统一设置,具体看文档。
* 修复tm-segTabs变动时触发两次change事件的问题。
* 优化tm-radio,tm-checkbox对齐表现。
* 修复tm-dialog在应用内浏览器中计算了顶部高度,导致上下不居中。
* tm-quickCity修复位置
* tm-flowLayout新增了一个clear,清空所有列表数据的方法。
* 单向tm-slider和双向tm-sliders滑块新增了start和end事件,即开始拖动和拖动时的触发事件。
* 优化tm-input校验函数。
* tm-bottomnavigation新增activeIndex属性,用于指定选择项,使用前必须设置autoSelected为false。如果切换页面时,将会被初始为0,请自行配置选项项。如果想夸页面自动选中项,请使用正常的tabar模式,而不是指定式。
## 1.2.293 (2021-12-31)
* 三个抽奖类游戏增加了disabledCenter属性,用于控制是否禁用点击图片中心能开始游戏的功能。
* tm-dragList重构了,现在拖动更为流畅顺滑,动画稍微加长,看起来更好。
* [新增]tm-dragGrid宫格拖动排序,为了大家方便,开发此组件,主要应用场景:主页功能模块编辑(类似ios桌面应用,支付宝首页模块拖动排序),新闻资讯分类(排序和编辑,类似头条)
* [优化]tm-button,为微信授权专门定制了更为简单的逻辑,具体请看文档,如何使用微信登录授权事宜。
* tm-choujiang修复圈数问题。现在新增了turns指定圈数。
* tm-switchList修复,在动态更新actions按钮数量时,导致位置计算错误。
* tm-badges新增了iconSize和iconWidth,只有为角标图标时才可以使用。
* tm-dropDownMenu优化,并且新增了两个可选组件,多选列表,下拉选择器的数据选择。新增了禁用时的颜色区分。
优化了渲染性能。当下拉条件超大量时,下面选项页面渲染过长(1秒)。因此新的组件采用了渐进式渲染。因此批量条件渲染也不在卡,而是秒开。
* tm-tabs增加了font-color
* tm-input新增,输入框和标题字号大小的控制允许输入内置的xxs,s,n或者任意数字。
* tm-menubars修复flat属性,忘记加''引号,导致扁平化样式失效。
* tm-avatarCrop新增quality和fileType方便压缩质量和导出图片格式。[修复]在appvue异常的情况
* tm-upload增加了一个清除所有文件的ref函数。
* tm-input新增了后缀图标和前缀图标颜色的主题色控制。
* 修复一css库的一些错误。
## 1.2.292 (2021-12-17)
* tm-listitem修改了判断url逻辑。
* tm-bottomnavigation添加了切换时的振动提醒(H5没有该效果)。
* tm-abs修复一个已知,当你手动去赋值切换tabs索引时,导致下划线未跟随。
* 修复tm-pagination未触发change事件,以及一个已知的问题。
* tm-verificationImg新增了一个禁用属性,用来禁用使用。
* tm-pullBottom增加了一个属性,用来控制不允许下拉,但允许上拉触底刷新数据。
* tm-calendar和tm-calendarView日历组件现已全部兼容了暗黑模式。
* tm-pickerDate和tm-pickerDateView进行了重构。
* tm-imags组件,由于测试代码未删除,导致上个版本,如果不设置宽度无法自动宽高。
* tm-switch更新了触发逻辑,代码控制不触发change事件,只有用户操作才会触发change
* tm-flotbutton主按钮(非子菜单)放开了原生的所有功能,具体参数,请对比tm-button
* render渲染引擎,增加了一个褐色Sepia图片风格滤镜。同时图片图形模块增加了一个load事件,只有图形name="image"时,图片加载成功触发该事件。
新增了HorizontalFlipVerticallyFlip水平,垂直翻转。HorizontalMirrorVerticallyMirror水平,垂直居中镜像对称。
* 修复主题库的样式在appvue真机下,不支持颜色值hex的16进制透明颜色值。现已修正为rgba模式,不使用hex模式。
## 1.2.291 (2021-12-13)
* **这是1.2.29的临时修订版本,上个版本测试不严谨,临时发个版本修正下**
* tm-switchList 调整了角标样式位置。
* tm-slider和tm-slider 修复信息在appvue下不居中的差异。
* 修正tm-pickers上个版本引出的不符合预期的动画效果
* 修复tm-stepper如果自己设定值时,无法赋值。
* tm-switchList的icon属性,现在支持自定图标,以及图片地址。
* tm-bottomnavigation修改了计算底部安全区域的值。
* tm-tree未过滤禁用项的选择。
* tm-upload,我变量写错了,导致1.2.29版本上传一直提示上传失败。
* tm-dialog层级过高。
* tm-poup重新编写了动画,过渡时间调整为240ms
## 1.2.29 (2021-12-13)
* **tm-render渲染引擎得到了重大更新,兼容微信小程序,性能翻倍。可用于生产啦。**
* tm-pickers,tm-tm-mapSelectedPoint,tm-poup,tm-slider,tm-sliders等相关组件,优化和修复了一些错误
* tm-dialog增加了最大高度的限制。同时新增了一个disabled属性,以控制是否允许点击按钮关闭窗体。true表示点击窗体任意位置也无法关闭窗体,只能通过v-model来控制
* tm-alerts修改了文档错误。同时新增了轮播的间隙。
* 文档错误的描述修复。
* 修复tm-tree在上个版本新加的允许子级中出现父级功能时,引出的bug。
* tm-timeline添加list监听,修改数据后动态更改。
* tm-maskFlow新增了blur是否显示模糊背景。
* tm-menu添加了点击任意位置关闭菜单的功能。
* tm-helpTips添加了点击任意位置关闭弹出层的功能。
* tm-tabs的model新增了一个可选项:fill的模式。显示背景主题样式。
* tm-dropDownMenu下拉选项新增了下拉,列表式菜单单选模式,具体见演示demo。同时美化了标题项图标。新增了一个shadow投影属性,你现在可以自己控制投影了。
* tm-timeline修复节点标题颜色,不会跟随自定主题更改,而是被默认主题覆盖的问题。
* tm-flop使用了新的库和效果。详见文档和示例。
* tm-avatarCrop优化了细节,兼容pc端
* 优化了tm-badges显示效果。
* tm-bottomnavigation数据格式dot中新增了color属性,以便更改角标的背景主题色,不提供使用自身的icon-color主题。
* tm-upload新增了一个code属性,用于标识服务上传返回数据中是否成功的代码。默认为0表示成功 。同时修复了一个判断上传成功与否的逻辑。
* [themetool主题生成工具](https://jx2d.cn/themetool/)更新了一个已知问题,当生成的主题使用背景作为文字颜色时,无法显示文字色。
theme->下的所有主题包已修正了此问题。主样式库也修正了此问题。
* tm-button修复一个已知的宽度计算的问题,因之前的版本怕宽度为rpx时,没有注意到,后期改的时候又rpx一次,相当于宽度被减半了。(只有使用width属性时出现问题)
* tm-pagination分页组件优化了性能,重新编写了计算页码函数,并且设置了最小可见页数为5。
* tm-menubars修正了一个已知的透明度问题。
* tm-listitem列表新增了下right-icon-size属性,可以修改右图标大小。
## 1.2.28 (2021-12-01)
* tm-more新增showMask属性,用于显示内容遮罩,默认不显示。
* tm-translate优化了动画组件。
* tm-tree修复了一个bug,当第一级为子级为子级时,无法选中。另外应网友建议。现在第一级允许出现子级字段。只要为空就可正常作为子级而不会被识别为父级。
* 优化了个别组件在安卓平台下的表现性能,建议大家及时更新新的版本
* tm-bottomnavigation底部导航工具现在角标可以添加图标角标,具体见文档示例。
## 1.2.27 (2021-11-30)
* [新增]tm-rendercavans渲染引擎,目前是测试版,应用场景:海报自绘,图表自绘,界面自绘,游戏动画等场景。
* tm-tabs添加了model="none",如果为none边框和下划线的样式将被去除不显示。同时新增了:active-border-color现在可以单独设置指示的颜色,渐变等,默认为空,如果不填写则默认使用主题color的主题色。更新了动画速率
* 优化tm-icons,现在开始你也不用图标定义前缀,变得更为简单,只需要设置图标字体名称和图标名称前缀相同即可,
自定义扩展图标,详情见自定图标.新增了几个品牌支付类图标,和品牌图标。同时新加了两套字体以便兼容安卓手机的支付宝显示不了图标的问题。
* tm-dialog增加属性bottom-border,是否显示标题底部边线,默认显示。
* tm-countdown更新了文档,之前文档漏写了插槽数据。
* tm-album相册集新增了自定义高度的属性
* tm-pickers和tm-pickersView增加了一个事件@change,当前选项在滚动时,会触发滚动选中的索引数组(非真正意义上的选中,只代表滚动时的选中项)
* tm-password新增了round,width,height,bg-color,shadow属性,以控制项目输入框的宽和高以及圆角的等相关属性。现在可以设置时尚的圆角输入框啦~
* tm-segTabs新增gutter属性,允许你设置四边的间隙大小。默认4,新增了个性化示例,以便大家知道它的设计亮点。重新优化了设计样式,紧贴未来设计趋势
* 优化了tm-sheet和tm-fullView初次加载时的宽度设置问题。
* tm-poup修复圆角对居中不起作用的bug,优化了tm-poup的动画效果。
* 修复tm-tree,子级被手动选中时父级未被选中。新增了半选中状态。
* 调整了tm-search清除图标和后缀图片同时显示时的位置,清除图标在前面。其它图标靠后。
* tm-steps完善了相关文档说明,新增了节点间连线的类型属性(可选css边线的标准类型),以及节点间连线内的中间图标属性,填写显示,不填写不显示。连线的厚度,也可以调整啦
* [新增]tm-avatarCrop图像裁剪,主要场景有:头像、文章图片,产品图片,海报图片尺寸等的调整和裁剪功能。
* tm-alerts文档描述错误。
* tm-row新增了一个click事件。tm-col不再阻止冒泡事件。
* 调整了tm-input前缀图片和清除图标与输入框的间距对齐的问题。(关于点击输入框时弹起键盘,紧接着点下一个禁用的输入框来选择下拉时,键盘不收起,这时uniapp的bug。我是无法修复。但使用了一个巧妙的方法解决,那就是在输入框禁用时,在覆盖一个view来失去焦点,以此来关闭键盘。)
* 修复tm-dropDownMenu因为拼错,导致样式错误
* tm-table表头新增了排序功能(仅对该列为数字时有升降,如果为字母或者其它字符则为反转和正常两种排序),现在表格内容自动断行。详见文档。
* 新加了font awmeson图标库在本库下,具体请查阅[拓展图标库的使用方法](https://jx2d.cn/guid/css/%E6%8B%93%E5%B1%95%E5%9B%BE%E6%A0%87.html)
* 修缮了官网文档中的一些组件参数错误的描述。其它组件的完善和优化。
## 1.2.26 (2021-11-18)
* tm-swiper修复修改list时没有变化,新增加了3d卡片动画效果。
* 优化tm-switchList滑动单元格
* [新增]tm-avatarGroup头像组件。偏移可自行设置。内部只能放置tm-avatar头像组件。
* tm-switch新增loadding,是否加载中的属性。
* 修复tm-input在文本域时下方的输入数字遮挡文本的情况
* 优化了tm-dialog对话框的错误和禁止穿透时提示动效反馈,让用户理解当前的操作。
* tm-upload新增了maxsize属性,以控制上传的图片大小限制。
* tm-images新增了待加载的动效效果,以便让用户知道图片的加载情况
* tm-tabs优化了其性能,更为流畅。
* tm-tags新增了抖动动画ani-on,默认false,同时新添加了关闭动画
* tm-quickCity优化了细节,新增了热门城市可供选择。
* tm-menu添加了显示动画。
* tm-password添加了当前输入位置的呼吸效果,以便让用户知道当前输入位置。
* tm-keyborad添加了按钮点按振动反馈效果(app和小程序有效)。优化底层可穿透,就是可以连续点击输入框输入内容,而不必来回切换关闭和弹出。
* tm-button自定义类属性拼写错误为itemeClass。正确的是:itemClass,
* tm-radio和tm-checkbox单选和复选增加了选中动效,同时调整了默认大小比之前更大之前为32.现在为38.
* tm-sliders和tm-slider单/双滑块新增了点击按钮时触发显示信息的动画,同时提高了其高度,方便手机用户。
* [新增]tm-groupButton按钮组,主要应用于app应用的按钮组合快捷操作使用,点击选中的效果。
* [新增]tm-segTabs分段器选项卡
* [新增]tm-album相册集,以便展示图片列表,随意控制间隙,列数。
* [新增]tm-gap间隙槽,横竖向,方便元素间加间距,特定场景布局时,不方便使用类ma,pa这样的类的一个补充。
* tm-input,新增了设置标题字号的属性:title-font-size,同时新增了聚焦状态属性,默认禁用,打开后,可以让用户更加直观知道当前的操作状态。优化了暗模式下的边线颜色搭配,使其更美观。
* [新增]tm-message提示框,主要用于操作反馈,含有丰富的动效反馈(亮点),以便让用户察觉所操作的含义。
* 上个版本,在尝试兼容nvue时,button和icon测试代码没有删除,导致不能适应暗黑和主题。
* nvue因为样式与标准的css3样式改动较大,所以本版暂时未发布nvue版本,请期待后续的兼容。
## 1.2.25 (2021-11-10)
* 从1.2.25开始已全面兼容app端(nvue计划在1.2.26中兼容)
* 优化了tm-choujiangGrid九宫格抽奖游戏。
* 修复tm-bottomnavigation历史遗留问题,导致角标不显示的问题,感谢网友的指出。
* tm-calendar现在允许动态更新范围选中日期。
* tm-choujiangGrid九宫格抽奖,新增转圈数和指定中奖位置。
* 修复tm-checkbox和tm-radio组件在使用超长文本时造成的挤压。
* tm-sticky现在自动吸顶支持小程序和appvue因为是使用onpagescroll模拟(非h5端)所以尽量内容不要过于复杂。
* [新增]tm-switchList滑动单元格,常用于,聊天列表,通讯录列表的左滑动操作等场景。
* [新增]tm-dragList拖放排序列表,常用于,支付顺序,工具选择等排序场景。
* [新增]tm-tree树状结构组件,用于:组织架构,目录,文件夹,筛选等类目较多的场景。
* [配套的主题生成工具已更新,兼容appvue主题](https://jx2d.cn/themetool/)
## 1.2.24 (2021-11-5)
* 本次更新重大:重新调整了主题库。现在所有主题(含渐变)都已经能够适应所有组件的
暗黑,text,outlined模式,得于此次重大的调整。现在样式主题库不再集成数十个主题,而是分别提供,单独按需引入,你需要
那个主题,就引用哪个主题。细节方面比之前更完整(不用担心大小问题)。现在基本样式库为152kb(原有387kb),拆分后每个主题css
大小约为20kb。并且我已经开发了主题生成工具(自动生成对应的渐变主题bg-gra开头-主题名)。
[配套的主题生成工具,已为你准备好](https://jx2d.cn/themetool/)
* tm-propress进度条优化,现在宽高可以随意输入数字或者带单位的数字比如5px,5rpx,10vw,50%
* tm-propressRound环形进度条优化在小程序上的显示,避免锯齿。
* 优化兼容tm-grouplist。
* 关闭了导入用户vuex目录,不存在时的错误提示(并不影响使用,只做为调试,给用户造成了困扰)
* tm-timeline修复了变换数据时,未响应。优化了细节
* tm-input增加了,点按清除图标的事件。
* tm-search增加了,点按清除图标的事件。
* tm-stepper新增,最大,最小值,宽度,高度的设置,同时长按增加或者减少按钮会持续+或者-
* tm-flowLayout优化了瀑布流组件【说明下,本站很多模拟数据,包括图标,图片使用的是外网的,导致加载过慢,看起来像是左边先出来或者右边,其实内部是已经排好了,之所以,有的占了坑却没显示,是卡。才造成你们的错觉。】
* 细调了主题库参数比例,优化了暗黑模式下的配色。现在的所有组件都支持暗黑模式
* 调整了tm-lottie动画url可以是地址,也可以是json对象数据。
* tm-menubars导航工具栏进行了优化。防止事件穿透,现在更加的与主题整合在一起。删除了iconColor和fontColor两个属性。
* tm-images现在支持base64图片。
* tm-divider修复了一个历史遗留问题。
* 优化了tm-menubars菜单工具栏,修复了右插槽多打了个空格的问题。
* [新增]tm-scroll横向滚动,主要应用横向滚动场景。比如横向产品展示,横向图标展示等。
* [新增]tm-coupon优惠券
* [新增]tm-skeleton骨架加载器
* css库新增内联对齐类:vertical-align-[top/center/bottom]方便内联上中下对齐。
* css库,外边距新增负数表达方式原有mt-10,写成负数方式为:mt--10。
* css库,新增位置控制t,l,r,b四个方向t-10表示top:10rpx。t--10表示top:-10rpx负数
* css库,新增wrap断行,nowrap不断行
* tm-cartCellListFood和tm-cartBarFood以及demo示例更改,之前在ios下有时数据触发购物车数据不同步的情况(当在加载新数据时,新数据与购物车数据不匹配和已有匹配数据)的同步和计算的问题。
这个列表数据,是可以异步加载,不需要美团或者饿了么一次把产品加载完成。购物车放到任意一页面都会同步数据。(适合在新的页面查看,或者搜索产品同步列表购买数据与购物车的打通)
* 其它更多组件的细微调节不一一写出。
## 1.2.23 (2021-10-28)
* tm-pickersDate日期下拉组件,已经更新了计算方式。比之前更流畅。
* 修复tm-bottomnavigation在真机上,未能隐藏原生底部bar,安全距离各个平台的差异。
* 修改了tm-flotbutton位置的计算方式。
* tm-signBoard签名版,现在已支持电脑版。
* tm-alerts,更新了新的功能,当提供数组为消息轮动时,提供了vertical属性,纵向还是横向的属性。
* 文档示例已经更新,文档库增加了组件搜索功能。[访问](https://jx2d.cn/)
## 1.2.22 (2021-10-26)
* 修复tm-alerts组件页面渲染中多写了个变量,已删除。
* tm-grid修改了默认样式。
* tm-calendarView和tm-calendar两个日历组件新增了formart分割数组,方便显示不同的分割符号,比如把年改成'-'或者'/',同时修复设置title时,顶部标题没有变化(忘记变量写上去了。)
* tm-pagination美化了样式
* tm-icons优化了关闭按钮的样式
* tm-timeline优化了图标样式
* tm-flotbutton增加了一个属性。safe:是否开启底部安全区域,当设置为bottom时(fixed模式下生效)会自动加上安全区域的高度
* 修复tm-bottomnavigation在与原生tabar共用时,出现计算当前页面的错误,概率性无法隐藏原生tabar,修改了底部安全区域的计算方法,现在兼容:应用内浏览器(比如微信,qq内的浏览器),小程序,app等。
* 修复tm-button,当配置url并未跳转(因为1.0.0版本遗留的问题,此功能后面给忘记加上去了。),同时修改了样式美观度,当模式在titl和text模式下,自动隐藏投影属性。
* 修复tm-search在H5端,运行在ios下,输入框中会默认显示ios特有的搜索图标,现已清除。不再双重图标。
* tm-helpTips修改了默认布局,关闭图标和文字形成水平左右布局。不再上下布局。
* 修复tm-actionSheetMenu,因主题切换引申出来的主题样式错乱。
* 修复tabs在重新赋值列表数据时,样式未触发重新计算。tabs首次加载,将不再执行焦点动画,只有操作后才会执行动画。
* [所有端]修复tm-pickers因切换到原生组件渲染时引出的bug,在改变列表长度时不会更新底层有index导致界面滚动不了。
* 修复tm-pickersDate在限制日期范围选择时,因我类型判断错误,1月份索引是0,属正常,但我判断成了假,导致在1月份时,天数被限制,显示不全。
现在解决方案是注销(v-if,数据多时,每次打卡有200ms左右延迟)来控制,而不是之前的v-show(可以减少初始化的次数,数据多时优势明显。打开不卡)
* [appvue]修复tm-echarts在appvue在安卓下报error.现已修复,同时新增了error事件,当渲染出错时可以捕获。
* [appvue]修复tm-quickIndex和tm-quickCity在appvue,无法显示的问题。
* [appvue]修复tm-pickers在appvue下报警告的提醒。
* [appvue]修复tm-input和tm-form在appvue下执行校验失败和获取数据失败的问题。
* [appvue]修复tm-grouplist在appvue下不起作用的bug.
* [appvue]修复tm-slider和tm-sliders在模拟器和真机上不可用的问题,在app上本组件需要你设定样式宽度或者它父组件定个宽度。
## 1.2.21 (2021-10-22)
* 修复tm-menubars在多个级联器下的布局概率下出现错位现象。
* 美化了tm-calendar日历选中和范围选择下的样式。
* 修改了说明文档和示例更新。
* 修复样式库round-bt的描述错误,正确的写法应该为round-bl(底左圆角而非bt),相应的文档也更新。
同时修复了,主题库中对色值明度提高了边界范围,使得稍微明亮主题下文本颜色仍然为白色,而非自身颜色暗黑的比例(影响中性颜色范围扩大)。
减淡了text模式下的颜色背景比之前少了1%。
* 为了让使用暗黑模式,tm-fullView组件修改了结构,不再使用内部的滚动(影响性能),而是超出不隐藏,默认为最小高度为屏高。同时增加了相关属性。
* tm-bottomnavigation现在修改为根据页面路径自动选中底部按钮。参数路径前面一定要加:/ ,比如:/pages/index/index(你可以使用系统的tabar以达到原生性能,当此组件在给定的tabar页面时,会自动隐藏原生的tabar)
* tm-pickers系列组件(共6个)进行了全部的重构,以支持电脑端(以及未来的nvue)。(原tm-pickers为自渲染,比原生的选中更流畅(无延迟),但不支持电脑端以及nvue,无奈放弃)。
* tm-slider单滑块和tm-sliders双滑块现开始支持电脑端(以及未来的nvue)(刻度尺模式不支持电脑端)
* tm-monthCalendar月份日历更改了默认样式。
## 1.2.0 (2021-10-20)
* 全新主题动态切换,一秒换新app主题(预览图在下方)
* 全新暗黑模式(预览图在下方)
* 新增[tm-lottie组件](https://jx2d.cn/guid/components/lottie动画.md),(预览图在下方)可执行动画的播放,暂停,停止等常用功能动画,具体见文档。
* 修复tm-pickersDate,单独设置年,和月,日选择时,不显示列表的错误。
* tm-grid数据结构新增了右角标可显示图标,而不局限红点,具体见文档示例。
* tm-tags修改了关闭图标样式对齐问题(小程序端),tm-tags新增了dense属性,是否去除边距间隙
* 修复tm-bottomnavigation点击事件返回参数错误,现已修正,并且修复了提供path未打开链接的bug,同时数据格式中,要求提供openType打开方式,同正常路由方式,完善了相关操作方法,自动隐藏系统tab
* 新增[瀑布流组件tm-flowLayout](https://jx2d.cn/guid/components/瀑布流.md)。
* 修改了签名板js库(和官方组件冲突的命名)和示例
* 头像组件tm-avatar文档,更新了一个示例,利用内置的js函数库快速的上传更改头像(只需要一个函数非常方便)
* tm-dropDownMenu组件的样式得到了较大更新,样式更为美观。
* tm-sliderNav和tm-treeSelect得到了样式上的较大更新更为美观,同时选中模式也更改为了点击整个选择。
* tm-input增加了padding属性,用于改变,左右上下的间距。
* tm-upload修复了一个错误,header没有传导进内部,导致上传时,无法传递header参数到服务器,同时新增了对返回参数的code校验,如果code!=0也会发出上传错误。新增了,项目圆角属性 round,默认3,方便修改圆角。
* tm-form修复了一个兼容性的问题,不知道什么原因,升级后的版本,对于取子childer发生了改变,现在只能更新修复了。修复在小程序下取不到表单数据的问题。
* tm-button新增了一个dense属性,为真是去除外边距。
* tm-images组件现已修复了,大小自动设置时的计算错误。
* tm-badges修复样式计算错误,由之前样式在1.0.0版本中遗留的bug。
* tm-cartBarFood组件,新增了一个ref函数getData,用于获取当前购物车的统计,应用场景为跨页面的数据同步,需要用到。
* tm-pickersDate日期选择组件修复了一个时间范围的bug问题。
* 新增了tm-echarts图表组件,版本号为5.2.1同百度官方版本,使用方法见文档,如果想要了解图表样式,请查看百度官方文档。
* 修复了日历,默认值空时,延时赋值,会被组件内部默认选中的数据覆盖的问题。
* css样式库,新增了渐变类的边线等等主题类的整体样式,因此增加了100kb大小。目前整个样式库为386kb。有些预设样式不得不增加大小。
* tm-tabs组件,修复如果把选中值设置为不存在(-1或者大于当前选项长度)的索引值,会出现警告。不再警告报错。
* tm-dialog增加了over-close属性,指示是否点击遮罩关闭窗体。
* 修改了tm-pagination分页组件的,分页策略,并修正了一些问题。
* 修改了其它组件一些样式及bug,就不一一列出了。
## 1.1.92021-09-28
* (紧急)关于HBuilder X编译器的BUG对我的组件影响如下:
3.2.9.20210927,编译后,使用所有插槽的组件,开发中热更新,经常丢失样式,严重影响开发;但发布后却不受影响。(影响热更新的开发)
3.2.9.202109273.2.3.20210825 版本,更新后编译后在(h5端)插槽中(tounch开头事件)失效,但click等事件不受影响(影响我的签名板组件示例demo)。
3.2.3.20210825编译版本,编译后插槽中的如果使用页面的变量,导致编译bug一堆,且必失败。(小程序和h5都有,影响我所有插槽组件)
3.2.2.20210818 (截止2021-9-28,是我认可且推荐的编译版本)
3.2.2.20210818之前的版本,插槽中的变量和事件直接变为子组件的变量和事件,导致所有插槽失败(所有端,影响所有我插槽组件)
* [tm-tabs选项卡切换](https://jx2d.cn/guid/components/%E9%80%89%E9%A1%B9%E5%8D%A1%E5%88%87%E6%8D%A2.html) bg-color='white'时导致背景丢失(此bug来自暗黑模式导致。),同时修复,在使用双向绑定激活项时,如果通过js改变激活项时,下面的指示条位置未改变。
* 修复多[级联选择器](https://jx2d.cn/guid/components/%E6%99%AE%E9%80%9A%E7%BA%A7%E8%81%94%E6%8B%89%E9%80%89%E6%8B%A9%E5%99%A8.html),在默认选中级级时,出现了位置不正确的问题。
现在可以通过id进行赋值,城市选择器同样适用,id(城市编码),名称,索引三种方式赋值。
* [tm-calendar日历组件](https://jx2d.cn/guid/components/%E6%97%A5%E5%8E%86.html),现在新增了选择开始和结束时触发相应的事件,同时,开始和结束下标文本会进行提示:开始和结束
* [tm-search搜索框](https://jx2d.cn/guid/components/%E6%90%9C%E7%B4%A2%E6%A1%86.html)组件新增了输入框后缀图标插槽suffixIcon,以便自定义个性化输入框。
* [tm-loadding组件](https://jx2d.cn/guid/components/%E5%8A%A0%E8%BD%BD%E7%8A%B6%E6%80%81.html)现在可以自定义文本,图标,主题色了。
* 新增[tm-weekbar时间周组件](https://jx2d.cn/uniapp/#/pages/comtwo/weekbar),主要应于场景有:根据时间快捷栏,快速选择时间来刷新数据,打卡,签到等应用场景。
* 新增[tm-verificationImg图片安全校验](https://jx2d.cn/uniapp/#/pages/comtwo/verificationImg),主要应于场景,用于多防刷流量的验证,或者多次密码错误,多次发送验证码时的一种安全验证。
* [tm-quickIndex索引组件](https://jx2d.cn/guid/components/%E5%BF%AB%E9%80%9F%E7%B4%A2%E5%BC%95.html),迎来了重大更新。优化了使用性能,现在可以快速的滑动快捷栏来选择组,同时提供了滑动时提示当前所在的组位置。
并且,新增了一个插槽cell,通过cell插槽,你现在可以完全定义每一项的内容,并且自己个性化布局内容。默认数据list结构现在开放了图标、头像展示在列表中。
同时也进行了美化快捷栏。具体请前往文档查看更新细节,手机可预览。
* [tm-input输入框](https://jx2d.cn/uniapp/#/pages/com/input)新增了输入框内前缀文字属性prefixp-text。
* 新增[tm-quickCity城区索引](https://jx2d.cn/uniapp/#/pages/comtwo/quickCity)选择组件。
* tm-tags新增事件click,点击组件时触发。
* css库细化了暗黑模式的颜色搭配,现在暗黑模式更为一体。
* tm-upload新增了一个ref函数pushFile,添加已上传图片至列表。新增了一个事件@def,当删除上传文件时触发。
* tm-stepper组件最小值时,将会出现禁用减少按钮的状态。
## 1.1.82021-09-17
* tm-images修复在引用静态路径时,报告路径错误。现在引用前已经设置为根路径。
因为在引用图片路径时,直接引用就可以比,如想引用static/a.jpg。那么在【任何页面,不分层级】里面只要src="static/a.jpg"即可,不需要再去./或者../../之类。
同时修复了单位问题,现在统一为rpx单位。
* tm-input新增了输入框内suffixIcon后缀图标字段属性,以便实现更多的功能样式。和右图标相似,但不同的是suffixIcon是在输入框内部。同时把height字段也应用到了输入框,而不是只应用于常规输入框
每个人设计的输入框可能高度不一致,因此放开了高度选项。(升级后不影响现有项目,已做兼容),文档中少写了一个属性:placeholder-class点位符自定义类,这个属性一直有,文档中漏掉了。
* tm-avatar新增了border属性字段,用于开启边框,控制头像边框大小。默认为0,颜色为字段color的颜色。
* tm-grouplist新增fontSize标题大小的设置,标题大小,注意是css库中的xxs,xs,s,n,g,lg,xl
* 优化了tm-row和tm-grouplist组件在appvue和小程序端的显示
#### 使用的tm-images的宽度会影响现有宽和高。因为这个组件之前的宽和高我忘记把rpx转换成px,所以之前的宽度如果是100实际是100px,升级后会变为50px(750的比例)。
#### 因此如果你使用了大量的tm-images请不要升级吧。如果只是少量,麻烦大家更正下,非常抱歉。!!!
* 很多组件,样式细节被我调整了(对现有项目不影响)。
## 1.1.72021-09-16
* 调整当tm-input组件当input-type=number,或者digit将会根据类型格式对应的整数和小数点数据,而不是原生的字符串格式。因1.1.6更新时,不小心删除了sync绑定方法,导致value.sync不双向绑定
v-model方式不受影响。调整了tm-input样式,右边如果超出了已定宽度断行,现在不允许断行,保证不受挤压。
* 调整了tm-dialog的高度和宽度(设为响应式宽度了,不再固定宽度),调整了对话框文档说明,呃,我写错了文档
* 修复tm-upload,当不提供url上传地址或者url上传报404或者301等不为200时,可能造成无限自动重试上传(设置了自动上传时)的bug,现已修复,建议及时更新版本。
* tm-pickersCity组件,default-value现已支持双向绑定请使用:default-value.sync,同时调整宽度为自动100%,之前是内联
* tm-pickers组件,default-value现已支持双向绑定请使用:default-value.sync,同时调整宽度为自动100%,之前是内联。
修复在滚动时,如果行数没有变化,也会触发下级数据更新。现在同位置滚动时将不再触发,级联变动数据。微调了样式。
* 调整了tm-stepper属性,新增了fontColor中间数字文字颜色主题,兼容之前版本。并修复当暗黑模式时,文字颜色还是为黑色,未变白色。
修复tm-stepper步进器,内部属性类型,标识错误的问题
* 修复了css库中,当border-b-1类库暗黑模式下与其它组件颜色不一致的问题。
* 新增[表单组件](https://jx2d.cn/uniapp/#/pages/comtwo/form),使用方法和注意事项请详见文档。内部会对Input组件的必填项进行校验。
以下是关于表单对相关组件的更新:
tm-upload:新增了prop属性name,用于提交表单时的标识,返回上传成功后的服务器数据结构
同时这些组件都相应增加了name字段:tm-input,tm-groupradio,tm-groupcheckbox,tm-upload,tm-rate,tm-slider,tm-steppertm-switch,表单类主要是这8个组件。
上述8个组件放置在表单组件内部时,提交触发事件会收集所有的数据形成对象结构体并返回。详情上方表单组件文档。
* [tm-table表格](https://jx2d.cn/guid/components/%E8%A1%A8%E6%A0%BC.html)组件新增了cell插槽,以便方便自定义单元格的内容。
* [tm-swiper轮播组件](https://jx2d.cn/guid/components/%E8%BD%AE%E6%92%AD.html)dot-model新增了一个round可选模式的样式,请前往文档查看。修改了原prop中的round属性,原为boolean,现改为Number
属性,如果要开启圆角,需要设置round="数字"即可圆角。同时新增了maring属性,即左右间距。默认0,设定数字后,两边会有间距。单位rpx
* [tm-table表格组件](https://jx2d.cn/guid/components/%E8%A1%A8%E6%A0%BC.html)新增了一个插槽cell,通过插槽你现在可以随意更改任何一个单元格的内容,比如添加按钮等元素。
* [tm-empty组件](https://jx2d.cn/guid/components/%E6%95%B0%E6%8D%AE%E7%A9%BA.html),新增prop属性icon,现在你可以自定义图片,可以是图标名称或者图片地址。
* 修复[tm-listItem组件](https://jx2d.cn//guid/components/%E5%88%97%E8%A1%A8.html),单独作为拓展(手风琴)列表模式时,点击展开内容时报报错警告(不影响使用)。现在不再出现警告及错误提示。
## 1.1.62021-09-13
* [tm-dialog对话框组件](https://jx2d.cn/guid/components/%E5%AF%B9%E8%AF%9D%E6%A1%86.html)的弹层动画在所有端去除了。经过大量测试uniapp对动画的支持在appvue和小程序中运行流畅度始终无法让人忍受。在H5端
非常的流畅。但其它端仅:left,right,bottom,top等动画流畅,对scaleopacity,这种大小渐隐效果的动画支持的太差了。
* [css库](https://jx2d.cn/guid/css/%E7%B1%BB%E5%BA%93%E7%9A%84%E7%A7%8D%E7%B1%BB.html)增加:flex-top-start,flex-top-center,flex-top-end,flex-wrap,flex-shrink,fulled-height类,具体请查阅文档。
* [tm-tabs选项卡切换](https://jx2d.cn/guid/components/%E9%80%89%E9%A1%B9%E5%8D%A1%E5%88%87%E6%8D%A2.html),本次更新,新增了两个prop属性,默认字号和激活后的字号大小设定。两个默认28upx
* 调整了[tm-sliderNav侧边导航](https://jx2d.cn/guid/components/%E5%88%86%E7%B1%BB%E9%80%89%E6%8B%A9.html)的样式。
* tm-tags组件,属性文档写错,尺寸最小到xs而文档中写成了xxs,导致使用错误。调整了[tags标签](https://jx2d.cn/guid/components/%E6%A0%87%E7%AD%BE.html)样式和间距。
* 修复了[tm-input组件](https://jx2d.cn/guid/components/%E8%BE%93%E5%85%A5%E6%A1%86.html)在appvue和小程序端因为默认为空时,显示clear图标造成和检验逻辑错误。
input-type属性增加了:textarea可选值,现在可以通过它展现文本域多选输入了,并且有字符限制提示,只需要通过maxlength来控制,右下角会显示当前字符数和限制数,并调整了样式。调整了文档示例。
* 修改和完善了[css库类清单](https://jx2d.cn/guid/css/%E7%B1%BB%E5%BA%93%E7%9A%84%E7%A7%8D%E7%B1%BB.html)文档,添加了渐变文字颜色的文档说明。
* 修改了文档,安装中的步骤方式。修改了文档:暗黑模式的使用方法:[暗黑模式](https://jx2d.cn/guid/special/%E6%9A%97%E9%BB%91%E6%A8%A1%E5%BC%8F.html)
* 微调了[tm-upload组件](https://jx2d.cn/guid/components/%E4%B8%8A%E4%BC%A0%E5%9B%BE%E7%89%87%E7%BB%84%E4%BB%B6.html)的样式计算精度。
* 调整了[tm-sliderNav组件](https://jx2d.cn/guid/components/%E4%BE%A7%E8%BE%B9%E5%AF%BC%E8%88%AA.html)数据为响应式,方便绑定数据后,更改数组时需要手动同步,现在不需要手动同步了。新增了fontSize字号大小的配置。
* 修改了[tm-listItem组件](https://jx2d.cn//guid/components/%E5%88%97%E8%A1%A8.html)左边内容的宽度为默认的100%(不会影响现有项目,建议插槽时套个view组方便控制内容的宽)。
* 新增[搜索框组件](https://jx2d.cn/guid/components/%E6%90%9C%E7%B4%A2%E6%A1%86.html)
* 微调了九宫格的dot默认位置布局.
* 修复了[展开更多](http://localhost:8081/guid/components/%E5%B1%95%E5%BC%80%E6%9B%B4%E5%A4%9A.html)在微信小程序上嵌套动画组件时,不显示操作按钮.
* 改善了tm-poup样式,现在可以居中弹层了.
* 修复tm-swiper组件在嵌套组件时,计算宽度错误。
## 1.1.52021-09-06
* 紧急修复因暗黑模式导致的个别组件:按钮,下拉选项受到了影响,样式有点问题,现已修复,请尽快更新。
* 重构了button,调整了css样式库
## 1.1.42021-09-06
* #### 兼容了前面的版本。现在基本所有组件都具备了color(注:按钮为theme)和black的属性,可全局切换暗黑
* 现在你可以在任意页面或者组件中,切换为 暗黑 模式。使用方法:this.$store.commit('setTmVuetifyBlack', !this.tmVuetify.black),如果想要某个组件不受全局
的暗模式影响,只要 :black="false或者true"即可不受全局的暗黑模式影响。
* js中通过this.tmVuetify.blackvue中通过:tmVuetify.black,来获取当前的全局模式:暗黑还是亮色状态。
* 主题切换是通过Vuex方式,因此如果你的项目已经存在了store怎么办?需要通过以下方法改变下:
* 第一种:请进入tool>store>tm-vuetify.js中把它的内容复制出来与你的store合并即可。并且进入:tm-vuetify>index.js中第91行删除或者注释即可
* 第二种:你可以把你的store和我的store合并,就不用作任何操作了,并且会在每个页面自动注入你的store函数,可以任意页面使用全局变量和函数啦。
* 个人建议第二种方法。如果你的项目庞大,请阅读:[官方Vuex介绍](https://uniapp.dcloud.io/vue-vuex),以及Vue的官方介绍[Vuex](https://vuex.vuejs.org/zh/)
* input组件,因为需要兼容一键切换暗黑模式,去掉了默认输入背景灰色,现在默认无背景了。(我觉得影响不大,你可以自己设定背景色)
* 调整了[input组件](https://jx2d.cn/guid/components/%E8%BE%93%E5%85%A5%E6%A1%86.html)的示例。
* 77+组件已经完成了暗黑模式的自动切换。
* 调整了下拉选项的样式,现在下拉的选项为悬浮。不再是静态撑开样式了。
* 细调了[步进器](https://jx2d.cn/guid/components/%E6%AD%A5%E8%BF%9B%E5%99%A8.html)的样式和暗黑模式颜色的搭配,现在更为美观了。把步进器修改为内联模式,而不是独占一行的块状。同时禁止为负,
现在只能输入正整数,其它字符都会转换为0。
* 调整了[对话框](https://jx2d.cn/guid/components/%E5%AF%B9%E8%AF%9D%E6%A1%86.html)的样式,更为精美,以适应暗黑模式。弹层的速度加快了。
* 修复了[按钮](https://jx2d.cn/guid/components/%E6%8C%89%E9%92%AE.html)自定义文字主题色时,不生效。
* ##### 重要: 重构了步骤条,变得更强大,属性保持不变,但不兼容之前的版本,主要是不兼容颜色值属性,旧版本只能输入颜色值,现不允许输入颜色值,只允许颜色类,和其它组件保持了统一性,同时提供了三个插槽用于自定义图标颜色等。变得更为美观和精致了。
移步[步骤条文档查看](https://jx2d.cn/guid/components/%E6%AD%A5%E9%AA%A4%E6%9D%A1.html)
* tm-bottomnavigation底部导航工具栏有了较大的更新,新版本,可以通过数据格式随意定义自己的样式,以突显用户需要的中间凸起的圆或者其它形状。
每一个项目都可以设置凸起的圆或者其它自己的形状和样式。[预览链接](https://jx2d.cn/guid/components/%E5%BA%95%E9%83%A8%E5%AF%BC%E8%88%AA%E5%B7%A5%E5%85%B7%E6%A0%8F.html)
* 修复在tm-translate[动画组件](https://jx2d.cn/guid/components/translate%E5%8A%A8%E7%94%BB.html)中嵌套其它组件时,计算高度错误。
* 更新了[tm-empty 数据空](https://jx2d.cn/guid/components/%E6%95%B0%E6%8D%AE%E7%A9%BA.html)组件,新增了label字段,以支持自定义提示文字。
## 1.1.32021-09-03
* 新增[【抽奖游戏组件】](https://jx2d.cn/guid/components/抽奖游戏.html),UI美观,颜值高,可自己定制,主题切换。
提供了:九宫格、老虎机、大转盘等常用的三种抽奖类游戏。并且每个游戏提供2套皮肤,当然你也可以自己设计。皮肤精美,游戏
好定制,可自己指定中奖,中奖概率、奖品图片可自定义。我个人编写不容易且服务器带宽有限,请不要直接引用我的游戏静态资源,请自行下载资源
到本地使用,提供游戏图片源AI文件,图片无水印。
* 调整了示例库界面和示例源码。
* 调整了类outlined颜色的梯度配比。原有outlined模式是主题色边框加透明背景。现改为:主题色边框+98%的主题色(饱和度的0.98).
使之更为柔和,符合整体搭配色彩。
* 缩减了主题库文本颜色的种类,原本是包含218种文本颜色,为了适应小程序大小。所以本次缩减为和背景颜色主题库一样的数量,具体请
查询官网:[CSS库预置主题](http://localhost:8081/guid/css/%E9%A2%84%E7%BD%AE%E4%B8%BB%E9%A2%98%E5%BA%93.html),从
原来的276kb缩减至254。
* 调整了类的投影透明度。黑白没有调整保持为0.08。除了黑白色的所有的颜色默认投影的透明度调整为0.2,原先为0.3;并且调整了投影系统比
原为1.2的拓展和3.3的系数。改为现在的1.5和4,现在所有有颜色的投影将变得更为柔和的同时也能显现出来投影的颜色。
* 调整了按钮组件默认的圆角,根据大小尺寸不一,各减1upx.
* 调整了css库文字的标准字号原有text-size-型号:xxs:20upx,xs:22upx,s:24upx,n:27upx,g:32upx,lg:34upx,xl:40upx
现调整为:xxs:20upx,xs:22upx,s:24upx,n:30upx,g:34upx,lg:36upx,xl:40upx,就是基本的字号由原来的14号字体调整为15号字体
大小。调整的动机是:不再遵循原有字号理论。因为现在手机屏幕大,分辨率清晰。原有的字号着实过小,看的难受。并且我建议大家使用
我的类来控制字号。这样方便有的软件:如果需要提供老年人使用时,就直接一键可切换大号字体。
* 主题css库因为一动,就是动整个组件库。因此改变我会非常谨慎。同时调整的同时,还在优化暗黑模式。之后的下个版本1.1.4,将推出主题动态切换的功能。
## 1.1.22021-09-01
* 新增[【时间轴组件】](https://jx2d.cn/guid/components/时间轴.html)UI美观,我已精心美化。
* 调整了[【帮助提示】](https://jx2d.cn/guid/components/帮助提示.html)弹层的文字大小为n,即标准字号大小。之前是s小号字体,在手机上显示更为合理。
* 新增了[【表格组件】](https://jx2d.cn/guid/components/表格.html),用于简单的业务展示,自定设置包括:列宽,单元格颜色,单元格夸列宽度设置,列颜色,行颜色,表头
等业务需要用到的强调显示的功能设置,具体请前往示例文档查阅。
* 调整了slider滑块组件,中间信息块横向显示时不居中的问题,现已调整。同时修复了在拖动滑块时,未阻止冒泡导致拖动的
同时拖动了页面,影响体验。经实际点按效果圆点在40upx时刚好合适拖动。之前的30upx过小,不好点按。同时优化了刻度尺拖动时
不够精确的体验。现在如果设定了刻度值拖动将会准确按照设定的值累加拖动。
同时调整了该示例,并且修改了插槽数据。具体见文档[滑块](https://jx2d.cn/guid/components/%E6%BB%91%E5%9D%97.html)
* 新增了[【展开更多】](https://jx2d.cn/guid/components/展开更多.html)组件,主要用于文章,列表内容的收缩更多和展开更多的显示,或者付费解锁阅读更多的场景。
* 新增了[【双向滑块】](https://jx2d.cn/guid/components/双向滑块.html),用法与已有的滑块一致,可双向按刻度滑动,双向竖向(刻度),横向双向滑动,
双向可交叉滑动,丝滑无阻碍。
* 修复组件【密码输入框】](https://jx2d.cn/guid/components/密码输入框.html),在输入完成后点击【确定】按钮未触发
confirm事件。这个是我疏忽忘记了。---感谢网友 --xiaolu-- 的提醒。
* 修正组件[【悬浮按钮】](https://jx2d.cn/guid/components/悬浮按钮.html)doc说明文档中对size属性的错误描述。文档写错了。组件是没问题的。
## 1.1.12021-08-30
* 修复了月份日历在ios系统下的bug,我忘记了我的月份日历 和 日历不是同一个库。搞混了。
* 修复日历,在时间范围选择上的一个bug,顺便记录下产生的过程。
使用new Date("2020/8")创建日期,理论上是正常的,但在ios下这是错误的。你需要这样创建:new Date(2020,7),可以查询MSDN文档;
同时美化了下标文本的间距。
* 调整了级联选择器底部的间距,使之更为美观合理。
* 调整了keyborad键盘按钮的默认圆角,调整了键盘标题上下的间距大小使之更为美观。
* 调整了poup的默认圆角由6改为10.
* 美化了password密码输入框的细节样式。
* 美化了upload组件的样式,同时新增了 主题色color 属性选项。
* 新增了文字渐变主题色彩,使用时,输入: text-gradient-色彩名称-accent 即可使文字与渐变背景同样的效果。
* 调整了pullBottom上拉刷新,下触底组件,新增了两个插槽供用户自己定义下拉和松开时显示自己的布局结构。
* 调整了按钮标准和大按钮时的两个的高度为76upx,之前是80upx
* 新增了 【帮助提示】组件,用于辅助弹层式不打扰页面常用的解释文字说明。
* 新增了 【定位组件】组件,用于辅助内容定位
## 1.1.02021-08-28
手机扫码预览
![Image](http://jx2d.cn/uniapp/static/logo_great.png)
### [文档介绍,点此打开](http://jx2d.cn)
![Image](http://jx2d.cn/uniapp/static/qrprev.png)
---
## 1.0.52021-08-20
** 非常好用的组件库,拿来即用 **
这是个初始版本1.0.0,并非1.0.4,因为我不知道怎么修改以前的信息,只能这么覆盖了。<br>
另外:初发版本,可能稳定性不可多 ,后续改动大,我预计要到1.1才能稳定。因为,不建议大家拿来生产用<br>
总的来说问题不大,如果觉得可以,对你有帮助,帮点个5星。如果觉得还不够完善,请再等下使用,等到稳定版本。会很快的。
***
### [文档介绍,点此打开](http://jx2d.cn)
***
## 1.0.42021-08-19
** 非常好用的组件库,拿来即用 **
更新了新的文档库见下方。
***
### [文档介绍,点此打开](http://jx2d.cn)
***
2021年8月19日14:57:35
@@ -0,0 +1,151 @@
<template>
<view class="tm-actionSheet ">
<tm-poup @change="toogle" ref="pop" v-model="showpop" height="auto" :black="black_tmeme" :bg-color="black_tmeme ? 'grey-darken-6' : 'grey-lighten-4'">
<view class="tm-actionSheet-title pa-32 pb-32 relative " :class="[black_tmeme ? 'grey-darken-5' : 'white']">
<view class="text-size-n text-align-center">{{ title }}</view>
<view class="tm-actionSheet-close rounded flex-center absolute" :class="black_tmeme ? 'grey-darken-4' : 'grey-lighten-3'">
<tm-icons @click="close" name="icon-times" size="24" :color="black_tmeme ? 'white' : 'grey'"></tm-icons>
</view>
</view>
<view>
<slot>
<tm-grouplist shadow="5" round="4">
<tm-listitem
:black="black_tmeme"
@click="onclick(index, item)"
v-for="(item, index) in actions"
:key="index"
:title="item[rangKey]"
:label="item['label'] ? item['label'] : ''"
:right-icon="item['rightIcon'] ? item['rightIcon'] : 'icon-angle-right'"
></tm-listitem>
</tm-grouplist>
</slot>
</view>
<view style="height: 50upx"></view>
</tm-poup>
</view>
</template>
<script>
/**
* 动作面板
* @description 动作面板,从底部弹出的操作菜单。
* @property {Boolean} black = [true|false] 默认:false,暗黑模式
* @property {Boolean} value = [true|false] 默认:false,显示菜单,推荐使用v-model,使用value.sync达到双向绑定。
* @property {String} title = [] 默认:'请操作',弹出层的标题。
* @property {Array} actions = [] 默认:[],格式见文档,操作数组。
* @property {String} rang-key = [title] 默认:title,actions对象数组时,自定义标题键。
* @property {Boolean} click-close = [true|false] 默认:true,点击项目时,是否自动关闭弹层。
* @property {Function} change 点击项目时触发,返回:{index:项目索引,data:actions的对象数据}
* @property {Function} input 弹层显示和隐藏时,将会触发。
* @example <tm-actionSheet @change="test" v-model="show" :actions="[{title:'说明文档',label:'这是说明文件的资料信息'},{title:'新建文件夹'}]"></tm-actionSheet>
*/
import tmGrouplist from '@/tm-vuetify/components/tm-grouplist/tm-grouplist.vue';
import tmListitem from '@/tm-vuetify/components/tm-listitem/tm-listitem.vue';
import tmIcons from '@/tm-vuetify/components/tm-icons/tm-icons.vue';
import tmPoup from '@/tm-vuetify/components/tm-poup/tm-poup.vue';
export default {
components: { tmGrouplist, tmListitem, tmIcons, tmPoup },
name: 'tm-actionSheet',
model: {
prop: 'value',
event: 'input'
},
props: {
value: {
type: Boolean,
default: false
},
black: {
type: Boolean,
default: null
},
title: {
type: String,
default: '操作栏'
},
// 数组格式。
/*
{
title:"标题",
label:"项目说明文字",
rightIcon:"",//右边图标。
}
*/
actions: {
type: Array,
default: () => {
return [];
}
},
// 自定义标题键key.
rangKey: {
type: String,
default: 'title'
},
// 点击项目时,是否关闭弹层
clickClose: {
type: Boolean,
default: true
}
},
data() {
return {
showpop: false
};
},
mounted() {
this.showpop = this.value;
},
watch: {
value: function(val) {
this.showpop = val;
}
},
computed: {
black_tmeme: function() {
if (this.black !== null) return this.black;
return this.$tm.vx.state().tmVuetify.black;
}
},
methods: {
close() {
this.$refs.pop.close();
},
toogle(e) {
let t = this;
if (e) {
this.$nextTick(function() {
if (this.showpop != this.value) {
this.showpop = this.value;
}
});
}
this.$emit('input', e);
this.$emit('update:value', e);
},
onclick(index, item) {
if (this.clickClose === true) {
this.$refs.pop.close();
this.$emit('change', {
index: index,
data: item
});
}
}
}
};
</script>
<style lang="scss" scoped>
.tm-actionSheet-title {
.tm-actionSheet-close {
top: 32upx;
right: 32upx;
width: 50upx;
height: 50upx;
}
}
</style>
@@ -0,0 +1,170 @@
<template>
<view class="tm-actionSheetMenu">
<tm-poup @change="toogle" ref="pop" :black="false" v-model="showpop" height="auto" bg-color="none">
<view class="ma-32">
<view class=" round-8 overflow ">
<tm-button
:fllowTheme="false"
:black="black_tmeme"
font-size="22"
font-color="grey"
:theme="black_tmeme ? 'grey-darken-4' : 'white'"
block
flat
>
{{ title }}
</tm-button>
<slot>
<tm-button
:fllowTheme="false"
:black="black_tmeme"
@click="onclick(index, item)"
v-for="(item, index) in actions"
:key="index"
:item-class="'text-' + color_tmeme"
:theme="black_tmeme ? 'grey-darken-4' : 'white'"
block
flat
>
{{ item[rangKey] ? item[rangKey] : item }}
</tm-button>
</slot>
</view>
<view style="height: 96upx " class="pb-24 pt-16">
<tm-button :round="8" :fllowTheme="false" :item-class="' text-weight-b'" :black="black_tmeme" @click="close" :theme="black ? 'grey-darken-4' : 'white'" block>
取消
</tm-button>
</view>
<!-- #ifdef H5 -->
<view style="height: var(--window-bottom);"></view>
<!-- #endif -->
</view>
</tm-poup>
</view>
</template>
<script>
/**
* 动作面板
* @description 动作面板,从底部弹出的操作菜单。
* @property {Boolean} black = [true|false] 默认:null,暗黑模式
* @property {Boolean} value = [true|false] 默认:false,显示菜单,推荐使用v-model,使用value.sync达到双向绑定。
* @property {String} title = [] 默认:'请操作',弹出层的标题。
* @property {Array} actions = [] 默认:[],可以是对象数组,也可是字符串数组。
* @property {String} rang-key = [title] 默认:title,actions对象数组时,需要提供自定义标题键。
* @property {Boolean} click-close = [true|false] 默认:true,点击项目时,是否自动关闭弹层。
* @property {Boolean} font-color = [] 默认:primary,文字颜色。可以是主题颜色,也可以是颜色格式rgb,rgba,#ff0000格式
* @property {Function} change 点击项目时触发,返回:{index:项目索引,data:actions的对象数据}
* @property {Function} input 弹层显示和隐藏时,将会触发。
* @example <tm-actionSheetMenu :value="true" :actions="['说明文档','说明文档']"></tm-actionSheetMenu>
*/
import tmPoup from '@/tm-vuetify/components/tm-poup/tm-poup.vue';
import tmButton from '@/tm-vuetify/components/tm-button/tm-button.vue';
export default {
components: { tmButton, tmPoup },
name: 'tm-actionSheetMenu',
model: {
prop: 'value',
event: 'input'
},
props: {
value: {
type: Boolean,
default: false
},
black: {
type: Boolean,
default: null
},
title: {
type: String,
default: '请操作'
},
fontColor: {
type: String,
default: 'black'
},
actions: {
type: Array,
default: () => {
return [];
}
},
// 自定义标题键key.
rangKey: {
type: String,
default: 'title'
},
// 点击项目时,是否关闭弹层
clickClose: {
type: Boolean,
default: true
},
// 跟随主题色的改变而改变。
fllowTheme: {
type: Boolean | String,
default: true
}
},
data() {
return {
showpop: false
};
},
mounted() {
this.showpop = this.value;
},
computed: {
color_tmeme: function() {
if (this.$tm.vx.state().tmVuetify.color !== null && this.$tm.vx.state().tmVuetify.color && this.fllowTheme) {
return this.$tm.vx.state().tmVuetify.color;
}
return this.fontColor;
},
black_tmeme: function() {
if (this.black !== null) return this.black;
return this.$tm.vx.state().tmVuetify.black;
}
},
watch: {
value: function(val) {
this.showpop = val;
}
},
methods: {
close() {
this.$refs.pop.close();
},
toogle(e) {
let t = this;
if (e) {
this.$nextTick(function() {
if (this.dataValue != this.defaultValue) {
this.dataValue = this.defaultValue;
}
});
}
this.$emit('input', e);
this.$emit('update:value', e);
},
onclick(index, item) {
this.$emit('change', { index: index, data: item });
if (this.clickClose === true) {
this.$refs.pop.close();
}
}
}
};
</script>
<style lang="scss" scoped>
.tm-actionSheetMenu-title {
.tm-actionSheetMenu-close {
top: 32upx;
right: 32upx;
width: 50upx;
height: 50upx;
}
}
</style>
+226
View File
@@ -0,0 +1,226 @@
<template>
<view class="tm-album">
<view class="tm-album-body flex flex-wrap" :class="[`ml--${gutter}`]" :style="{width:alb_wk_body_size+'rpx'}">
<block v-for="(item,index) in listData" :key="index">
<view v-if="index<max-1" :style="{width:alb_body_size+'rpx'}" class="tm-album-item">
<view @click="clickAlbum(item,index)" :class="[`py-${item[textKey]?0:gutter}`,`px-${gutter}`]">
<view :style="{width:alb_size+'rpx',height:height||alb_size+'rpx'}" class="relative flex-center">
<tm-images :model="model" :round="round" :width="alb_size" :height="(height||alb_size)" :src="item[rangKey]"></tm-images>
<view class="tm-album-action absolute fulled fulled-height t-0 l-0" :class="[
actionsPos=='top'?'flex-top-end':'',
actionsPos=='bottom'?'flex-end-right':'',
]">
<slot name="actions" :item="{data:item,index:index}"></slot>
</view>
</view>
<view v-if="item[textKey]" class="py-24 text-align-center text-overflow">
<slot name="default" :text="{text:item[textKey]}">
<text class="text-size-s ">{{item[textKey]}}</text>
</slot>
</view>
</view>
</view>
</block>
<view v-if="listData.length>max-1" :style="{width:alb_body_size+'rpx'}" class="tm-album-item " :class="[`round-${round}`,black_tmeme?'bk':'']">
<view @click="more" :class="[`px-${gutter}`]">
<view :style="{width:alb_size+'rpx',height:height||alb_size+'rpx'}" class="relative flex-center flex-col" :class="[`round-${round}`,color_tmeme]">
<view><text class="iconfont icon-angle-right text-size-xl"></text></view>
<view class="text-size-s mt-12">更多</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
/**
* 相册集
* @property {Array} list = [] 数据列表,可以是对象数组,也可以是字符串数组,也可对象和字符串数组混合型。
* @property {Number} grid = [] 默认:4,默认一行几个。
* @property {Number} height = [] 默认:0,默认宽度和高度是相同的。指定相册集的高度。如果指定了高度将不使用计算的高度。
* @property {Number} gutter = [] 默认:4,图片之间的间隙。
* @property {Number} round = [] 默认:3,项目的圆角
* @property {Number} max = [] 默认:999,最大可展示数量,超过不显示,以更多项目代替。
* @property {String} rang-key = [] 默认:'src',如果list的项目是对象,则需要提供图片字段名称
* @property {String} text-key = [] 默认:'text',如果list的项目是对象,则需要提供图片标题字段名称
* @property {Boolean|String} preview = [] 默认:true,点击图片是否可以预览。
* @property {Boolean|String} actions-pos = [bottom|top] 默认:bottom,可选bottom,top,内部压在图片上方的元素位置。内容可在插槽actions中布局。
* @property {String} color = [] 默认:primary,主题色名称,显示更多项目的背景主题色。
*/
import tmImages from '@/tm-vuetify/components/tm-images/tm-images.vue';
export default {
name:"tm-album",
components: {
tmImages
},
props: {
list: {
type: Array,
default: ()=>[]
},
//指定相册集的高度。如果指定了高度将不使用计算的高度。
height:{
type:Number,
default:0
},
grid:{
type:Number,
default:4
},
gutter:{
type:Number,
default:4
},
round:{
type:Number,
default:3
},
max:{
type:Number,
default:999
},
rangKey: {
type: String,
default: 'src'
},
textKey: {
type: String,
default: 'text'
},
preview:{
type:Boolean,
default:true
},
actionsPos:{
type:String,
default:'bottom',//bottomtop
},
black:{
type:Boolean,
default:null
},
// 跟随主题色的改变而改变。
fllowTheme:{
type:Boolean|String,
default:true
},
color:{
type:String,
default:"primary"
},
model:{
type:String,
default:'scaleToFill',//bottomtop
}
},
data() {
return {
alb_size:0,
alb_body_size:0,
alb_wk_body_size:0,
listAlbum:[]
};
},
watch:{
list:{
deep:true,
handler(){
this.listData = this.chuliList(this.list)
}
}
},
created(){
this.listData = this.chuliList(this.list)
},
computed: {
black_tmeme: function() {
if (this.black !== null) return this.black;
return this.$tm.vx.state().tmVuetify.black;
},
color_tmeme:function(){
if(this.$tm.vx.state().tmVuetify.color!==null&&this.$tm.vx.state().tmVuetify.color && this.fllowTheme){
return this.$tm.vx.state().tmVuetify.color;
}
return this.color;
},
listData:{
get:function () {
return this.listAlbum;
},
set:function (val) {
this.listAlbum = val;
}
}
},
async mounted() {
await this.setInits();
},
methods: {
//处理数据,以标准化。
chuliList(){
let list = [...this.list];
let p = [];
for (var i = 0; i < list.length; i++) {
if(typeof list[i] == 'string'){
let ls = {}
ls[this.rangKey] = list[i];
ls[this.textKey] = null;
p.push(ls)
}else if(typeof list[i] == 'object'){
let ls = {}
ls[this.rangKey] = '';
ls[this.textKey] = null;
ls={...ls,...list[i]}
p.push(ls)
}
}
return p;
},
async setInits(){
this.$nextTick(async function() {
let t = this;
this.$tm.sleep(150).then(async function(){
let p = await t.$Querey('.tm-album',t).catch(e =>{});
if (!p[0]) return;
let grid = t.grid || 1;
let size = (p[0].width+t.gutter)/grid;
let gutter = t.gutter*2
let ratio = 750/uni.upx2px(750);
let blv = size * ratio - gutter;
t.alb_body_size = size * ratio;
t.alb_wk_body_size = p[0].width*ratio+t.gutter*2+(grid*2);
t.alb_size = blv;
})
});
},
clickAlbum(item,index){
if(this.preview){
uni.$tm.preview(item[this.rangKey],this.listData,this.rangKey);
}
this.$emit('click',{item,index})
},
more(){
this.$emit('more')
}
},
}
</script>
<style lang="scss" scoped>
.tm-album{
.tm-album-action{
}
}
</style>
@@ -0,0 +1,290 @@
<template>
<!-- 提示框 -->
<view :animation="animation" v-if="show" @click="onclick" @tap="onclick" class="tm-alerts text-size-n "
:class="[color_tmeme,(outlined||outlined=='true')?'outlined':'','round-'+round,black_tmeme?'bk':'',
text=='true'||text?'text':'',
`mx-${margin[0]} my-${margin[1]} shadow-${color_tmeme}-${shadow}`
]"
>
<view class=" flex-start" :class="[dense===true||dense=='true'?'py-16 px-32':'py-32 px-32']">
<view v-if="leftIcon&&leftIcon!='true'" class="body-left ">
<tm-icons :color="(text||outlined)?color_tmeme:iconColor" :dense="true" :name="leftIcon" size="32"></tm-icons>
</view>
<view v-if="valueType===true" @click.stop="openUrl(label)" class="body-center text-overflow">{{label[rangKey]?label[rangKey]:label}}</view>
<view v-if="valueType===false" class="tm--alerts--id body-center" :style="{height:sliderwidth.height}">
<swiper v-if="sliderwidth.width" :vertical="vertical" :style="{height:sliderwidth.height,width:sliderwidth.width}" :interval="interval_vs" circular autoplay :duration="duration_vs" >
<swiper-item @click.stop="openUrl(item)" v-for="(item,index) in label" :key="index" class="body-center text-overflow" style="line-height: 50upx;" :style="{height:sliderwidth.height}">
{{item[rangKey]?item[rangKey]:item}}
</swiper-item>
</swiper>
</view>
<view v-if="url && !close" class="body-right text-align-right">
<tm-icons :color="(text||outlined)?color_tmeme:iconColor" :dense="true" :name="rightIcon" size="28"></tm-icons>
</view>
<view v-if="close" class="body-right text-align-right">
<tm-icons @click="closeview" :color="(text||outlined)?color_tmeme:iconColor" :dense="true" name="icon-times" size="28"></tm-icons>
</view>
</view>
</view>
</template>
<script>
/**
* 消息提示框
* @property {String | Boolean} dense = [true|false] 默认:true,是否减小边距。
* @property {Array} margin = [] 默认:[32,24],边距。
* @property {String|Number} shadow = [] 默认:6,投影
* @property {String|Number} round = [] 默认:4,圆角
* @property {String|Number} duration = [] 默认:500,轮播速度。
* @property {String|Number} interval = [] 默认:1500,轮播间隙。
* @property {String | Boolean} outlined = [true|false] 默认:false,是否为边框样式
* @property {String | Boolean} text = [true|false] 默认:false,是否为文本模式,即减淡背景颜色。
* @property {String | Array} label = [true|false] 默认:"",当为数组时,自动变成轮播信息。
* @property {String | Boolean} black = [true|false] 默认:false,是否开启暗黑模式
* @property {String} color = [] 默认:primary,主题颜色名称
* @property {String} icon-color = [] 默认:white,图标的主题颜色名称
* @property {Boolean} url = [true|false] 默认:false,是否显示打开链接图标
* @property {String} left-icon = [] 默认:"icon-lightbulb",左边图标名称
* @property {String} right-icon = [] 默认:"icon-angle-right",右边图标名称
* @property {String} rang-key = [] 默认:"text",当label娄数组时需要提供此值。
* @property {String | Boolean} ani = [true|false] 默认:false,是否禁用动画,
* @property {String | Boolean} vertical = [true|false] 默认:false,是否是纵向。
* @property {String | Boolean} close = [true|false] 默认:false 是否显示关闭图标,
* @property {Function} click 整个组件点击事件
* @example <tm-alerts label="9"></tm-alerts>
*/
import tmIcons from "@/tm-vuetify/components/tm-icons/tm-icons.vue"
export default {
components:{tmIcons},
name:"tm-alerts",
model:{
prop:'value',
event:'input'
},
props: {
// 是否减小边距。
dense: {
type: String | Boolean,
default: true
},
// 是否为边框样式,
outlined: {
type: String | Boolean,
default: false
},
// 边距。
margin: {
type: Array,
default: ()=>{
return [32,24];
}
},
shadow: {
type: String|Number,
default: 4
},
round: {
type: String|Number,
default: 3
},
// 是否为文本模式,即减淡背景颜色。
text: {
type: String | Boolean,
default: false
},
// 当为数组时,自动变成轮播信息。
label:{
type:String | Array,
default:""
},
// 当label娄数组时需要提供此值。
rangKey:{
type:String,
default:"text"
},
// 是否开启暗黑模式
black: {
type: String | Boolean,
default: null
},
// 主题颜色名称
color: {
type: String,
default: 'primary'
},
// 图标主题颜色名称
iconColor: {
type: String,
default: 'white'
},
// 是否显示为打开链接的消息提醒。
url: {
type: Boolean,
default: false
},
// 左边图标名称
leftIcon: {
type: String,
default: 'icon-lightbulb'
},
// 右边图标名称
rightIcon: {
type: String,
default: 'icon-angle-right'
},
//是否禁用动画,
ani: {
type: Boolean,
default: false
},
// 是否显示关闭图标
close: {
type: Boolean,
default: false
},
// 跟随主题色的改变而改变。
fllowTheme:{
type:Boolean|String,
default:true
},
duration:{
type:Number,
default:1000
},
vertical:{
type:Boolean,
default:false
},
//间隔,单位ms
interval:{
type:Number,
default:1500
},
value: {
type: Boolean|String,
default: true
},
},
data() {
return {
animation: null,
outid: 'alert-88',
sliderwidth:{height:0,width:0},
};
},
computed:{
// valueType:true,//默认是string.
valueType:function(){
if(Array.isArray(this.label)){
return false;
}
return true;
},
black_tmeme: function() {
if (this.black !== null) return this.black;
return this.$tm.vx.state().tmVuetify.black;
},
color_tmeme:function(){
if(this.$tm.vx.state().tmVuetify.color!==null&&this.$tm.vx.state().tmVuetify.color && this.fllowTheme){
return this.$tm.vx.state().tmVuetify.color;
}
return this.color;
},
duration_vs:function () {
return this.duration;
},
interval_vs:function () {
return this.interval;
},
show:{
get:function(){
return this.value;
},
set:function(val){
this.$emit('input',val)
this.$emit('update:value',val)
},
},
},
mounted() {
let t= this;
this.$Querey('.tm--alerts--id',this,50).then(res=>{
let wh = res[0];
t.sliderwidth = {
height:uni.upx2px(50)+'px',width:(wh.width||0)+'px'
}
}).catch(e=>{})
},
methods: {
onclick(e) {
this.$emit('click', e);
},
openUrl(e) {
this.$emit('click', e);
if(typeof e !== 'object' || !e['url']) return;
let urls = getCurrentPages();
let url = e.url;
if (urls.length >= 5) {
uni.redirectTo({
url:url,
fail: e => {
console.error(e);
}
});
} else {
uni.navigateTo({
url: url,
fail: e => {
console.error(e);
}
});
}
},
closeview(e) {
let t = this;
var animation = uni.createAnimation({
duration: 500,
timingFunction: 'ease'
});
this.animation = animation;
animation
.scale(0.7, 0.7)
.opacity(0)
.step();
this.outid = setTimeout(() => {
t.show = false;
t.animation = null;
}, 500);
}
}
};
</script>
<style lang="scss" scoped>
.tm-alerts {
.gs {
width: 50upx;
min-width: 70upx;
}
.body-left,
.body-right {
@extend .gs;
}
.body-center {
width: 100%;
}
}
</style>
@@ -0,0 +1,204 @@
<template>
<view
@click="onclick"
:style="configStyle"
class="tm--avatar d-inline-block "
:class="[titl ? 'round-2' : 'rounded', text ? '' : `shadow-${color_tmeme}-${shadow}`, customClass]"
>
<view class="tm--avatar--dot" :class="[dotPos == 'top' ? 'top' : '', dotPos == 'bottom' ? 'bottom' : '']">
<slot name="dot">
<view v-if="dotPos == 'bottom'" style="width: 100%;"><tm-badges :offset="[0, -10]" v-if="dot" :color="dotColor"></tm-badges></view>
<tm-badges :offset="[2, -2]" v-if="dot && dotPos == 'top'" :color="dotColor"></tm-badges>
</slot>
</view>
<view
class="flex-center overflow text-align-center tm--avatar--conter"
:class="[
titl ? `round-${round}` : 'rounded',
!label && !src ? color_tmeme : '',
label ? color_tmeme : '',
black_tmeme ? 'bk' : '',
text ? 'text' : '',
outlined ? 'outlined' : '',
`border-${color_tmeme}-a-${border}`
]"
:style="{ width: imgstyle.width, height: imgstyle.height }"
>
<slot name="default" :src="src">
<image v-if="!label" :class="[titl ? 'round-0' : 'rounded']" :style="{ width: imgstyle.width, height: imgstyle.height }" :src="src"></image>
<text v-if="label" :style="{ fontSize: fontsize }">{{ label }}</text>
</slot>
</view>
</view>
</template>
<script>
/**
* 头像框
* @property {Number | String} size = [98|80|64] 默认:98,头像的宽高,单位upx
* @property {String} color = [primary] 默认:primary,主题背景色
* @property {Number|String} shadow = [] 默认:0,投影
* @property {Number} round = [] 默认:0,圆角,只有在titl下起作用。
* @property {String} label = [] 默认:'',当填入信息时,文本头像,禁用img模式。
* @property {String} font-size = [] 默认:'36',文字大小,单位upxlabel时启用。
* @property {String} src = [] 默认:'https://picsum.photos/200',头像图片地址,label时禁用用。
* @property {Boolean} titl = [true|false] 默认:false,开户titl模式即正常的正方形而非圆形。
* @property {Boolean} text = [true|false] 默认:false,文本模式
* @property {Boolean} outlined = [true|false] 默认:false,边框模式
* @property {Boolean} dot = [true|false] 默认:false,显示头像点。建议自行通过slot dot 自行设置。
* @property {String} dot-color = [] 默认:primary,角标颜色
* @property {String} dot-pos = [top|bottom] 默认:top,解析的位置
* @property {Number|String} border = [] 默认:0,边框,边框颜色为你的color颜色
* @property {String | Boolean} black = [true|false] 默认:false,是否开启暗黑模式
* @property {String} custom-class = [] 默认:'',自定义类。
* @property {Function} click 返回:{event,src,label})。
* @example <tm-avatar ></tm-avatar>
*/
import tmBadges from '@/tm-vuetify/components/tm-badges/tm-badges.vue';
export default {
components: { tmBadges },
name: 'tm-avatar',
props: {
// 头像的宽高upx
size: {
type: Number | String,
default: 98
},
// 主题背景色
color: {
type: String,
default: 'primary'
},
dotColor: {
type: String,
default: 'red'
},
// 自定义类
customClass: {
type: String,
default: ''
},
// 投影
shadow: {
type: Number | String,
default: 0
},
// 当填入信息时,禁用img模式。
label: {
type: String,
default: ''
},
// 单位upx
fontSize: {
type: String | Number,
default: 36
},
// 注意,只有当label没有填写时才会启用。
src: {
type: String,
default: 'https://picsum.photos/200'
},
// 开户til模式即正常的正方形而非圆形。
titl: {
type: Boolean,
default: false
},
black: {
type: Boolean | String,
default: null
},
round: {
type: Number,
default: 0
},
text: {
type: Boolean,
default: false
},
outlined: {
type: Boolean,
default: false
},
dot: {
type: Boolean,
default: false
},
dotPos: {
type: String,
default: 'top'
},
border: {
type: Number | String,
default: 0
},
// 跟随主题色的改变而改变。
fllowTheme: {
type: Boolean | String,
default: true
}
},
data() {
return {
imgstyle: { width: 0, height: 0 },
wkstyle: {}
};
},
computed: {
fontsize: function() {
return uni.upx2px(this.fontSize) + 'px';
},
black_tmeme: function() {
if (this.black !== null) return this.black;
return this.$tm.vx.state().tmVuetify.black;
},
color_tmeme: function() {
if (this.$tm.vx.state().tmVuetify.color !== null && this.$tm.vx.state().tmVuetify.color && this.fllowTheme) {
return this.$tm.vx.state().tmVuetify.color;
}
return this.color;
},
configStyle: {
get: function() {
return this.wkstyle;
},
set: function(obj) {
this.wkstyle = uni.$tm.objToString(obj);
}
}
},
mounted() {
this.imgstyle = {
width: uni.upx2px(parseInt(this.size)) + 'px',
height: uni.upx2px(parseInt(this.size)) + 'px'
};
},
methods: {
setConfigStyle(val) {
this.configStyle = val;
},
onclick(e) {
this.$emit('click', { event: e, src: this.src, label: this.label });
}
}
};
</script>
<style lang="scss" scoped>
.tm--avatar {
position: relative;
line-height: 0;
vertical-align: middle;
.tm--avatar--dot {
position: absolute;
z-index: 10;
width: 100%;
&.bottom {
bottom: 0upx;
}
}
.tm--avatar--conter {
line-height: 0;
}
}
</style>
@@ -0,0 +1,524 @@
<template>
<view class="tm-avatarCrop fixed t-0 l-0 black" :style="{ width: `${width}px`, height: `${height}px` }">
<tm-sticky model="bottom">
<view class="fulled flex-center mb-32">
<tm-button size="m" text @click="$emit('cancel')">取消</tm-button>
<tm-button size="m" @click="selectedImage">选择图片</tm-button>
<tm-button size="m" @click="saveImage">确定</tm-button>
</view>
</tm-sticky>
<view class="flex-center"><canvas id="AvatarCrop" canvas-id="AvatarCrop" :style="{ width: `${area_width}px`, height: `${area_height}px` }"></canvas></view>
<movable-area class="absolute t-0 l-0 zIndex-n1" :style="{ width: `${width}px`, height: `${height}px` }">
<movable-view
:out-of-bounds="false"
@scale="movaScaleChange"
@change="movaChange"
:x="areview_x"
:y="areview_y"
direction="all"
:scale="true"
:style="{ width: `${scale_w}px`, height: `${scale_h}px` }"
>
<image v-show="image_src" @load="loadImage" :src="image_src" :style="{ width: `${scale_w}px`, height: `${scale_h}px` }"></image>
</movable-view>
</movable-area>
<view :style="{ width: `${width}px`, height: `${height}px` }" class="absolute tm-avatarCrop-bodywk t-0 l-0 zIndex-n16">
<view
class="tm-avatarCrop-area relative"
:class="[isArc ? 'rounded' : '']"
:style="{
width: `${area_width}px`,
height: `${area_height}px`,
top: `${posArray[0].y}px`,
left: `${posArray[0].x}px`
}"
>
<view class="flex-center text-size-s" :style="{ height: pos_size + 'px' }">:{{ Math.floor(area_width) }},:{{ Math.floor(area_height) }}</view>
<block v-for="(item, index) in 4" :key="index">
<view
v-if="(isRatio == true && index !== 3) || index == 3"
:key="index"
:style="{ width: `${pos_size}px`, height: `${pos_size}px` }"
@touchstart.stop.prevent="m_start($event, index)"
@touchmove.stop.prevent="m_move($event, index)"
@touchend.stop.prevent="m_end($event, index)"
@mousedown.stop.prevent="m_start($event, index)"
@mousemove.stop.prevent="m_move($event, index)"
@mouseup.stop.prevent="m_end($event, index)"
@mouseleave="m_end($event, index)"
class="tm-avatarCrop-pos absolute black opacity-5"
:class="[
'tm-avatarCrop-pos-'+index,
index == 0?'tm-avatarCrop-area-top-left':'',
index == 1?'tm-avatarCrop-area-top-right': '',
index == 2?'tm-avatarCrop-area-bottom-left': '',
index == 3?'tm-avatarCrop-area-bottom-right': ''
]"
:id="`${index}`"
>
<tm-icons style="line-height: 0;" color="white" v-if="index !== 3" :size="22" dense name="icon-expand-alt"></tm-icons>
<tm-icons style="line-height: 0;" color="white" v-else :size="22" dense name="icon-arrows-alt"></tm-icons>
</view>
</block>
</view>
</view>
</view>
</template>
<script>
/**
* 图片裁剪
* @property {Number} area-width = [] 默认300,裁剪框的宽度
* @property {Number} area-height = [] 默认300,裁剪框的高度
* @property {Number} quality = [] 默认1,图片压缩质量0-1
* @property {String} fileType = [jpg|png] 默认 png
* @property {Boolean} is-ratio = [] 默认false,是否允许用户调整裁剪框的大小
* @property {Boolean} is-arc = [] 默认false,是否圆形
*/
import tmIcons from '@/tm-vuetify/components/tm-icons/tm-icons.vue';
import tmButton from '@/tm-vuetify/components/tm-button/tm-button.vue';
import tmSticky from '@/tm-vuetify/components/tm-sticky/tm-sticky.vue';
import tmImages from '@/tm-vuetify/components/tm-images/tm-images.vue';
export default {
name: 'tm-avatarCrop',
props: {
areaWidth: {
type: Number,
default: 300
},
areaHeight: {
type: Number,
default: 300
},
//是否允许用户调整距形区域的大小 。
isRatio: {
type: Boolean,
default: false
},
//是否圆形。
isArc: {
type: Boolean,
default: false
},
quality: {
type: Number,
default: 1
},
fileType:{
type:String,
default:'png'
},
confirm: {
type: Function,
default: function(data) {
return function(data) {};
}
}
},
computed: {},
components: {
tmIcons,
tmButton,
tmSticky,
tmImages
},
data() {
return {
width: 0,
height: 0,
canvanid: 'AvatarCrop',
showCanva: false,
area_width: 0,
area_height: 0,
prevent_left: 0,
prevent_top: 0,
old_x: 0,
old_y: 0,
posArray: [],
endDrage: true,
pos_size: 24,
image_src: '',
scale_w: 0, //图片缩放的宽,
scale_h: 0, //图片缩放的高。
scale: 1,
real_w: 0,
real_h: 0,
scale_areview_x: 0,
scale_areview_y: 0,
areview_x: 0,
areview_y: 0,
areview_new_x: 0,
areview_new_y: 0,
isAddImage: false
};
},
destroyed() {},
created() {
let sys = uni.getSystemInfoSync();
this.width = sys.windowWidth;
this.height = sys.screenHeight;
this.area_width = uni.upx2px(this.areaWidth);
this.area_height = uni.upx2px(this.areaHeight);
let dr = [];
for (let i = 0; i < 4; i++) {
dr.push({
x: 0,
y: 0
});
}
dr[0].x = (this.width - this.area_width) / 2;
dr[0].y = (this.height - this.area_height) / 2;
this.posArray = [...dr];
},
async mounted() {
let t = this;
await this.jishunTopData();
},
methods: {
async jishunTopData() {
let t =this;
this.$nextTick(async function() {
this.listData = [];
uni.$tm.sleep(100).then(s=>{
let psd = uni.createSelectorQuery().in(t);
psd.select('.tm-avatarCrop-pos-0').boundingClientRect()
.select('.tm-avatarCrop-pos-1').boundingClientRect()
.select('.tm-avatarCrop-pos-2').boundingClientRect()
.select('.tm-avatarCrop-pos-3').boundingClientRect()
.exec(function(p){
let list = p;
let dr = [...t.posArray];
for (let i = 0; i < list.length; i++) {
if(list[i]){
dr.splice(parseInt(list[i].id), 1, {
x: list[i].left,
y: list[i].top
});
}
}
t.posArray = [...dr];
})
})
});
},
async loadImage(e) {
this.isAddImage = true;
this.posArray.splice(0, 1, {
x: (this.width - this.area_width) / 2,
y: (this.height - this.area_height) / 2
});
await this.jishunTopData();
this.$nextTick(async function() {
let img_w = e.detail.width;
let img_h = e.detail.height;
this.real_w = img_w;
this.real_h = img_h;
let ratio_w = img_w >= this.width ? this.width : img_w;
let ratio_h = ratio_w / (img_w / img_h);
this.scale_w = ratio_w;
this.scale_h = ratio_h;
//图片宽大于高度时,
this.areview_y = (this.height - this.scale_h) / 2;
this.areview_x = (this.width - this.scale_w) / 2;
this.areview_new_x = this.areview_x;
this.areview_new_y = this.areview_y;
});
},
selectedImage() {
let t = this;
uni.chooseImage({
count: 1,
sizeType: ['compressed'],
success: function(res) {
t.image_src = res.tempFilePaths[0];
}
});
},
saveImage() {
if (!this.image_src) {
uni.$tm.toast('未选择图片');
return;
}
let t = this;
this.$nextTick(async function() {
let scale_x = this.posArray[0].x - this.areview_new_x;
let scale_y = this.posArray[0].y - this.areview_new_y;
//计算真实的xy时,需要通过原有的缩放的大小通过比例来放大或者缩小到真实在源图片上的坐标。
let real_x = (this.real_w / (this.scale_w * this.scale)) * scale_x;
let real_y = (this.real_h / (this.scale_h * this.scale)) * scale_y;
let real_w = (this.real_w / (this.scale_w * this.scale)) * this.area_width;
let real_h = real_w;
if (this.isRatio) {
real_h = (this.real_h / (this.scale_h * this.scale)) * this.area_height;
}
const ctx = uni.createCanvasContext('AvatarCrop', this);
if (!ctx) return;
//如果把框移动到图片外,则不要截取。
if (real_x < 0 || real_y < 0) {
uni.$tm.toast('请把框移入图片中');
return;
}
if (this.isArc) {
ctx.beginPath();
ctx.arc(this.area_width / 2, this.area_width / 2, this.area_width / 2, 0, 2 * Math.PI);
ctx.clip();
}
ctx.drawImage(this.image_src, real_x, real_y, real_w, real_h, 0, 0, this.area_width, this.area_height);
uni.showLoading({ title: '...' });
function getimage() {
let rtx = uni.getSystemInfoSync().pixelRatio;
let a_w = t.area_width * rtx;
let a_h = t.area_height * rtx;
console.log(a_w,a_h);
uni.canvasToTempFilePath(
{
x: 0,
y: 0,
width: a_w,
height: a_h,
canvasId: 'AvatarCrop',
quality:t.quality,
fileType:t.fileType,
success: function(res) {
// 在H5平台下,tempFilePath 为 base64
uni.hideLoading();
t.$nextTick(function() {
t.$emit('confirm', { width:a_w, height: a_h, src: res.tempFilePath });
t.confirm({ width: a_w, height: a_h, src: res.tempFilePath });
});
},
fail: function(res) {
uni.$tm.toast('请重试');
}
},
t
);
}
ctx.draw(true, function() {
getimage();
});
});
},
movaChange(e) {
//当添加新图片时,这里的执行要比添加时的慢。因此会覆盖前面设置的xy
if (!this.isAddImage) {
//移动后,真实的x,y已经得到,不需要再计算缩放的xy
//(因为uniapp的bug缩放后返回 的xy始终是原有的xy而不是真实的)
this.scale_areview_x = 0;
this.scale_areview_y = 0;
this.areview_new_x = e.detail.x;
this.areview_new_y = e.detail.y;
} else {
this.isAddImage = false;
}
},
movaScaleChange(e) {
//通过缩放,计算出缩放后的x,y的和原有的x,y之间的差值得到真实的x,y。
//(因为uniapp的bug缩放后返回 的xy始终是原有的xy而不是真实的)
let scale_x = -(this.scale_w - this.scale_w * e.detail.scale) / 2;
let scale_y = (this.scale_h - this.scale_h * e.detail.scale) / 2;
this.areview_new_x = -scale_x;
this.areview_new_y = this.posArray[0].y - Math.abs(scale_y);
// 保存缩放的比例。
this.scale = e.detail.scale;
},
m_start(event, index) {
event.preventDefault();
event.stopPropagation();
const ctx = uni.createCanvasContext('AvatarCrop', this);
if (ctx) {
ctx.clearRect(0, 0, this.area_width, this.area_height);
ctx.draw();
}
var touch;
if (event.type.indexOf('mouse') == -1 && event.changedTouches.length == 1) {
touch = event.changedTouches[0];
} else {
touch = {
pageX:event.pageX,
pageY:event.pageY
}
}
// #ifdef APP-VUE
if (index == 0 || index == 2) {
this.old_x = touch.pageX;
} else if (index == 1 || index == 3) {
this.old_x = touch.pageX + this.area_width;
}
if (index == 0 || index == 1) {
this.old_y = touch.pageY;
} else if (index == 2 || index == 3) {
this.old_y = touch.pageY+ this.area_height;
}
// #endif
// #ifndef APP-VUE
if (index == 0 || index == 2) {
this.old_x = touch.pageX - event.currentTarget.offsetLeft - this.posArray[index].x;
} else if (index == 1 || index == 3) {
this.old_x = touch.pageX - event.currentTarget.offsetLeft - this.posArray[index].x + this.area_width - this.pos_size;
}
if (index == 0 || index == 1) {
this.old_y = touch.pageY - event.currentTarget.offsetTop - this.posArray[index].y;
} else if (index == 2 || index == 3) {
this.old_y = touch.pageY - event.currentTarget.offsetTop - this.posArray[index].y + this.area_height - this.pos_size;
}
// #endif
this.endDrage = false;
},
m_move(event, index) {
if (this.endDrage) return;
let t = this;
event.preventDefault();
event.stopPropagation();
var touch;
if (event.type.indexOf('mouse') == -1 && event.changedTouches.length == 1) {
var touch = event.changedTouches[0];
} else {
touch = {
pageX:event.pageX,
pageY:event.pageY
}
}
// #ifdef APP-VUE
let ch = touch.pageY - this.pos_size/2;
let chx = touch.pageX - this.pos_size/2;
// #endif
// #ifndef APP-VUE
let ch = touch.pageY - t.old_y;
let chx = touch.pageX - t.old_x;
// #endif
let pos_size = this.pos_size;
let x_cha_len = chx - t.posArray[index].x;
let y_cha_len = ch - t.posArray[index].y;
t.posArray.splice(index, 1, {
x: chx,
y: ch
});
let w = 0;
let h = 0;
if (index == 0) {
t.posArray.splice(1, 1, {
x: t.posArray[1].x,
y: ch
});
t.posArray.splice(2, 1, {
x: chx,
y: t.posArray[2].y
});
w = t.posArray[1].x + pos_size - t.posArray[0].x;
h = t.posArray[2].y + pos_size - t.posArray[0].y;
} else if (index == 1) {
t.posArray.splice(0, 1, {
x: t.posArray[0].x,
y: ch
});
t.posArray.splice(3, 1, {
x: chx,
y: t.posArray[3].y
});
w = t.posArray[1].x + pos_size - t.posArray[0].x;
h = t.posArray[2].y + pos_size - t.posArray[1].y;
} else if (index == 2) {
t.posArray.splice(0, 1, {
x: chx,
y: t.posArray[0].y
});
t.posArray.splice(3, 1, {
x: t.posArray[3].x,
y: ch
});
w = t.posArray[3].x + pos_size - t.posArray[2].x;
h = t.posArray[2].y + pos_size - t.posArray[1].y;
}
if (index !== 3) {
this.area_width = w < 30 ? 30 : w;
this.area_height = h < 30 ? 30 : h;
} else {
let top_x = chx - this.area_width + pos_size;
let top_y = ch - this.area_height + pos_size;
t.posArray.splice(0, 1, {
x: top_x,
y: top_y
});
t.posArray.splice(1, 1, {
x: top_x + this.area_width - pos_size,
y: top_y
});
t.posArray.splice(2, 1, {
x: top_x,
y: top_y + this.area_height - pos_size
});
}
},
m_end(event, index) {
if (this.disabled) return;
let t = this;
event.preventDefault();
event.stopPropagation();
this.endDrage = true;
}
}
};
</script>
<style lang="scss" scoped>
.tm-avatarCrop {
.tm-avatarCrop-bodywk {
pointer-events: none;
}
.tm-avatarCrop-area {
background-color: rgba(0, 0, 0, 0.3);
border: 1px dotted rgba(255, 255, 255, 0.7);
pointer-events: none;
.tm-avatarCrop-pos {
display: flex;
justify-content: center;
align-items: center;
background-color: grey;
pointer-events: auto;
&.tm-avatarCrop-area-top-left {
left: 0;
top: 0;
transform: rotate(90deg);
}
&.tm-avatarCrop-area-top-right {
right: 0;
top: 0;
}
&.tm-avatarCrop-area-bottom-right {
right: 0;
bottom: 0;
}
&.tm-avatarCrop-area-bottom-left {
left: 0;
bottom: 0;
}
}
}
}
</style>
@@ -0,0 +1,58 @@
<template>
<view class="tm-avatarGroup">
<view :class="[`pl-${posX}`]">
<slot></slot>
</view>
</view>
</template>
<script>
export default {
name: 'tm-avatarGroup',
props:{
posX:{
type:Number,
default:30
}
},
data() {
return {
};
},
mounted() {
this.$nextTick(function(){
this.inits();
})
},
updated(){
this.$nextTick(function () {
this.inits();
})
},
methods: {
inits() {
let t = this;
let ch = [];
// #ifndef H5
ch = this.$children;
// #endif
// #ifdef H5
ch = this.$children[0].$children[0].$children;
// #endif
ch.forEach((item, index) => {
if (item.$options.name === 'tm-avatar') {
item.setConfigStyle({
'margin-left':`-${t.posX}rpx`
})
}
});
}
}
};
</script>
<style lang="scss"></style>
@@ -0,0 +1,147 @@
<!-- 徽章 -->
<template>
<view class="tm--badges fulled">
<view @click.stop="onclick" v-if="icon" class="tm--badges--cm icons flex-center border-white-a-1" :class="[color]" :style="offses+`;width:${iconWidth}rpx;height:${iconWidth}rpx;`">
<view class="d-inline-block flex-center vertical-align-middle" style="transform: scale(0.7); line-height: 0;">
<tm-icons style="line-height: 0;" :size="iconSize" :color="iconColor" dense :name="icon"></tm-icons>
</view>
</view>
<view
@click="onclick"
v-if="!icon"
:style="offses"
class="tm--badges--cm d-inline-block px-6 "
:class="[color_tmeme, label != '' && !dot ? 'num shadow-red-10' : 'dot', 'flex-center']"
>
<text v-if="!dot" class="text-size-xs">{{ label }}</text>
</view>
</view>
</template>
<script>
/**
* 徽章、角标
* @property {String} label = [] 默认:'',当填入信息时即显示数字角标。
* @property {Number} icon-size = [] 默认:24,当为图标时,可以设置图标大小。
* @property {Number} icon-width = [] 默认:32,当为图标时,可以设置宽高。
* @property {String} color = [] 默认:red,主题背景色
* @property {String} icon-color = [] 默认:white,图标主题背景色
* @property {Boolean|String} dot = [] 默认:true,使用点。优先级高于label数字展示。
* @property {String} icon = [] 默认:'',使用图标作为显示角标。
* @property {Array} offset = [] 默认:[0,0],位置,x,y偏移量。
* @property {Funciton} click 点击角标时触发。
* @example <tm-badges />
*/
import tmIcons from '@/tm-vuetify/components/tm-icons/tm-icons.vue';
export default {
components: { tmIcons },
name: 'tm-badges',
props: {
label: {
type: String | Number,
default: 0
},
// 使用点。优先级高于label数字展示。
dot: {
type: Boolean | String,
default: true
},
// 使用图标展示
icon: {
type: String,
default: ''
},
iconSize: {
type: Number|String,
default: 24
},
iconWidth:{
type: Number|String,
default: 32
},
// 主题色名称
color: {
type: String,
default: 'red'
},
// 图标主题色名称
iconColor: {
type: String,
default: 'white'
},
// 位置[0,0]
offset: {
type: Array,
default: () => {
return [0, 0];
}
},
// 跟随主题色的改变而改变。
fllowTheme: {
type: Boolean | String,
default: false
}
},
computed: {
offses: function() {
let p = uni.$tm.objToString({
transform: `translateX(${this.offset[0]}px) translateY(${this.offset[1]}px)`
});
return p;
},
color_tmeme: function() {
if (this.$tm.vx.state().tmVuetify.color !== null && this.$tm.vx.state().tmVuetify.color && this.fllowTheme) {
return this.$tm.vx.state().tmVuetify.color;
}
return this.color;
}
},
data() {
return {};
},
methods: {
onclick(e) {
this.$emit('click', e);
}
}
};
</script>
<style lang="scss" scoped>
.tm--badges {
position: relative;
// pointer-events: none;
display: block;
.tm--badges--cm {
position: absolute;
right: 0;
top: 0;
width: 100%;
&.num {
width: auto;
min-width: 26rpx;
height: 35rpx;
color: #fff;
border-radius: 50rpx;
font-size: 22upx;
line-height: 35upx;
text-align: center;
}
&.icons {
@extend .num;
}
&.dot {
width: 16upx;
height: 16upx;
min-width: 0 !important;
color: #fff;
padding: 0;
border-radius: 50%;
}
}
}
</style>
@@ -0,0 +1,258 @@
<!-- 横幅消息提醒故名思义即悬浮在顶部的消息条只有点关闭才会消失一般用来重要的中断提醒 -->
<!-- 和tm-alerts有点类似但作用不同 -->
<template>
<view :animation="animation" v-if="show" class="tm-banners text-size-n "
:class="[dense===true||dense=='true'?'dense-32':'',black_tmeme?'bk':'',color_tmeme,
outlined||outlined=='true'?'outlined':'','round-'+round,text?'text':'']"
:style="widths"
>
<view class=" flex-start" :class="[dense===true||dense=='true'?'py-16 px-32 ':'py-32 px-32']">
<view class="body-left ">
<tm-icons :black="black_tmeme" :color="(text||outlined)?color_tmeme:iconColor" :dense="true" :name="leftIcon" size="32"></tm-icons>
</view>
<view @click="onclick" @tap="onclick" class="body-center " :class="['text-overflow-'+col]">{{label}}</view>
<view @click.stop="closeview" v-if="close" class="body-right text-align-right">
<tm-icons :black="black_tmeme" :color="(text||outlined)?color_tmeme:iconColor" :dense="true" name="icon-times" size="28"></tm-icons>
</view>
</view>
</view>
</template>
<script>
/**
* 横幅消息提示框
* @property {String | Boolean} dense = [true|false] 默认:true,是否减小边距。
* @property {String | Boolean} outlined = [true|false] 默认:false,是否为边框样式
* @property {String} position = [top|bottom] 默认:top,位置
* @property {String | Boolean} text = [true|false] 默认:false,是否为文本模式,即减淡背景颜色。
* @property {String | Array} label = [true|false] 默认:"",当为数组时,自动变成轮播信息。
* @property {String | Boolean} black = [true|false] 默认:false,是否开启暗黑模式
* @property {String} color = [primary|blue|red|green|yellow|orange] 默认:primary,主题颜色名称
* @property {String} icon-color = [primary|blue|red|green|yellow|orange] 默认:primary,图标主题颜色名称
* @property {String} left-icon = [icon-lightbulb] 默认:"icon-lightbulb",左边图标名称
* @property {String | Boolean} ani = [true|false] 默认:false,是否禁用动画,
* @property {String | Boolean} close = [true|false] 默认:false 是否显示关闭图标,
* @property { Number|String} y = [] 默认:0 距离顶部的偏移量。
* @property { Number|String} width = [] 默认:'90%', 宽度,数字,或者百度比。数字的单位是upx
* @property { Number|String} col = [] 默认:1, 多行模式。默认为1行,可选最多3行。
* @property { Number|String} round = [] 默认:2 圆角。
* @property {Function} click 整个组件点击事件
* @example <tm-banners label="瞪村槈李厚厚霖无可奈何需"></tm-banners>
*/
import tmIcons from "@/tm-vuetify/components/tm-icons/tm-icons.vue"
export default {
components:{tmIcons},
name:'tm-banners',
model:{
prop:'value',
event:'input'
},
props: {
// 是否减小边距。
dense: {
type: String | Boolean,
default: true
},
// 是否为边框样式,
outlined: {
type: String | Boolean,
default: false
},
// 是否为文本模式,即减淡背景颜色。
text: {
type: String | Boolean,
default: false
},
// 当为数组时,自动变成轮播信息。
label:{
type:String | Array,
default:""
},
// 是否开启暗黑模式
black: {
type: String | Boolean,
default: null
},
// 主题颜色名称
color: {
type: String,
default: 'primary'
},
iconColor:{
type: String,
default: 'white'
},
// 左边图标名称
leftIcon: {
type: String,
default: 'icon-lightbulb'
},
//是否禁用动画,
ani: {
type: Boolean,
default: false
},
// 是否显示关闭图标
close: {
type: Boolean,
default: true
},
// 距离顶部的偏移量。
y: {
type: Number|String,
default: 16
},
// 距离顶部的偏移量。
position: {
type: String,
default: 'top' //top|bottom
},
// 宽度,数字,或者百度比。数字的单位是upx
width: {
type: Number|String,
default: '90%'
},
// 圆角
round: {
type: Number|String,
default: '3'
},
// 多行模式。默认为1行,可选最多3行。
col:{
type: Number|String,
default: 1
},
// 跟随主题色的改变而改变。
fllowTheme:{
type:Boolean|String,
default:true
},
value: {
type: Boolean|String,
default: true
},
},
data() {
return {
animation: null,
outid: 'alert-88',
};
},
computed:{
show:{
get:function(){
return this.value;
},
set:function(val){
this.$emit('input',val)
this.$emit('update:value',val)
},
},
black_tmeme: function() {
if (this.black !== null) return this.black;
return this.$tm.vx.state().tmVuetify.black;
},
color_tmeme:function(){
if(this.$tm.vx.state().tmVuetify.color!==null&&this.$tm.vx.state().tmVuetify.color && this.fllowTheme){
return this.$tm.vx.state().tmVuetify.color;
}
return this.color;
},
widths:function(){
let topoffset = 0 + uni.upx2px(parseInt(this.y));
if(isNaN(parseInt((this.width)))){
return this.$tm.objToString({
width:'100%',
left:'0px',
top:this.position=='bottom'?'inherit':`${topoffset}px`,
bottom:this.position=='top'?'inherit':`${topoffset}px`
});
}
if(String(this.width).indexOf('%')>-1||String(this.width).indexOf('vw')>-1){
let num = parseFloat(this.width.replace(/(\%|vw|)/g,''));
let blv = this.width.replace(/[\.|a-zA-Z0-9]/g,'');
return this.$tm.objToString({
width:this.width,
left:(100-num)/2 + blv,
top:this.position=='bottom'?'inherit':`${topoffset}px`,
bottom:this.position=='top'?'inherit':`${topoffset}px`
});
}
let sysinfo = uni.getSystemInfoSync();
let wid = uni.upx2px(parseFloat(this.width));
let lf = (sysinfo.windowWidth - wid) / 2;
return this.$tm.objToString({
width:uni.upx2px(parseFloat(this.width))+'px',
left:lf+'px',
top:this.position=='bottom'?'inherit':`${topoffset}px`,
bottom:this.position=='top'?'inherit':`${topoffset}px`
});
}
},
mounted() {
},
methods: {
onclick(e) {
this.$emit('click', e);
},
closeview(e) {
let t = this;
var animation = uni.createAnimation({
duration: 500,
timingFunction: 'ease'
});
this.animation = animation;
animation
.scale(0.7, 0.7)
.opacity(0)
.step();
this.outid = setTimeout(() => {
t.show = false;
t.animation=null;
}, 500);
}
}
};
//
</script>
<style lang="scss" scoped>
.tm-banners {
width: calc(100% - 32upx);
left: 32upx;
&.dense-32{
width: calc(100% - 64upx);
}
position: fixed;
z-index: 500;
// color: #fff;
.gs {
width: 50upx;
min-width: 70upx;
}
.body-left,
.body-right {
@extend .gs;
}
.body-center {
width: 100%;
}
}
</style>
@@ -0,0 +1,466 @@
<!-- 导航工具栏 -->
<template>
<view :style="{
width: position != 'static' ? wininfo.windowWidth - offsetLeft * 2 + 'px' : 'auto',
left: position != 'static' ? offsetLeft + 'px' : '0px',
top: position == 'top' ? top_px + 'px' : 'auto',
bottom: position == 'bottom' ? bottom_px + 'px' : 'auto'
}" class="tm-bottomnavigation " :class="[
black_tmeme ? 'grey-darken-5' : '',
black_tmeme ? 'bk' : '',
'round-' + round,
bgTheme,
position,
border === 'top' ? 'border-t-1' : '',
border === 'bottom' ? 'border-b-1' : '',
'sheetIDX'
]">
<view :style="{ background: bgColor }" class=" flex-between py-10">
<block v-for="(item, index) in listDate" :key="index">
<view :style="{
width: listLen + '%'
}" class="flex-center" :key="index" :class="[active_selecindex == index && ani == true ? ` ani ` : ``]">
<tm-button :key="index" titl height="100%" :width="btnwidth"
:iconSize="item['iconSize'] ? item['iconSize'] : 38"
:fontSize="item['fontSize'] ? item['fontSize'] : 20" :vertical="vertical" :icon="active_selecindex==index?item.icon:(item['noIcon']||item.icon)"
:label="item.value" block :theme="active_selecindex == index ? iconColor : iconColorGrey"
:black="black_tmeme" @click.stop="onclick($event, index)" item-class="noGutter "
:font-color="active_selecindex == index ? iconColor : iconColorGrey">
<template v-slot:icon="{ data }">
<!-- #ifdef MP -->
<text v-if="false">{{offsetLeft}}</text>
<!-- #endif -->
<view style="height: 52rpx;">
<view v-if="!item['custom']" >
<view v-if="item.showDot" class="relative fulled" style="z-index: 10;">
<tm-badges :color="item.dot['color']||iconColor" v-if="!item.dot['dot']&&!item.dot['icon']" :label="item.dot.num"
:offset="[10, 0]" :dot="item.dot.dot"></tm-badges>
<tm-badges :color="item.dot['color']||iconColor" v-if="item.dot['dot']&&!item.dot['icon']"></tm-badges>
<tm-badges :color="item.dot['color']||iconColor" :offset="[10,0]" :icon="item.dot['icon']" v-if="item.dot['icon']"></tm-badges>
</view>
<tm-icons :black="black_tmeme"
:color="active_selecindex == index ? iconColor : iconColorGrey" :size="data.size"
:name="data.icon"></tm-icons>
</view>
<view v-if="item['custom'] === true"
class="tm-bottomnavigation-custom absolute flex-center" :style="{
width: '100%',
height: '100%',
minHeight: '100%',
top: '-50%',
left: '0',
flexShrink: 0
}">
<view
:class="[item['customColor'] ? item['customColor'] : (bgColor?'':'red'), black_tmeme ? 'bk' : '']"
:style="{width: '90rpx',height: '90rpx',background:bgColor?bgColor:''}" class="rounded flex-center">
<tm-icons :black="black_tmeme" :color="item['customFontColor'] || iconColorGrey"
:size="data.size" :name="data.icon"></tm-icons>
</view>
</view>
</view>
</template>
<template v-slot:default="{data}">
<text :class="[active_selecindex == index ? 'ani' : '']">{{ data }}</text>
</template>
</tm-button>
</view>
</block>
</view>
<!-- 安全区域的高度 -->
<view v-if="safe" :style="{ height: safeBottomeHeight + 'px',background: bgColor }"></view>
</view>
<!--
list:[{...}],基础属性为{icon:"图标或者图片",value:"标题"}
全部属性为除了上述基本两个属性其它全为可选
{
icon: 'icon-user-fill',
noIcon:'',//未选中时的图标不提供默认使用相同的图标icon
value: '个人中心',
iconSize: 38,
fontSize: 20,
showDot:false,//是否显示角标
dot:{
dot:false,//是否显示点如果是点则不显示Num
num:"",//是否显示数字
ico:"",//是否显示图标优先级高于dot,num.
},
path: ''
}
-->
</template>
<script>
/**
* 底部导航工具条
* @property {Array} list = [] 默认:[],基本属性:{icon:"图标或者图片",value:"标题"}
* @property {String} bg-theme = [] 默认:"white",背景颜色主题名称
* @property {String | Boolean} black = [true|false] 默认:null,暗黑模式。
* @property {String} bg-color = [] 默认:'',自定义背景颜色。
* @property {String} icon-color = [] 默认:使用主题色,项目默认激活色。
* @property {String} icon-color-grey = [] 默认:使用主题色,项目失去焦点时的颜色。
* @property {String} position = [bottom|top|static] 默认:bottom,可选位置:bottom|top|static
* @property {Number|String} top = [] 默认:0,距离顶部的距离:只有在position=='top'使用
* @property {Number|String} bottom = [] 默认:0,距离底部的距离:只有在position=='bottom'使用
* @property {String} border = [top|bottom] 默认:top,显示上边线还是下边线。可选top / bottom
* @property {String|Boolean} vertical = [true|false] 默认:true,文字方向:默认是竖向。否则为横向。
* @property {String|Number} offset-left = [] 默认:0,偏移量。即离左边的间距。如果提供,自动居中出现两边间隙。
* @property {String|Boolean} safe = [true|false] 默认:true,// 是否开启底部安全区域。
* @property {String|Boolean} auto-selected = [true|false] 默认:true,// 是否开启自动匹配页面选中底部按钮。
* @property {Function} change 切换按钮时触发。
*
*/
import tmButton from "@/tm-vuetify/components/tm-button/tm-button.vue"
import tmIcons from "@/tm-vuetify/components/tm-icons/tm-icons.vue"
import tmBadges from "@/tm-vuetify/components/tm-badges/tm-badges.vue"
export default {
components: {
tmButton,
tmIcons,
tmBadges
},
name: 'tm-bottomnavigation',
props: {
list: {
Array,
default: () => {
return [];
}
},
// 背景颜色主题名称
bgTheme: {
type: String,
default: 'white'
},
// 是否启用暗黑主题。
black: {
type: String | Boolean,
default: null
},
// 背景颜色,自定义。
bgColor: {
type: String,
default: ''
},
// 自定义项目文字默认激活色。
iconColor: {
type: String,
default: 'primary'
},
// 自定义项目文字默认失去焦点色。
iconColorGrey: {
type: String,
default: 'grey-lighten-1'
},
// 可选bootom / top / static
position: {
type: String,
default: 'bottom'
},
// 距离顶部的距离。默认是0,只有在position=='top'使用
top: {
type: Number | String,
default: 0
},
// 距离顶部的距离。默认是0,只有在position=='bottom'使用
bottom: {
type: Number | String,
default: 0
},
// 显示上边线还是下边线。可选top / bottom
border: {
type: String,
default: 'top'
},
// 文字方向:默认是竖向。否则为横向。
vertical: {
type: String | Boolean,
default: true
},
// 只支持圆角主题。如round-5
round: {
type: String | Number,
default: 0
},
// 偏移量。即离左边的间距。如果提供,整个navbar的宽度会是100% - offsetLeft*2。
offsetLeft: {
type: String | Number,
default: 0
},
// 是否开启底部安全区域。
safe: {
type: String | Boolean,
default: true
},
// 是否开启点按动画,默认开启。
ani: {
type: String | Boolean,
default: true
},
// 是否自动匹配页面选中底部按钮?
autoSelected: {
type: String | Boolean,
default: true
},
activeIndex:{
type:Number,
default:0
}
},
computed: {
black_tmeme: function() {
if (this.black !== null) return this.black;
return this.$tm.vx.state().tmVuetify.black;
},
top_px: function() {
return this.top;
},
bottom_px: function() {
return this.bottom;
},
wininfo: function() {
return uni.getSystemInfoSync();
},
listLen: function() {
if (this.listDate.length == 0) return 1;
return 100 / this.listDate.length;
},
active_selecindex:function(){
if(!this.autoSelected) return this.slectedIndx;
return this.$tm.vx.state().tmVuetify.tmVueTifly_pagesIndex;
}
},
created() {
uni.hideTabBar({animation:false})
},
data() {
return {
slectedIndx: -1, //默认激活的值。
btnwidth: 0,
safeBottomeHeight: 0,
listDate: [],
pages:[]
};
},
watch:{
list:{
deep:true,
handler(){
let t = this;
this.$nextTick(function() {
let qr = uni.createSelectorQuery().in(this);
qr.select('.sheetIDX')
.boundingClientRect()
.exec(e => {
t.btnwidth = e[0].width / 5 + 'px';
t.listItem();
});
});
}
},
activeIndex:function(){
if(!this.autoSelected) this.slectedIndx = this.activeIndex;
}
},
mounted() {
if(!this.autoSelected) this.slectedIndx = this.activeIndex;
setTimeout(function(){
uni.hideTabBar({animation:false})
},350)
let t = this;
this.$nextTick(function() {
uni.hideTabBar({animation:false})
let qr = uni.createSelectorQuery().in(this);
qr.select('.sheetIDX')
.boundingClientRect()
.exec(e => {
t.btnwidth = e[0].width / 5 + 'px';
t.listItem();
});
});
},
methods: {
autoxz(){
let sy = uni.getSystemInfoSync();
// #ifdef MP
this.safeBottomeHeight = sy.screenHeight - sy.safeArea.bottom;
// #endif
// #ifdef H5
this.safeBottomeHeight = sy.windowHeight - sy.safeArea.height;
// #endif
// #ifdef APP
this.safeBottomeHeight = Math.abs(sy.safeArea.bottom - sy.safeArea.height);
// #endif
if(!this.autoSelected){
return;
}
let pageRoute = this.$tm.vx.state().tmVuetify.tmVueTifly_pages;
let index = this.listDate.findIndex((el,index)=>{
let url = el?.path||"";
url = url.split('?')[0]||"";
return url == pageRoute
})
if(index>-1){
this.slectedIndx = index;
this.$tm.vx.commit('setPageNowIndex',index)
this.$emit('change', {
index: index,
item: this.listDate[index]
});
}
},
listItem() {
let mod = {
icon: 'icon-user-fill',
value: '个人中心',
iconSize: 38,
fontSize: 20,
showDot: false, //是否显示角标。
dot: {
dot: false, //是否显示点。如果是点,则不显示Num
num: '', //是否显示数字。
ico: '' //是否显示图标,优先级高于dot,num.
},
path: '',
openType:'switchTab'
};
this.$nextTick(function() {
let tm = [];
let ls = this.$tm.deepClone(this.list);
ls.forEach((item, index) => {
tm.push({
...mod,
...item
});
});
this.listDate = tm;
this.autoxz();
});
},
onclick(e, index) {
let t = this;
this.slectedIndx = index;
this.$tm.vx.commit('setPageNowIndex',index)
let item = this.listDate[index];
this.$emit('change', {
index: index,
item: item
});
if (item?.path) {
let oldPath = item['path']||"";
oldPath = oldPath.split("?")[0]||"";
let pages = getCurrentPages();
let url = pages[pages.length-1].route;
if(url[0]!='/') url = '/' + url;
url = url.split('?')[0]||"";
if(url==oldPath) return;
// #ifdef MP || APP
try{
uni.vibrateShort()
}catch(e){
// ...
}
// #endif
switch (item['openType']) {
case 'switchTab':
uni.switchTab({
url: item.path,
fail: (e) => {
console.log(e);
}
})
break;
case 'redirect':
uni.redirectTo({
url: item.path
})
break;
case 'reLaunch':
uni.reLaunch({
url: item.path
})
break;
case 'navigateBack':
uni.navigateBack({
url: item.path
})
break;
default:
uni.navigateTo({
url: item.path
})
break;
}
}
}
}
};
</script>
<style>
page,
body {
padding-bottom: 167rpx;
}
</style>
<style lang="scss" scoped>
.tm-bottomnavigation {
// animation: scalse 0.4s linear;
&.bottom {
position: fixed;
bottom: 0;
left: 0;
z-index: 450;
}
&.top {
position: fixed;
z-index: 450;
top: 0;
left: 0;
}
}
.ani {
animation: scalse 0.4s linear;
}
@keyframes scalse {
0% {
transform: scale(0.9);
}
50% {
transform: scale(1.1);
}
100% {
transform: scale(1);
}
}
</style>
@@ -0,0 +1,702 @@
<template>
<view class="tm-button " :class="[block ? 'd-block' : 'd-inline-block ']">
<view
class="flex-center tm-button-btn fulled-height"
:class="[block ? '' : customDense_puted ? '' : 'ma-10', block ? 'd-block' : 'd-inline-block', black_tmeme ? 'bk' : '', customStyleTm]"
>
<!-- @getuserinfo="getuserinfo" -->
<button
@contact="contact"
@error="error"
@getphonenumber="getphonenumber"
@launchapp="launchapp"
@opensetting="opensetting"
@click="onclick"
@longpress="$emit('longpress', $event)"
@touchcancel="$emit('touchcancel', $event)"
@touchend="$emit('touchend', $event)"
@touchstart="$emit('touchstart', $event)"
:disabled="disabled"
:loading="loading"
:open-type="openType"
:hover-start-time="hoverStartTime"
:hover-stay-time="hoverStaySime"
:hover-stop-propagation="hoverStopPropagation"
:send-message-img="sendMessageImg"
:send-message-path="sendMessagePath"
:send-message-title="sendMessageTitle"
:show-message-card="showMessageCard"
:class="[
showValue ? 'showValue' : '',
titl ? 'text-' + color_tmeme : '',
vertical ? 'flex-col flex-center' : 'flex-center',
black_tmeme ? 'bk' : '',
disabled ? color_tmeme + ' grey-lighten-2' : color_tmeme,
block ? '' : sizes,
text || titl ? '' : bgcolor ? 'shadow-' + shadow : 'shadow-' + shadowthemeName + '-' + shadow,
flat ? 'flat' : '',
text ? 'text ' : '',
plan ? 'plan outlined' : '',
titl ? 'titl' : '',
fab?'rounded':'round-' + round,
customClassTm_puted
]"
:style="{
background: bgcolor ? bgcolor + ' !important' : 'default',
width: widths != 0 && widths != 'inherit' ? widths : 'inherit',
height: heights,
lineHeight: 'inherit'
}"
>
<slot name="icon" :data="{ icon: icon, size: icon_size.upx, fab: fab }">
<block v-if="vtype == true">
<text
v-if="!fab && icon"
:class="[`${prefx_computed} ${icon}`, fontColor ? `text-${colors.color}` : '', black_tmeme ? 'opacity-6' : '', 'px-12']"
:style="{
fontSize: `${icon_size.px}px`,
lineHeight:'normal'
}"
></text>
<text
v-if="fab && icon && !loading && !titl"
:class="[`${prefx_computed} ${icon}`, fontColor ? `text-${colors.color}` : '', black_tmeme ? 'opacity-6' : '']"
:style="{
fontSize: `${icon_size.px}px`,
lineHeight:'normal'
}"
></text>
<text
v-if="fab && icon && !loading && titl"
:class="[`${prefx_computed} ${icon}`, fontColor ? `text-${color_tmeme}` : '', black_tmeme ? 'opacity-6' : '']"
:style="{
fontSize: `${icon_size.px}px`,
lineHeight:'normal'
}"
></text>
</block>
<block v-if="vtype == false"><tm-icons :size="icon_size.upx" :name="icon"></tm-icons></block>
</slot>
<view v-if="!fab || showValue" class="d-inline-block tm-button-label flex-shrink" :style="{ fontSize: font_size }" :class="[fontColor ? `text-${colors.color}` : '']">
<slot name="default" :data="label">{{ label }}</slot>
</view>
</button>
</view>
</view>
</template>
<script>
/**
* 按钮
* @description 注意,按钮功能同原生按钮功能,所有属性都有。
* @property {Function} contact
* @property {Function} error
* @property {Function} getphonenumber
* @property {Function} getuserinfo
* @property {Function} opensetting
* @property {Function} click
* @property {Boolean} disabled = [true|false] 是否禁用
* @property {Boolean} loading = [true|false] 是否加载中
* @property {String} open-type = [contact|getPhoneNumber|getUserInfo|launchapp|share|openSetting] 同原生btn相同,//当等于getUserProfile时,自动获取微信授权。
* @property {Boolean} block = [true|false] 是否独占一行
* @property {Boolean} show-value = [true|false] fab模式是隐藏文字的。如果这个设置为true,不管在什么情况下都会显示文字。
* @property {String} url = [] 默认"",如果提供将会跳转连接。
* @property {String} size = [xs|s|m|n|l|g] 默认"n",按钮尺寸大小。
* @property {Number|String} font-size = [] 默认 0,自定义文字大小,默认使用主题size的大小。
* @property {Number|String} icon-size = [] upx单位默认 0,自定义图标大小,默认使用主题size的大小。
* @property {String} icon = [] 单位默认 "",自定义按钮图标。
* @property {String} item-class = [] 默认 "",按钮自定样式类。
* @property {String|Number} shadow = [] 默认 5,按钮投影
* @property {String} font-color = [] 默认主题颜色,自定文字颜色
* @property {String} bgcolor = [] 默认主题颜色,自定义-背景颜色
* @property {String} theme = [] 默认 primary,主题颜色
* @property {Boolean|String} black = [true|false] 默认 false, 暗黑模式
* @property {Boolean|String} flat = [true|false] 默认 false, 去除所有投影圆角效果,扁平模式。
* @property {Boolean|String} text = [true|false] 默认 false, 文本模式。背景减淡。文字使用主题色。
* @property {Boolean|String} plan = [true|false] 默认 false, 镂空。
* @property {Boolean|String} fab = [true|false] 默认 false, 圆模式。
* @property {Boolean|String} dense = [true|false] 默认 false, 是否去除外间隙。
* @property {Boolean|String} titl = [true|false] 默认 false, 无背景,无边框,只保留按钮区域和文字颜色或者图标颜色。
* @property {String} label = [] 默认 "", 按钮文字也可以使用插槽模式直接写在组件内部。
* @property {String|Number} width = [] 默认 "", 按钮宽, 单位px ,可以是百分比 如果不设置block为true时,此项设置无效。
* @property {String|Number} height = [] 默认 "", 按钮高, 单位px ,可以是百分比如果不设置block为true时,此项设置无效。
* @property {String|Number} round = [] 默认 2, 圆角
* @property {String|Number} navtie-type = [form] 默认 :'', 可选值form,提供此属性为form时,事件会被表单接替,会触发表单提交事件。
* @property {String} userProfileError = ['auto'] 默认:'auto' 仅open-type='getUserProfile'时有效,当微信授权失败提示的信息,默认为auto,如果非auto将显示自定义错误提示。如果为'',将不显示错误提示。
* @example <tm-button>确定</tm-button>
*/
import tmIcons from '@/tm-vuetify/components/tm-icons/tm-icons.vue';
export default {
components: { tmIcons },
name: 'tm-button',
props: {
hoverStartTime:20,
hoverStaySime:70,
appParameter:String,
hoverStopPropagation:{
type:Boolean,
default:false
},
sessionFrom:String,
sendMessageTitle:String,
sendMessagePath:String,
sendMessageImg:String,
showMessageCard:String,
navtieType: {
type: String,
default: '' //form
},
// 内联还是独占一行的块状100%,默认内联样式。
block: {
type: String | Boolean,
default: false
},
// fab模式是隐藏文字的。如果这个设置为true,不管在什么情况下都会显示文字。
showValue: {
type: String | Boolean,
default: false
},
disabled: {
type: String | Boolean,
default: false
},
loading: {
type: String | Boolean,
default: false
},
url: {
type: String,
default: ''
},
// xs,s,n,l,g
size: {
type: String,
default: 'n'
},
// 文字大小。
fontSize: {
type: Number | String,
default: 0
},
// 图标大小。
iconSize: {
type: Number | String,
default: 0
},
// icon-cog-fill
icon: {
type: String,
default: ''
},
// 按钮自定样式类。
itemClass: {
type: String,
default: ''
},
// 同原生btn相同。contact|getPhoneNumber|getUserInfo|launchapp|share|openSetting
//当等于getUserProfile时,自动获取微信授权。
openType: {
type: String,
default: ''
},
shadow: {
type: Number | String,
default: 4
},
vertical: {
type: Boolean | String,
default: false
},
// 自定义-文字颜色。
fontColor: {
type: String,
default: ''
},
// 自定义-背景颜色
bgcolor: {
type: String | Array,
default: ''
},
// 主题颜色
theme: {
type: String,
default: 'primary'
},
//优先级最高: 优先级1
black: {
type: Boolean | String,
default: null
},
// 优先级高:优先级2
flat: {
type: Boolean | String,
default: null
},
// 优先级高:文本模式。背景减淡。文字使用主题色。
text: {
type: Boolean | String,
default: false
},
// 优先级高:镂空。
plan: {
type: Boolean | String,
default: false
},
// 圆模式。
fab: {
type: Boolean | String,
default: false
},
// 无背景,无边框,只保留按钮区域和文字颜色或者图标颜色。
titl: {
type: Boolean | String,
default: false
},
label: {
type: String,
default: ''
},
// 如果不设置block为true时,此项设置无效。
width: {
type: String | Number,
default: NaN
},
// 如果不设置block为true时,此项设置无效。
height: {
type: String | Number,
default: NaN
},
// 圆角
round: {
type: String | Number,
default: 3
},
dense: {
type: Boolean | String,
default: false
},
// 跟随主题色的改变而改变。
fllowTheme: {
type: Boolean | String,
default: true
},
//当微信授权失败提示的信息,默认为auto,如果非auto将显示自定义错误提示。如果为'',将不显示错误提示。
userProfileError:{
type:String,
default:'auto'
}
},
created() {
this.customClassTm_puted = this.itemClass;
this.customDense_puted = this.dense;
this.color_tmeme = this.theme;
},
watch: {
theme: function() {
this.color_tmeme = this.theme;
}
},
computed: {
prefx_computed() {
let prefix = this.icon.split('-')[0];
if (prefix == 'icon') return 'iconfont';
if (prefix == 'mdi') return 'mdi';
return prefix;
},
customDense_puted: {
get: function() {
return this.customDense;
},
set: function(val) {
this.customDense = val;
}
},
customClassTm_puted: {
get: function() {
return this.customClassTm;
},
set: function(val) {
this.customClassTm = val;
}
},
vtype: function() {
if (this.icon[0] == '.' || this.icon[0] == '/' || this.icon.substring(0, 4) == 'http' || this.icon.substring(0, 5) == 'https' || this.icon.substring(0, 3) == 'ftp') {
return false;
}
return true;
},
black_tmeme: function() {
if (this.black !== null) return this.black;
return this.$tm.vx.state().tmVuetify.black;
},
color_tmeme: {
get: function() {
if (this.$tm.vx.state().tmVuetify.color !== null && this.$tm.vx.state().tmVuetify.color && this.fllowTheme) {
return this.$tm.vx.state().tmVuetify.color;
}
return this.color_tmeme_computed;
},
set: function(val) {
this.color_tmeme_computed = val;
}
},
// 投影的颜色名字。
shadowthemeName: function() {
if (!this.theme) return 'none';
return this.color_tmeme;
},
widths: {
get: function() {
let w;
if (typeof this.width === 'undefined') {
return 'auto';
}
let item = this.$TestUnit(this.width);
if (item) {
if (item.type == 'string') {
w = item.value;
}
if (item.type == 'number') {
w = item.value + 'px';
}
}
return w;
}
},
heights: {
get: function() {
let item = this.$TestUnit(this.height);
if (item) {
if (item.type == 'string') {
return item.value;
}
if (item.type == 'number') {
return item.value + 'px';
}
}
return 'auto';
}
},
sizes: function() {
if (!isNaN(this.width) || !isNaN(this.height)) return;
if (this.size == 'xs') {
return this.fab ? 'fabxs text-size-xs rounded' : 'wxs round-1 text-size-xs';
} else if (this.size == 's') {
return this.fab ? 'fabs text-size-xs rounded' : 'ws round-1 text-size-s';
} else if (this.size == 'm') {
return this.fab ? 'fabm text-size-xs rounded' : 'wm round-1 text-size-n';
} else if (this.size == 'n') {
return this.fab ? 'fabn text-size-xs rounded' : 'wn round-1 text-size-n';
} else if (this.size == 'l') {
return this.fab ? 'fabl text-size-xs rounded' : 'wl round-2 text-size-g';
} else if (this.size == 'g') {
return this.fab ? 'fabg text-size-xs rounded' : 'wg round-3 text-size-g';
}
},
font_size: function() {
if (this.fontSize > 0) {
return this.fontSize + 'rpx';
}
if (this.size == 'xs') {
return uni.upx2px(20) + 'px';
} else if (this.size == 's') {
return uni.upx2px(22) + 'px';
} else if (this.size == 'm') {
return uni.upx2px(24) + 'px';
} else if (this.size == 'n') {
return uni.upx2px(28) + 'px';
} else if (this.size == 'l') {
return uni.upx2px(32) + 'px';
} else if (this.size == 'g') {
return uni.upx2px(36) + 'px';
}
},
icon_size: function() {
if (this.iconSize) {
return { px: uni.upx2px(this.iconSize), upx: this.iconSize };
}
if (this.size == 'xs') {
return { px: uni.upx2px(20), upx: 20 };
} else if (this.size == 's') {
return { px: uni.upx2px(22), upx: 22 };
} else if (this.size == 'm') {
return { px: uni.upx2px(24), upx: 24 };
} else if (this.size == 'n') {
return { px: uni.upx2px(28), upx: 28 };
} else if (this.size == 'l') {
return { px: uni.upx2px(32), upx: 32 };
} else if (this.size == 'g') {
return { px: uni.upx2px(36), upx: 36 };
}
},
defaultfontsize: function() {
if (this.iconSize) {
return this.iconSize;
}
if (this.size == 'xs') {
return uni.upx2px(20);
} else if (this.size == 's') {
return uni.upx2px(22);
} else if (this.size == 'm') {
return uni.upx2px(24);
} else if (this.size == 'n') {
return uni.upx2px(28);
} else if (this.size == 'l') {
return uni.upx2px(32);
} else if (this.size == 'g') {
return uni.upx2px(36);
}
},
colors: function() {
let color = this.$TestColor(this.fontColor);
return color;
}
},
data() {
return {
customClassTm: '',
customDense: false,
customStyleTm: '',
color_tmeme_computed: ''
};
},
methods: {
setConfigStyle(val) {
this.customStyleTm = val;
},
onclick() {
let t = this;
this.$emit('click', ...arguments);
if(this.openType=='getUserInfo' || this.openType == 'getUserProfile'){
// #ifdef MP-WEIXIN
uni.getUserProfile({
desc: '需要获取用户信息',
lang: 'zh',
complete: function (userProfile) {
if(userProfile.errMsg !='getUserProfile:ok'){
if(t.userProfileError==''||t.userProfileError=='true') return;
if(t.userProfileError!='auto'){
uni.showToast({
title:t.userProfileError,icon:'error',mask:true
})
return;
}
uni.showToast({
title:userProfile.errMsg,icon:'error',mask:true
})
return;
}
t.$emit('getUserInfo', userProfile);
t.$emit('getUserProfile', userProfile);
},
fail: (error) => {
if(t.userProfileError==''||t.userProfileError=='true') return;
if(t.userProfileError!='auto'){
uni.showToast({
title:t.userProfileError,icon:'error',mask:true
})
return;
}
uni.showToast({
title:error
})
}
});
// #endif
}
if (this.url !== '' && typeof this.url === 'string') {
let url = this.url;
if (url[0] !== '/') url = '/' + url;
uni.navigateTo({
url: url
});
}
},
contact() {
this.$emit('contact', ...arguments);
},
error() {
this.$emit('error', ...arguments);
},
getphonenumber() {
this.$emit('getphonenumber', ...arguments);
},
getuserinfo() {
this.$emit('getuserinfo', ...arguments);
},
launchapp() {
this.$emit('launchapp', ...arguments);
},
opensetting() {
this.$emit('opensetting', ...arguments);
}
}
};
</script>
<style lang="scss" scoped>
.tm-button {
vertical-align: middle;
.tm-button-btn button {
background: none;
border: none;
width: 100%;
height: auto;
line-height: 88upx;
height: 88upx;
vertical-align: middle;
// #ifdef H5
transition: all 0.3s;
// #endif
.tm-button-label {
vertical-align: middle;
}
&::after {
border: none;
}
&.fabxs {
width: 48upx;
height: 48upx !important;
line-height: 48upx;
text-align: center;
padding: 0 !important;
}
&.fabs {
width: 64upx;
height: 64upx !important;
line-height: 64upx;
text-align: center;
padding: 0 !important;
}
&.fabm {
width: 90upx;
height: 90upx !important;
line-height: 90upx;
text-align: center;
padding: 0 !important;
}
&.fabn {
width: 110upx;
height: 110upx !important;
line-height: 110upx;
text-align: center;
padding: 0 !important;
}
&.fabl {
width: 140upx;
height: 140upx !important;
line-height: 140upx;
text-align: center;
padding: 0 !important;
}
&.fabg {
width: 180upx;
height: 180upx !important;
line-height: 180upx;
text-align: center;
padding: 0 !important;
}
&.wxs {
min-width: 64upx;
height: 36upx !important;
// line-height: 24upx;
text-align: center;
// padding: 0 !important;
padding: 0 12upx;
}
&.ws {
min-width: 90upx;
height: 48upx !important;
// line-height: 24upx;
text-align: center;
padding: 0 16upx;
}
&.wm {
min-width: 140upx;
height: 64upx !important;
// line-height: 88upx;
text-align: center;
padding: 0 24upx;
}
&.wn {
min-width: 240upx;
height: 72upx !important;
// line-height: 88upx;
text-align: center;
// padding: 0 !important;
padding: 0 32upx;
}
&.wl {
min-width: 280upx;
height: 72upx !important;
// line-height: 88upx;
text-align: center;
padding: 0 32upx;
}
&.wg {
min-width: 400upx;
height: 76upx !important;
// line-height: 88upx;
text-align: center;
padding: 0 32upx;
}
&.plan {
box-shadow: none !important;
// background: transparent !important;
border-width: 1px !important;
border-style: solid;
}
&.titl {
box-shadow: none !important;
background: transparent !important;
border: none;
}
&:active,
&:focus {
opacity: 0.64;
}
&.showValue {
line-height: inherit !important;
}
&.noGutter {
min-width: 0 !important;
margin: 0 !important;
padding: 0 !important;
overflow: unset !important;
}
}
&.d-block {
button {
// padding: 30upx 0;
font-size: 32upx;
&.plan {
box-shadow: none !important;
background: transparent !important;
border-width: 1px !important;
border-style: solid;
}
}
&.showValue {
line-height: inherit !important;
}
}
&.d-inline-block {
vertical-align: middle;
}
}
</style>
@@ -0,0 +1,959 @@
<template>
<view class="tm-calendar" :class="[inline?'d-inline-block':'d-block']">
<view @click.stop.prevent="openPoup"><slot></slot></view>
<tm-poup @change="toogle" clss-style="tm-calendar-wk" :black="black_tmeme" ref="pop" height="1040" v-model="show">
<view class="tm-calendar-wk">
<view class="shadow-10">
<view class="tm-calendar-title pa-32 pb-16">
<view class="text-size-g text-align-center text-grey">{{title}}</view>
<view class="tm-calendar-close rounded flex-center" :class="black_tmeme?'grey-darken-3':'grey-lighten-3'">
<tm-icons @click="$refs.pop.close()" name="icon-times" size="24" :color="black_tmeme?'white':'grey'"></tm-icons>
</view>
</view>
<view class="pa-32 flex-center" style="width: 96%;margin: auto;">
<view>
<tm-icons dense @click="preYear" name="icon-angle-double-left" color="grey-lighten-1"></tm-icons>
<text class="px-24"></text>
<tm-icons dense @click="preMonth" name="icon-angle-left" color="grey-lighten-1"></tm-icons>
<!-- <text class="px-8 "></text> -->
</view>
<view class="text-size-n text-weight-b px-40">{{titleValue}}</view>
<view>
<!-- <text class="px-8"></text> -->
<tm-icons dense @click="nextMonth" name="icon-angle-right" color="grey-lighten-1"></tm-icons>
<text class="px-24"></text>
<tm-icons dense @click="nextYear" name="icon-angle-double-right" color="grey-lighten-1"></tm-icons>
</view>
</view>
</view>
</view>
<view class="tm-calendar-body">
<view class="tm-calendar-bg flex-center">
<text class="text tm-calendar-bg-center-text" :class="[black_tmeme?' opacity-5 ':'']">{{centerValue}}</text>
</view>
<view class="tm-calendar-content ">
<view style="height: 32upx;"></view>
<tm-row>
<!-- #ifdef MP-->
<tm-col color="" align="middle" width="14.28%" v-for="(item,index) in ['一','二','三','四','五','六','日']"
:key="index"><text class="text-size-s py-16">{{item}}</text>
</tm-col>
<!-- #endif -->
<!-- #ifdef H5 || APP-VUE || APP-PLUS -->
<tm-col color="" align="middle" width="14.28%" v-for="(item,index) in ['一','二','三','四','五','六','日']"
:key="index+'_acv'"><text class="text-size-s py-16">{{item}}</text>
</tm-col>
<!-- #endif -->
<tm-col :round="(item.start||item.end||item.checked)?4:0" @click="day_danxuanclick(item,index)"
:color="item.beginEnd?(item.checked===true||item.start||item.end?color_tmeme+(black_tmeme?' bk ':''):(item.guocheng?color_tmeme+' text opacity-7 '+(black_tmeme?'bk':''):'')):''"
:custom-class="isSelectedDateClass(item)"
align="middle" width="14.28%" v-for="(item,index) in nowData"
:key="index">
<view class="tm-calendar-col flex-center flex-col py-4" :class="[black_tmeme&&!item.beginEnd?' opacity-2 ':'']">
<text class="text-size-n "
:class="[
!item.nowMonth&&!item.guocheng&&!item.checked&&!item.start&&!item.end?(black_tmeme?'text-grey-darken-3':'text-grey-lighten-1'):'',
item.checked||item.start||item.end?'text-white':'',
item.guocheng?' text text-'+color_tmeme:'',
!item.beginEnd?'text-grey-lighten-3':''
]"
>{{item.day}}</text>
<!--
!item.nowMonth&&!item.guocheng&&!item.checked&&!item.start&&!item.end?(black?'text-grey-darken-3':'text-grey-lighten-1'):'',
item.checked||item.start||item.end?'text-white':(item.guocheng?'':'text-grey-lighten-1'),
item.guocheng?'text-'+color:'',
!item.beginEnd?'text-grey-lighten-3':'',
-->
<view class="text-size-xs text_bl"
>
<block v-if="item.start">
开始
</block>
<block v-if="item.end">
结束
</block>
<block v-if="!item.start&&!item.end">
{{item.text?item.text:(showNongli?item.nongli.day:'')}}
</block>
</view>
</view>
</tm-col>
</tm-row>
</view>
<view class="" style="height: 40rpx;"></view>
<view class="text-align-center px-32 text-grey ">{{selectedVal}}</view>
<view class="pa-32">
<tm-button :black="black_tmeme" @click="confirm" block itemeClass="round-24" :theme="btnColor?btnColor:color_tmeme" fontSize="32">{{btnText}}</tm-button>
</view>
</view>
</tm-poup>
</view>
</template>
<script>
/**
* 日历
* @description 日历组件,提供节气、农历公历显示,时间范围选择等功能。
* @property {String} mode = [day|rang] 默认:day单选日期,rang范围选择日期。
* @property {String} title = [] 弹层层标题
* @property {String} btn-text = [] 底部按钮确认的文字
* @property {Boolean} inline = [] 默认true,是否内联或者块状block,内联有助于单行内想快速显示操作多个日历。非内联,适合独占一行。
* @property {String} btn-color = [primary|green|orange|red|blue|bg-gradient-blue-lighten] 默认:bg-gradient-blue-lighten底部按钮确认的背景颜色仅支持主题色名称
* @property {String} color = [primary|green|orange|red|blue] 主题默认:primary,提供是请写主题色名称
* @property {String} default-value = [] 默认时间默认:当前时间,格式:'2021-7-21'
* @property {String} bing-end = [] 时间格式:'2021-7-21',范围结束时间当mode=rang时有效
* @property {String} bing-start = [] 时间格式:'2021-7-21',范围结束时间当mode=rang时有效
* @property {Boolean|String} disabled = [true|false] 是否禁用,只读,默认false
* @property {Function} confirm 按钮点击确认时会发送当前选中的数据。如果数据为空,则不触发。
* @property {Function} rang-start 日历为rang时间范围模式时,当设定开始时间触发,并返回开始时间和结束时间(空)的数据。
* @property {Function} rang-end 日历为rang时间范围模式时,当设定结束时间触发,并返回开始时间和结束时间的数据。
* @property {String} time-start = [] 日历时间可选范围开始日期格式'2021-7-21'
* @property {String} time-end = [] 日历时间可选范围结束日期格式'2021-7-21'
* @property {Array<object>} txt = [] 设置日期下标文本;格式[{date:'2021-7-21',text:"测试"}]
* @property {Array<object>} selected-date-class = [] 设置指定日期的样式,格式[{date:'2021-7-21',class:"类名"}]
* @property {Boolean|String} black = [true|false] 是否开启暗黑模式
* @property {Boolean} show-nongli = [true|false] 是否显示农历
* @property {Array} formart = [] 默认['年','月','日'],时间的分割符。
* @example <tm-calendar :txt="bbc" ref='calendar' mode="rang" time-start="2021-7-1" time-end="2021-7-31" @confirm="next" v-model="tts" ></tm-calendar>
*/
import tmIcons from "@/tm-vuetify/components/tm-icons/tm-icons.vue"
import tmCol from "@/tm-vuetify/components/tm-col/tm-col.vue"
import tmRow from "@/tm-vuetify/components/tm-row/tm-row.vue"
import tmButton from "@/tm-vuetify/components/tm-button/tm-button.vue"
import tmPoup from "@/tm-vuetify/components/tm-poup/tm-poup.vue"
export default {
components:{tmIcons,tmCol,tmRow,tmButton,tmPoup},
name: "tm-calendar",
props:{
black:{
type:Boolean|String,
default:null
},
disabled:{
type:Boolean|String,
default:false
},
// 默认时间。
defaultValue:{
type:String,
default:''
},
mode:{
type:String,
default:'day'
},
bingStart:{
type:String,
default:null
},
timeStart:{
type:String,
default:null
},
timeEnd:{
type:String,
default:null
},
bingEnd:{
type:String,
default:null
},
// 顶部标题。
title:{
type:String,
default:'选择时间'
},
// 底部按钮文件
btnText:{
type:String,
default:'确认'
},
// 底部按钮背景主题色名称
btnColor:{
type:String,
default:''
},
// 主题色。
color:{
type:String,
default:'primary'
},
value:{
type:Boolean,
default:false
},
txt:{
type:Array,
default:()=>{ return []}
},
// 指定日期的样式类格式同txt
selectedDateClass:{
type:Array,
default:()=>{ return []}
},
// 是否显示家历
showNongli:{
type:Boolean,
default:false
},
// 跟随主题色的改变而改变。
fllowTheme:{
type:Boolean|String,
default:true
},
formart:{
type:Array,
default:()=>{
return ['年','月','日'];
}
},
inline:{
type:Boolean|String,
default:true
}
},
model:{
prop:'value',
event:'input'
},
watch:{
bingStart:function(){
clearTimeout(this.tmid)
let t = this;
this.tmid = setTimeout(function(){
t.watchRangeTime();
},500)
},
bingEnd:function(){
clearTimeout(this.tmid)
let t = this;
this.tmid = setTimeout(function(){
t.watchRangeTime();
},500)
},
value:function(val){
this.show = val;
},
txt:function(val){
if(this.cal){
this.cal.setTimeArrayText(this.txt);
}
},
defaultValue:function(val,oldVal){
if(!this.cal) return;
// 自动更新默认的显示时间。
if(this.mode=='day'){
let d = new Date().toLocaleDateString().replace(/\//g,'-');
if(this.defaultValue){
d = this.defaultValue;
}
if(!oldVal||oldVal==''){
this.cal = new this.$tm.calendar({value:d,start:this.timeStart,end:this.timeEnd})
this.nowData = this.cal.getData();
}
this.cal.setValue(d)
let index = this.findItemToindexByDate(this.cal.now_day_month)
if(index>-1){
this.nowData = this.cal.getData();
let item = this.nowData[index];
item.checked = true;
this.selectedDay = item;
this.fanxuanDate();
}
}
}
},
data() {
return {
show: false,
nowData:[],//当前月份数据。
cal:null,//日历对象数据。
selectedDay:null,
start:null,
end:null,
fanwei:[],
tmid:56986,
};
},
destroyed() {
clearTimeout(this.tmid)
},
computed:{
black_tmeme: function() {
if (this.black !== null) return this.black;
return this.$tm.vx.state().tmVuetify.black;
},
color_tmeme:function(){
if(this.$tm.vx.state().tmVuetify.color!==null&&this.$tm.vx.state().tmVuetify.color && this.fllowTheme){
return this.$tm.vx.state().tmVuetify.color;
}
return this.color;
},
titleValue:function(){
if(! this.cal) return ''
return this.cal.value.getFullYear()+(this.formart[0]??'年')+(this.cal.value.getMonth()+1)+(this.formart[1]??'月');
},
centerValue:function(){
if(! this.cal) return ''
return this.cal.value.getMonth()+1;
},
selectedVal:function(){
if(this.mode=='day'&&this.selectedDay){
return this.selectedDay.year+(this.formart[0]??'年')+this.selectedDay.month+(this.formart[1]??'月')+this.selectedDay.day+(this.formart[2]??'');
}
if(this.mode=='rang'&&this.start&&!this.end){
let p = this.start.year+(this.formart[0]??'年')+this.start.month+(this.formart[1]??'月')+this.start.day+(this.formart[2]??'');
let et = '' ;
return p + " ~ " + et;
}
if(this.mode=='rang'&&this.start&&this.end){
let p = this.start.year+(this.formart[0]??'年')+this.start.month+(this.formart[1]??'月')+this.start.day+(this.formart[2]??'日');
let p2 = this.end.year+(this.formart[0]??'年')+this.end.month+(this.formart[1]??'月')+this.end.day+(this.formart[2]??'日');
return p + " ~ " +p2;
}
}
},
mounted() {
this.watchRangeTime();
this.show = this.value;
},
methods: {
watchRangeTime(){
let d = new Date().toLocaleDateString().replace(/\//g,'-');
if(this.defaultValue){
d = this.defaultValue;
}
this.cal = new this.$tm.calendar({value:d,start:this.timeStart,end:this.timeEnd})
if(this.txt){
this.cal.setTimeArrayText(this.txt);
}
this.nowData = this.cal.getData();
if(this.mode=='day'){
let index = this.findItemToindexByDate(this.cal.now_day_month)
if(index>-1){
let item = this.nowData[index];
item.checked = true;
this.selectedDay = item;
this.fanxuanDate();
}
// 反选选区。
}else if(this.mode=='rang'){
if(this.bingStart&&this.bingEnd){
this.fanxuanxuanwuBydate(
new Date(this.bingStart.replace(/-/g,'/')),
new Date(this.bingEnd.replace(/-/g,'/')),
)
}
}
},
openPoup(){
if(this.disabled==true) return;
this.show=!this.show
},
isGuochengBg(item){
if(!item.beginEnd) return '';
if(item.checked===true||item.start||item.end) return this.color_tmeme;
if(item.guocheng) return this.color_tmeme + ' text' ;
if(item.guocheng) return '';
},
isSelectedDateClass(date){
let rangeL = 'round-l-24 round-tr-0 round-br-0'
let rangeR = 'round-r-24 round-tl-0 round-bl-0'
let index = this.selectedDateClass.findIndex((item)=>{
let val = date.year+'-'+date.month+'-'+date.day;
return val == item.date;
})
let pc = '';
if(date.start&&!date.end){
pc = ` ${rangeL} `;
}else if(date.end&&!date.start){
pc = ` ${rangeR} `;
}else if(date.start&&date.end){
pc = ` `
}
if(index >-1){
return this.selectedDateClass[index].class + pc
}
return pc;
},
close(){
this.$refs.pop.close();
},
toogle(e){
this.$emit('input',e);
this.$emit('update:value',e);
if(e){
this.watchRangeTime();
}
},
// 获取当前月份的数据。
getNowMonthData(){
return this.cal.getData();
},
// 设置时间文本数据。
setNowMonthData(e){
if(!Array.isArray(e)) return;
this.cal.setTimeArrayText(e);
this.nowData = this.cal.getData();
let index = this.findItemToindexByDate(this.cal.now_day_month)
if(index>-1){
let item = this.nowData[index];
item.checked = true;
this.selectedDay = item;
}
this.fanxuanDate()
},
confirm(){
if(this.mode=='day'){
if(this.selectedDay){
this.$emit('confirm',this.selectedDay);
this.$emit('update:defaultValue',this.selectedDay.year+'-'+this.selectedDay.month+'-'+this.selectedDay.day)
}
}else if(this.mode=='rang'){
if(this.start&&this.end){
let bts = this.start.year+'-'+this.start.month+'-'+this.start.day;
this.$emit('update:bingStart',bts)
this.$emit('update:defaultValue',bts)
let ets = this.end.year+'-'+this.end.month+'-'+this.end.day;
this.$emit('update:bingEnd',ets)
this.$emit('confirm',[this.start,this.end]);
// this.fanwei
}
}
this.close();
},
preMonth(){
if(!this.cal) return;
this.nowData = this.cal.prevMonth().getData();
this.$nextTick(function(){
this.fanxuanDate();
})
},
nextMonth(){
if(!this.cal) return;
this.nowData = this.cal.nextMonth().getData();
this.$nextTick(function(){
this.fanxuanDate();
})
},
preYear(){
if(!this.cal) return;
this.nowData = this.cal.prevYear().getData();
this.$nextTick(function(){
this.fanxuanDate();
})
},
nextYear(){
if(!this.cal) return;
this.nowData = this.cal.nexYear().getData();
this.$nextTick(function(){
this.fanxuanDate();
})
},
fanxuanDate(){
if(this.mode=='day'){
if(this.selectedDay){
let index = this.findItemToindex(this.selectedDay);
if(index>-1){
this.nowData.splice(index,1,this.selectedDay);
}
}
}else if(this.mode=='rang'){
if(this.start&&!this.end){
let index = this.findItemToindex(this.start);
if(index>-1){
this.nowData.splice(index,1,this.start);
}
return;
}
if(this.start&&this.end){
let index = this.findItemToindex(this.start);
if(index>-1){
this.nowData.splice(index,1,this.start);
}
index = this.findItemToindex(this.end);
if(index>-1){
this.nowData.splice(index,1,this.end);
}
for(let i=0;i<this.fanwei.length;i++){
let idx = this.fanwei[i];
index = this.findItemToindex(idx);
if(index>-1){
this.nowData.splice(index,1,idx);
}
}
return;
}
}
},
isSelected:function(item){
let index = this.selectedDay.indexOf(idx=>{
return idx.year===item.year&&idx.month===item.month&&idx.day===item.day;
})
if(index===-1) return false;
return true;
},
// 查找选中的
findChecked(){
let xz = [];
this.nowData.forEach((item,index)=>{
if(item.checked===true){
xz.push({
item:item,
index:index
})
}
})
return xz;
},
// 查找index
findItemToindex(item){
let index=-1;
for(let i=0;i<this.nowData.length;i++){
let idx = this.nowData[i];
if(idx.year==item.year&&idx.month==item.month&&idx.day==item.day){
index=i;
break;
}
}
return index;
},
// 根据时间选取查找
findItemToindexByDate(date){
let index=-1;
for(let i=0;i<this.nowData.length;i++){
let idx = this.nowData[i];
if(idx.year==date.getFullYear()&&idx.month==(date.getMonth()+1)&&idx.day==date.getDate()){
index=i;
break;
}
}
return index;
},
// 清除当前日历中选中的部分
clearCheckedNowDay(){
let xz = [];
this.nowData.forEach((item,index)=>{
item.checked = false;
xz.push(item);
})
this.nowData = xz;
},
// 清除选区的内容。
clearRangeNowDay(){
let xz = [];
let dqdata = this.cal.value;
for(let i=0;i<this.nowData.length;i++){
let item = this.nowData[i];
let index = i;
item.start = false;
item.end = false;
item.guocheng = false;
item.checked = false;
if(item.year==dqdata.getFullYear()&&item.month==dqdata.getMonth()+1){
item.nowMonth =true;
}else{
item.nowMonth =false;
}
xz.push(item);
}
this.nowData = xz;
},
// 通过外围 时间默认的选中
fanxuanxuanwuBydate(start,end){
if(!start||!end) return;
this.$nextTick(function(){
if(start.getTime()>end.getTime()) return;
// 获取开始月份的数据。
let sobj = new this.$tm.calendar({value:start.toLocaleDateString().replace(/\//g,'-')});
// 获取结束月份的数据。
let eobj = new this.$tm.calendar({value:end.toLocaleDateString().replace(/\//g,'-')});
function findItemToindex_only(obj,type){
let item=null;
for(let i=0;i<obj.length;i++){
let idx = obj[i];
if(idx.nowDay==true){
item = idx;
break;
}
}
if(type == 'start'&&item){
item.start=true;
item.end=false;
item.checked=false;
item.guocheng=false;
}else if(type == 'end'&&item){
item.start=false;
item.end=true;
item.checked=false;
item.guocheng=false;
}
return item;
}
let sodata = sobj.getData();
let eodata = eobj.getData();
let start_obj = null;
let end_obj = null;
start_obj = findItemToindex_only(sodata,'start')
let s_index = this.findItemToindex(start_obj)
if(s_index>-1){
this.nowData.splice(s_index,1,start_obj)
}
end_obj = findItemToindex_only(eodata,'end')
let e_index = this.findItemToindex(end_obj)
if(e_index>-1){
this.nowData.splice(e_index,1,end_obj)
}
this.start = start_obj
this.end = end_obj
// 如果结束和开始相等。
if(start.getTime()==end.getTime()){
this.start = {...this.start,start:true,end:true}
this.end = {...this.end,start:true,end:true}
}
// 计算包含的时间 区域。
let start_time = new Date(this.start.year+"/"+this.start.month+"/"+this.start.day).getTime()
let start_bdm = new Date(this.start.year,this.start.month-1).getTime()
let end_time = new Date(this.end.year+"/"+this.end.month+"/"+this.end.day).getTime()
let end_bdm = new Date(this.end.year,this.end.month-1).getTime()
this.fanwei=[];
let m=[];
let testc = new this.$tm.calendar({value:this.start.year+"/"+this.start.month+"/"+this.start.day})
testc.setTimeArrayText(this.txt);
function findItemToindex_only_nob(item,obj){
let istrue = false;
for(let i=0;i<obj.length;i++){
let idx = obj[i];
if(item.year==idx.year&&item.month==idx.month&&item.day==idx.day){
istrue = true;
break;
}
}
return istrue;
}
for(let j=0;j<1000;j++){
let pds = new Date(testc.value.getFullYear(),testc.value.getMonth()).getTime();
let testod = testc.getData();
if(pds<=end_bdm&&pds>=start_bdm){
for(let k=0;k<testod.length;k++){
if(!findItemToindex_only_nob(testod[k],m)){
m.push(testod[k]);
}
}
testc.nextMonth()
}else{
break;
}
}
for(let i=0;i<m.length;i++){
let dod = {...m[i]};
let npds = new Date(dod.year+"/"+(dod.month)+"/"+dod.day);
let dq = npds.getTime()
if(dq > start_time && dq < end_time){
dod.start=false;
dod.end=false;
dod.checked=false;
dod.guocheng=true;
let isindex =this.findItemToindex(dod);
if(isindex>-1){
this.nowData.splice(isindex,1,dod);
}
this.fanwei.push(dod)
}
}
// 开始反选。
this.fanxuanDate();
})
},
day_danxuanclick(e,index) {
// 是否禁用。
if(this.disabled||this.disabled=='true'){
this.$tm.toast("已被禁用")
return;
}
if(!e.beginEnd){
this.$tm.toast("不可选!")
return;
};
if(this.mode=='day'){
this.clearCheckedNowDay();
let p = {...e};
p.checked = true;
this.selectedDay = p;
this.nowData.splice(index,1,p);
}else{
let p = {...e};
if(!this.start&&!this.end){
this.clearRangeNowDay();
p.start = true;
p.end = false;
p.guocheng = false;
this.start = p
this.nowData.splice(index,1,p);
//发布选中开始的事件。
this.$emit("rang-start",{start:p,end:null})
return;
}
if(this.start&&this.end){
this.clearRangeNowDay();
p.start = true;
p.end = false;
p.guocheng = false;
this.start = p
this.nowData.splice(index,1,p);
this.end=null;
//发布选中开始的事件。
this.$emit("rang-start",{start:p,end:null})
return;
}
if(this.start&&!this.end){
this.$nextTick(function(){
let selected = new Date(e.year+"/"+e.month+"/"+e.day).getTime();
let selectedStart = new Date(this.start.year+"/"+this.start.month+"/"+this.start.day).getTime()
if(selected <= selectedStart)
{
// this.clearRangeNowDay();
let enjh = uni.$tm.deepClone(this.start);
enjh.start = selected<selectedStart?false:true;
enjh.end = true;
enjh.guocheng = false;
this.end = enjh
let index_check =-1;
for(let ix =0 ; ix <this.nowData.length;ix++){
let item_check = this.nowData[ix]
if( item_check.month==enjh.month&&item_check.year==enjh.year&&item_check.day==enjh.day){
index_check=ix;
break;
}
}
if(index_check>-1){
this.nowData.splice(index_check,1,this.end);
}
p.start = true;
p.end = selected<selectedStart?false:true;
p.guocheng = false;
this.start = p
}else if(selected > selectedStart)
{
p.start = false;
p.end = true;
p.guocheng = false;
this.end = p
}
this.nowData.splice(index,1,p);
//发布选中开始的事件。
this.$emit("rang-start",{start:p,end:this.end})
//发布选中开始的事件。
this.$emit("rang-end",{start:this.start,end:this.end})
// 计算包含的时间 区域。
let start_time = new Date(this.start.year+"/"+this.start.month+"/"+this.start.day).getTime()
let start_bdm = new Date(this.start.year,this.start.month-1).getTime()
let end_time = new Date(this.end.year+"/"+this.end.month+"/"+this.end.day).getTime()
let end_bdm = new Date(this.end.year,this.end.month-1).getTime()
this.fanwei=[];
let m=[];
let testc = new this.$tm.calendar({value:this.start.year+"-"+this.start.month+"-"+this.start.day})
testc.setTimeArrayText(this.txt);
function findItemToindex_only(item,obj){
let istrue = false;
for(let i=0;i<obj.length;i++){
let idx = obj[i];
if(item.year==idx.year&&item.month==idx.month&&item.day==idx.day){
istrue = true;
break;
}
}
return istrue;
}
for(let j=0;j<1000;j++){
let npsDate = new Date(testc.value.getFullYear(),testc.value.getMonth());
let pds = npsDate.getTime();
let testod = testc.getData();
if(pds<=end_bdm&&pds>=start_bdm){
for(let k=0;k<testod.length;k++){
if(!findItemToindex_only(testod[k],m)){
m.push(testod[k]);
}
}
testc.nextMonth()
}else{
break;
}
}
for(let i=0;i<m.length;i++){
let dod = {...m[i]};
let npds = new Date(dod.year+"/"+(dod.month)+"/"+dod.day);
let dq = npds.getTime()
if(dq > start_time && dq < end_time){
dod.start=false;
dod.end=false;
dod.checked=false;
dod.guocheng=true;
let isindex =this.findItemToindex(dod);
if(isindex>-1){
this.nowData.splice(isindex,1,dod);
}
this.fanwei.push(dod)
}
}
})
}
}
}
},
}
</script>
<style lang="scss" scoped>
.tm-calendar-col{
width: 100%;
// height: 80upx;
min-height: 80upx;
// text-align: center;
// line-height: 80upx;
line-height: inherit;
position: relative;
.text_bl{
// position: absolute;
bottom: 14upx;
}
}
.textOn{
color:#1976d2 !important;
}
.tm-calendar-wk {
width: 100%;
.tm-calendar-title {
position: relative;
.tm-calendar-close {
position: absolute;
top: 32upx;
right: 32upx;
height: 50upx;
width: 50upx;
}
}
}
.tm-calendar-body {
position: relative;
.tm-calendar-bg {
height: 570upx;
.text {
font-size: 400upx !important;
color: rgba(225, 225, 225, 0.4);
&.tm-calendar-bg-center-text span{
font-size: 400upx !important;
}
}
}
.tm-calendar-content {
width: 100%;
position: absolute;
top: 0;
left: 0;
}
}
</style>
@@ -0,0 +1,887 @@
<template>
<view class="tm-calendarView">
<view class="tm-calendarView-wk">
<view class="shadow-10">
<view class="tm-calendarView-title pa-32 pb-16">
<view class="text-size-g text-align-center">{{title}}</view>
</view>
<view class="flex-center pa-32 " style="width: 70%;margin: auto;">
<view>
<tm-icons dense @click="preYear" name="icon-angle-double-left" color="grey-lighten-1"></tm-icons>
<text class="px-24"></text>
<tm-icons dense @click="preMonth" name="icon-angle-left" color="grey-lighten-1"></tm-icons>
</view>
<view class="text-size-n text-weight-b px-40">{{titleValue}}</view>
<view>
<tm-icons dense @click="nextMonth" name="icon-angle-right" color="grey-lighten-1"></tm-icons>
<text class="px-24"></text>
<tm-icons dense @click="nextYear" name="icon-angle-double-right" color="grey-lighten-1"></tm-icons>
</view>
</view>
</view>
</view>
<view class="tm-calendarView-body">
<view class="tm-calendarView-bg flex-center">
<text class="text" :class="[black?' opacity-5 ':'']">{{centerValue}}</text>
</view>
<view class="tm-calendarView-content">
<view style="height: 32upx;"></view>
<tm-row>
<!-- #ifdef H5 || APP-VUE || APP-PLUS -->
<tm-col color="" align="center" width="14.28%" v-for="(item,index) in ['一','二','三','四','五','六','日']"
:key="index+'_a'"><text class="text-size-s py-16">{{item}}</text>
</tm-col>
<!-- #endif -->
<!-- #ifdef MP-->
<tm-col color="" align="center" width="14.28%" v-for="(item,index) in ['一','二','三','四','五','六','日']"
:key="index"><text class="text-size-s py-16">{{item}}</text>
</tm-col>
<!-- #endif -->
<tm-col :round="(item.start||item.end||item.checked)?4:0" @click="day_danxuanclick(item,index)"
:color="item.beginEnd?(item.checked===true||item.start||item.end?color_tmeme+(black_tmeme?' bk ':''):(item.guocheng?color_tmeme+' text opacity-7 '+(black_tmeme?'bk':''):'')):''"
:custom-class="isSelectedDateClass(item)"
align="middle" width="14.28%" v-for="(item,index) in nowData"
:key="index">
<view class="tm-calendarView-col flex-center flex-col" :class="[black&&!item.beginEnd?' opacity-2 ':'']">
<text class="text-size-n"
:class="[
!item.nowMonth&&!item.guocheng&&!item.checked&&!item.start&&!item.end?(black_tmeme?'text-grey-darken-3':'text-grey-lighten-1'):'',
item.checked||item.start||item.end?'text-white':'',
item.guocheng?' text text-'+color_tmeme:'',
!item.beginEnd?'text-grey-lighten-3':''
]"
>{{item.day}}</text>
<view class="text-size-xs text_bl"
>
<block v-if="item.start">
开始
</block>
<block v-if="item.end">
结束
</block>
<block v-if="!item.start&&!item.end">
{{item.text?item.text:(showNongli?item.nongli.day:'')}}
</block>
</view>
</view>
</tm-col>
</tm-row>
</view>
<view class="" style="height: 40rpx;"></view>
<view v-if="showConfirm">
<view class="text-align-center px-32 text-grey ">{{selectedVal}}</view>
<view class="pa-32">
<tm-button @click="confirm" block itemeClass="round-24" :theme="btnColor?btnColor:color_tmeme" fontSize="32">{{btnText}}</tm-button>
</view>
</view>
</view>
</view>
</template>
<script>
/**
* 日历,这是一个内嵌版,可以直接嵌入页面显示,属性基本等同:tm-calendar
* @description 日历组件,提供节气、农历公历显示,时间范围选择等功能。
* @property {String} mode = [day|rang] 默认:day单选日期,rang范围选择日期。
* @property {Function} confirm = [] 当选择日期确认后触发,如果未选择确认后不会触发事件。
* @property {String} title = [] 弹层层标题
* @property {String} btn-text = [] 底部按钮确认的文字
* @property {String} btn-color = [primary|green|orange|red|blue|bg-gradient-blue-lighten] 默认:bg-gradient-blue-lighten底部按钮确认的背景颜色仅支持主题色名称
* @property {String} color = [primary|green|orange|red|blue] 主题默认:primary,提供是请写主题色名称
* @property {String} default-value = [] 默认时间默认:当前时间,格式:'2021-7-21'
* @property {String} bing-end = [] 时间格式:'2021-7-21',当mode=rang时有效
* @property {String} bing-start = [] 时间格式:'2021-7-21',当mode=rang时有效
* @property {Boolean|String} disabled = [true|false] 是否禁用,只读,默认false
* @property {Function} confirm = [] 按钮点击确认时会发送当前选中的数据。如果数据为空,则不触发。
* @property {String} time-start = [] 日历时间可选范围开始日期格式'2021-7-21'
* @property {String} time-end = [] 日历时间可选范围结束日期格式'2021-7-21'
* @property {Array<object>} txt = [] 设置日期下标文本;格式[{date:'2021-7-21',text:"测试"}]
* @property {Array<object>} selected-date-class = [] 设置指定日期的样式,格式[{date:'2021-7-21',class:"类名"}]
* @property {Boolean|String} black = [true|false] 是否开启暗黑模式
* @property {Boolean} show-nongli = [true|false] 是否显示农历
* @property {Boolean} show-confirm = [true|false] true,是否显示底部按钮和详细信息。
* @property {Array} formart = [] 默认['年','月','日'],时间的分割符。
* @example <tm-calendarView :txt="bbc" ref='calendar' mode="rang" time-start="2021-7-1" time-end="2021-7-31" @confirm="next" v-model="tts" ></tm-calendarView>
*/
import tmIcons from "@/tm-vuetify/components/tm-icons/tm-icons.vue"
import tmCol from "@/tm-vuetify/components/tm-col/tm-col.vue"
import tmRow from "@/tm-vuetify/components/tm-row/tm-row.vue"
import tmButton from "@/tm-vuetify/components/tm-button/tm-button.vue"
export default {
components:{tmIcons,tmCol,tmRow,tmButton},
name: "tm-calendarView",
props:{
black:{
type:Boolean|String,
default:false
},
disabled:{
type:Boolean|String,
default:false
},
// 默认时间。
defaultValue:{
type:String,
default:''
},
mode:{
type:String,
default:'day'
},
bingStart:{
type:String,
default:null
},
timeStart:{
type:String,
default:null
},
timeEnd:{
type:String,
default:null
},
bingEnd:{
type:String,
default:null
},
// 顶部标题。
title:{
type:String,
default:'选择时间'
},
// 底部按钮文件
btnText:{
type:String,
default:'确认'
},
// 底部按钮背景主题色名称
btnColor:{
type:String,
default:''
},
// 主题色。
color:{
type:String,
default:'primary'
},
// 主题色。
txt:{
type:Array,
default:()=>{ return []}
},
// 指定日期的样式类格式同txt
selectedDateClass:{
type:Array,
default:()=>{ return []}
},
// 是否显示家历
showNongli:{
type:Boolean,
default:false
},
showConfirm:{
type:Boolean,
default:true
},
// 跟随主题色的改变而改变。
fllowTheme:{
type:Boolean|String,
default:true
},
formart:{
type:Array,
default:()=>{
return ['年','月','日'];
}
}
},
watch:{
bingStart:function(){
this.watchRangeTime();
},
bingEnd:function(){
this.watchRangeTime();
},
txt:function(val){
if(this.cal){
this.cal.setTimeArrayText(this.txt);
}
},
defaultValue:function(val){
// 自动更新默认的显示时间。
if(this.mode=='day'){
let d = new Date().toLocaleDateString().replace(/\//g,'-');
if(this.defaultValue){
d = this.defaultValue;
}
this.cal.setValue(d)
let index = this.findItemToindexByDate(this.cal.now_day_month)
if(index>-1){
this.nowData = this.cal.getData();
let item = this.nowData[index];
item.checked = true;
this.selectedDay = item;
this.fanxuanDate();
}
}
}
},
data() {
return {
showCarlader: false,
nowData:[],//当前月份数据。
cal:null,//日历对象数据。
selectedDay:null,
start:null,
end:null,
fanwei:[]
};
},
computed:{
black_tmeme: function() {
if (this.black !== null) return this.black;
return this.$tm.vx.state().tmVuetify.black;
},
color_tmeme:function(){
if(this.$tm.vx.state().tmVuetify.color!==null&&this.$tm.vx.state().tmVuetify.color && this.fllowTheme){
return this.$tm.vx.state().tmVuetify.color;
}
return this.color;
},
show:{
get:function(){
return this.showCarlader;
},set:function(val){
this.showCarlader = val
}
},
titleValue:function(){
if(! this.cal) return ''
return this.cal.value.getFullYear()+(this.formart[0]??'年')+(this.cal.value.getMonth()+1)+(this.formart[1]??'月');
},
centerValue:function(){
if(! this.cal) return ''
return this.cal.value.getMonth()+1;
},
selectedVal:function(){
if(this.mode=='day'&&this.selectedDay){
return this.selectedDay.year+(this.formart[0]??'年')+this.selectedDay.month+(this.formart[1]??'月')+this.selectedDay.day+(this.formart[2]??'日');
}
if(this.mode=='rang'&&this.start&&!this.end){
let p = this.start.year+(this.formart[0]??'年')+this.start.month+(this.formart[1]??'月')+this.start.day+(this.formart[2]??'日');
let et = '' ;
return p + " ~ " + et;
}
if(this.mode=='rang'&&this.start&&this.end){
let p = this.start.year+(this.formart[0]??'年')+this.start.month+(this.formart[1]??'月')+this.start.day+(this.formart[2]??'日');
let p2 = this.end.year+(this.formart[0]??'年')+this.end.month+(this.formart[1]??'月')+this.end.day+(this.formart[2]??'日');
return p + " ~ " +p2;
}
}
},
mounted() {
this.watchRangeTime();
},
methods: {
watchRangeTime(){
let d = new Date().toLocaleDateString().replace(/\//g,'-');
if(this.defaultValue){
d = this.defaultValue;
}
this.cal = new this.$tm.calendar({value:d,start:this.timeStart,end:this.timeEnd})
if(this.txt){
this.cal.setTimeArrayText(this.txt);
}
this.nowData = this.cal.getData();
if(this.mode=='day'){
let index = this.findItemToindexByDate(this.cal.now_day_month)
if(index>-1){
let item = this.nowData[index];
item.checked = true;
this.selectedDay = item;
this.fanxuanDate();
}
// 反选选区。
}else if(this.mode=='rang'){
if(this.bingStart&&this.bingEnd){
this.fanxuanxuanwuBydate(
new Date(this.bingStart.replace(/-/g,'/')),
new Date(this.bingEnd.replace(/-/g,'/')),
)
}
}
},
isSelectedDateClass(date){
let rangeL = 'round-l-24 round-tr-0 round-br-0'
let rangeR = 'round-r-24 round-tl-0 round-bl-0'
let index = this.selectedDateClass.findIndex((item)=>{
let val = date.year+'-'+date.month+'-'+date.day;
return val == item.date;
})
let pc = '';
if(date.start&&!date.end){
pc = ` ${rangeL} `;
}else if(date.end&&!date.start){
pc = ` ${rangeR} `;
}else if(date.start&&date.end){
pc = ` `
}
if(index >-1){
return this.selectedDateClass[index].class + pc
}
return pc;
},
// 获取当前月份的数据。
getNowMonthData(){
return this.cal.getData();
},
// 设置时间文本数据。
setNowMonthData(e){
if(!Array.isArray(e)) return;
this.cal.setTimeArrayText(e);
this.nowData = this.cal.getData();
let index = this.findItemToindexByDate(this.cal.now_day_month)
if(index>-1){
let item = this.nowData[index];
item.checked = true;
this.selectedDay = item;
}
this.fanxuanDate()
},
confirm(){
if(this.mode=='day'){
if(this.selectedDay){
this.$emit('confirm',this.selectedDay);
this.$emit('update:defaultValue',this.selectedDay.year+'-'+this.selectedDay.month+'-'+this.selectedDay.day)
}
}else if(this.mode=='rang'){
if(this.start&&this.end){
let bts = this.start.year+'-'+this.start.month+'-'+this.start.day;
this.$emit('update:bingStart',bts)
this.$emit('update:defaultValue',bts)
let ets = this.end.year+'-'+this.end.month+'-'+this.end.day;
this.$emit('update:bingEnd',ets)
this.$emit('confirm',[this.start,this.end]);
// this.fanwei
}
}
return null;
},
preMonth(){
if(!this.cal) return;
this.nowData = this.cal.prevMonth().getData();
this.$nextTick(function(){
this.fanxuanDate();
})
},
nextMonth(){
if(!this.cal) return;
this.nowData = this.cal.nextMonth().getData();
this.$nextTick(function(){
this.fanxuanDate();
})
},
preYear(){
if(!this.cal) return;
this.nowData = this.cal.prevYear().getData();
this.$nextTick(function(){
this.fanxuanDate();
})
},
nextYear(){
if(!this.cal) return;
this.nowData = this.cal.nexYear().getData();
this.$nextTick(function(){
this.fanxuanDate();
})
},
fanxuanDate(){
if(this.mode=='day'){
if(this.selectedDay){
let index = this.findItemToindex(this.selectedDay);
if(index>-1){
this.nowData.splice(index,1,this.selectedDay);
}
}
}else if(this.mode=='rang'){
if(this.start&&!this.end){
let index = this.findItemToindex(this.start);
if(index>-1){
this.nowData.splice(index,1,this.start);
}
return;
}
if(this.start&&this.end){
let index = this.findItemToindex(this.start);
if(index>-1){
this.nowData.splice(index,1,this.start);
}
index = this.findItemToindex(this.end);
if(index>-1){
this.nowData.splice(index,1,this.end);
}
for(let i=0;i<this.fanwei.length;i++){
let idx = this.fanwei[i];
index = this.findItemToindex(idx);
if(index>-1){
this.nowData.splice(index,1,idx);
}
}
return;
}
}
},
isSelected:function(item){
let index = this.selectedDay.indexOf(idx=>{
return idx.year===item.year&&idx.month===item.month&&idx.day===item.day;
})
if(index===-1) return false;
return true;
},
// 查找选中的
findChecked(){
let xz = [];
this.nowData.forEach((item,index)=>{
if(item.checked===true){
xz.push({
item:item,
index:index
})
}
})
return xz;
},
// 查找index
findItemToindex(item){
let index=-1;
for(let i=0;i<this.nowData.length;i++){
let idx = this.nowData[i];
if(idx.year==item.year&&idx.month==item.month&&idx.day==item.day){
index=i;
break;
}
}
return index;
},
// 根据时间选取查找
findItemToindexByDate(date){
let index=-1;
for(let i=0;i<this.nowData.length;i++){
let idx = this.nowData[i];
if(idx.year==date.getFullYear()&&idx.month==(date.getMonth()+1)&&idx.day==date.getDate()){
index=i;
break;
}
}
return index;
},
// 清除当前日历中选中的部分
clearCheckedNowDay(){
let xz = [];
this.nowData.forEach((item,index)=>{
item.checked = false;
xz.push(item);
})
this.nowData = xz;
},
// 清除选区的内容。
clearRangeNowDay(){
let xz = [];
let dqdata = this.cal.value;
for(let i=0;i<this.nowData.length;i++){
let item = this.nowData[i];
let index = i;
item.start = false;
item.end = false;
item.guocheng = false;
item.checked = false;
if(item.year==dqdata.getFullYear()&&item.month==dqdata.getMonth()+1){
item.nowMonth =true;
}else{
item.nowMonth =false;
}
xz.push(item);
}
this.nowData = xz;
},
// 通过外围 时间默认的选中
fanxuanxuanwuBydate(start,end){
if(!start||!end) return;
this.$nextTick(function(){
if(start.getTime()>end.getTime()) return;
// 获取开始月份的数据。
let sobj = new this.$tm.calendar({value:start.toLocaleDateString().replace(/\//g,'-')});
// 获取结束月份的数据。
let eobj = new this.$tm.calendar({value:end.toLocaleDateString().replace(/\//g,'-')});
function findItemToindex_only(obj,type){
let item=null;
for(let i=0;i<obj.length;i++){
let idx = obj[i];
if(idx.nowDay==true){
item = idx;
break;
}
}
if(type == 'start'&&item){
item.start=true;
item.end=false;
item.checked=false;
item.guocheng=false;
}else if(type == 'end'&&item){
item.start=false;
item.end=true;
item.checked=false;
item.guocheng=false;
}
return item;
}
let sodata = sobj.getData();
let eodata = eobj.getData();
let start_obj = null;
let end_obj = null;
start_obj = findItemToindex_only(sodata,'start')
let s_index = this.findItemToindex(start_obj)
if(s_index>-1){
this.nowData.splice(s_index,1,start_obj)
}
end_obj = findItemToindex_only(eodata,'end')
let e_index = this.findItemToindex(end_obj)
if(e_index>-1){
this.nowData.splice(e_index,1,end_obj)
}
this.start = start_obj
this.end = end_obj
// 如果结束和开始相等。
if(start.getTime()==end.getTime()){
this.start = {...this.start,start:true,end:true}
this.end = {...this.end,start:true,end:true}
}
// 计算包含的时间 区域。
let start_time = new Date(this.start.year+"/"+this.start.month+"/"+this.start.day).getTime()
let start_bdm = new Date(this.start.year+"/"+this.start.month).getTime()
let end_time = new Date(this.end.year+"/"+this.end.month+"/"+this.end.day).getTime()
let end_bdm = new Date(this.end.year+"/"+this.end.month).getTime()
this.fanwei=[];
let m=[];
let testc = new this.$tm.calendar({value:this.start.year+"/"+this.start.month+"/"+this.start.day})
testc.setTimeArrayText(this.txt);
function findItemToindex_only_nob(item,obj){
let istrue = false;
for(let i=0;i<obj.length;i++){
let idx = obj[i];
if(item.year==idx.year&&item.month==idx.month&&item.day==idx.day){
istrue = true;
break;
}
}
return istrue;
}
for(let j=0;j<1000;j++){
let pds = new Date(testc.value.getFullYear()+"/"+(testc.value.getMonth()+1)).getTime();
let testod = testc.getData();
if(pds<=end_bdm&&pds>=start_bdm){
for(let k=0;k<testod.length;k++){
if(!findItemToindex_only_nob(testod[k],m)){
m.push(testod[k]);
}
}
testc.nextMonth()
}else{
break;
}
}
for(let i=0;i<m.length;i++){
let dod = {...m[i]};
let npds = new Date(dod.year+"/"+(dod.month)+"/"+dod.day);
let dq = npds.getTime()
if(dq > start_time && dq < end_time){
dod.start=false;
dod.end=false;
dod.checked=false;
dod.guocheng=true;
let isindex =this.findItemToindex(dod);
if(isindex>-1){
this.nowData.splice(isindex,1,dod);
}
this.fanwei.push(dod)
}
}
// 开始反选。
this.fanxuanDate();
})
},
day_danxuanclick(e,index) {
// 是否禁用。
if(this.disabled||this.disabled=='true'){
this.$tm.toast("已被禁用")
return;
}
if(!e.beginEnd){
this.$tm.toast("不可选!")
return;
};
if(this.mode=='day'){
this.clearCheckedNowDay();
let p = {...e};
p.checked = true;
this.selectedDay = p;
this.nowData.splice(index,1,p);
}else{
let p = {...e};
if(!this.start&&!this.end){
this.clearRangeNowDay();
p.start = true;
p.end = false;
p.guocheng = false;
this.start = p
this.nowData.splice(index,1,p);
//发布选中开始的事件。
this.$emit("rang-start",{start:p,end:null})
return;
}
if(this.start&&this.end){
this.clearRangeNowDay();
p.start = true;
p.end = false;
p.guocheng = false;
this.start = p
this.nowData.splice(index,1,p);
this.end=null;
//发布选中开始的事件。
this.$emit("rang-start",{start:p,end:null})
return;
}
if(this.start&&!this.end){
this.$nextTick(function(){
let selected = new Date(e.year+"/"+e.month+"/"+e.day).getTime();
let selectedStart = new Date(this.start.year+"/"+this.start.month+"/"+this.start.day).getTime()
if(selected <= selectedStart)
{
// this.clearRangeNowDay();
let enjh = uni.$tm.deepClone(this.start);
enjh.start = selected<selectedStart?false:true;
enjh.end = true;
enjh.guocheng = false;
this.end = enjh
let index_check =-1;
for(let ix =0 ; ix <this.nowData.length;ix++){
let item_check = this.nowData[ix]
if( item_check.month==enjh.month&&item_check.year==enjh.year&&item_check.day==enjh.day){
index_check=ix;
break;
}
}
if(index_check>-1){
this.nowData.splice(index_check,1,this.end);
}
p.start = true;
p.end = selected<selectedStart?false:true;
p.guocheng = false;
this.start = p
}else if(selected > selectedStart)
{
p.start = false;
p.end = true;
p.guocheng = false;
this.end = p
}
this.nowData.splice(index,1,p);
//发布选中开始的事件。
this.$emit("rang-start",{start:p,end:this.end})
//发布选中开始的事件。
this.$emit("rang-end",{start:this.start,end:this.end})
// 计算包含的时间 区域。
let start_time = new Date(this.start.year+"/"+this.start.month+"/"+this.start.day).getTime()
let start_bdm = new Date(this.start.year,this.start.month-1).getTime()
let end_time = new Date(this.end.year+"/"+this.end.month+"/"+this.end.day).getTime()
let end_bdm = new Date(this.end.year,this.end.month-1).getTime()
this.fanwei=[];
let m=[];
let testc = new this.$tm.calendar({value:this.start.year+"-"+this.start.month+"-"+this.start.day})
testc.setTimeArrayText(this.txt);
function findItemToindex_only(item,obj){
let istrue = false;
for(let i=0;i<obj.length;i++){
let idx = obj[i];
if(item.year==idx.year&&item.month==idx.month&&item.day==idx.day){
istrue = true;
break;
}
}
return istrue;
}
for(let j=0;j<1000;j++){
let npsDate = new Date(testc.value.getFullYear(),testc.value.getMonth());
let pds = npsDate.getTime();
let testod = testc.getData();
if(pds<=end_bdm&&pds>=start_bdm){
for(let k=0;k<testod.length;k++){
if(!findItemToindex_only(testod[k],m)){
m.push(testod[k]);
}
}
testc.nextMonth()
}else{
break;
}
}
for(let i=0;i<m.length;i++){
let dod = {...m[i]};
let npds = new Date(dod.year+"/"+(dod.month)+"/"+dod.day);
let dq = npds.getTime()
if(dq > start_time && dq < end_time){
dod.start=false;
dod.end=false;
dod.checked=false;
dod.guocheng=true;
let isindex =this.findItemToindex(dod);
if(isindex>-1){
this.nowData.splice(isindex,1,dod);
}
this.fanwei.push(dod)
}
}
})
}
}
}
},
}
</script>
<style lang="scss" scoped>
.tm-calendarView-col{
width: 100%;
height: 80upx;
// text-align: center;
// line-height: 80upx;
line-height: inherit;
position: relative;
.text_bl{
// position: absolute;
bottom: 14upx;
}
}
.textOn{
color:#1976d2 !important;
}
.tm-calendarView-wk {
width: 100%;
.tm-calendarView-title {
position: relative;
.tm-calendarView-close {
position: absolute;
top: 32upx;
right: 32upx;
}
}
}
.tm-calendarView-body {
position: relative;
.tm-calendarView-bg {
height: 570rpx;
.text {
font-size: 400upx;
color: rgba(225, 225, 225, 0.4);
}
}
.tm-calendarView-content {
width: 100%;
position: absolute;
top: 0;
left: 0;
}
}
</style>
+210
View File
@@ -0,0 +1,210 @@
<template>
<view @click="onclick" class="tm-card " :class="['mx-32', 'my-24',
black_tmeme ? 'grey-darken-5' : propsAttr.bgColor, `round-${propsAttr.round}`, `shadow-${propsAttr.bgColor}-${propsAttr.shadow}`]">
<view :class="['pa-24']">
<view :class="[propsAttr.img?'flex-start':'']">
<view v-if="propsAttr.img" class="flex-shrink mr-24">
<slot name="img" :text="{data:propsAttr.img}">
<tm-images :round="propsAttr.imgRound" :width="90" :src="propsAttr.img"></tm-images>
</slot>
</view>
<view class="fulled">
<view class="subtitle_wk flex-between ">
<view v-if="propsAttr.subTitle" class="subtitle text-size-s text-grey">
<slot name="subTitle" :text="{data:propsAttr.subTitle}">{{ propsAttr.subTitle }}</slot>
</view>
<view class="px-12"></view>
<view v-if="propsAttr.statusText" class="substatus flex-shrink text px-12 py-6 text-size-xs round-6 text-weight-b" :class="[black_tmeme ? 'bk' : '', propsAttr.statusColor]">
<slot name="statusText" :text="{data:propsAttr.statusText}">{{ propsAttr.statusText }}</slot>
</view>
</view>
<view v-if="propsAttr.title" :class="[`text-size-${titleSize}`]" class=" my-16 text-weight-b text-overflow-2">
<slot name="title" :text="{data:propsAttr.title}">{{ propsAttr.title }}</slot>
</view>
<view v-if="propsAttr.subText" class="text-size-s text-grey text-overflow-2">
<slot name="subText" :text="{data:propsAttr.subText}">{{ propsAttr.subText }}</slot>
</view>
</view>
</view>
<view class=" py-24 flex-center" v-if="titleBorder">
<view class="border-t-1 fulled" :class="[black_tmeme?'bk':'']"></view>
</view>
<view class="text-size-n " :class="[black_tmeme ? 'bk' : '']">
<slot name="content" :text="{data:propsAttr.content}">
<view selectable >{{ propsAttr.content }}</view>
</slot>
</view>
<view class=" py-24 flex-center" v-if="propsAttr.actionBorder">
<view class="border-t-1 fulled" :class="[black_tmeme?'bk':'']"></view>
</view>
<view class="flex-end" :class="[black_tmeme ? 'bk' : '']" v-if="btnColorToarrays.length > 0">
<slot name="action" :action="{ btn: action, color: btnColorToarrays }">
<view v-for="(item, index) in action" @click.stop="actionClick(item, index)" :key="index" class="d-inline-block">
<tm-button :round="propsAttr.actionRound" :theme="btnColorToarrays[index]" :key="index" :black="black_tmeme" size="m">
{{ item }}
</tm-button>
</view>
</slot>
</view>
</view>
</view>
</template>
<script>
/**
* 卡片
* @param {String} title-size= [xxs|s|n|g|lg|xl] 默认:g,对应的文本尺寸:xxs,xs,s,n,g,lg,xl
* @param {String} img= [] 默认:'',头像图片地址。
* @param {Number} imgRound= [] 默认:4,头像圆角
* @param {Number} round= [] 默认:4,卡片圆角
* @param {Number} shadow= [] 默认:4,卡片投影
* @param {Array|String} btnColor= [] 默认:white,按钮颜色,可以是string,或者数组['white,'red']按钮组就会依次使用该颜色,如只是一颜色,第一个使用,后面的使用默认的white.
* @param {Array} action= [] 默认:[],操作按钮组
* @param {Number} action-round= [] 默认:2,操作按钮组的圆角,
* @param {Boolean} action-border= [] 默认:true,是否显示操作按钮上方的边线
* @param {String} status-color= [] 默认:black,卡片右上角状态文件的主题色
* @param {String} status-text= [] 默认:'',卡片右上角状态文本
* @param {String} sub-title= [] 默认:'',卡片左上角的卡片名称
* @param {String} title= [] 默认:'',标题
* @param {String} sub-text= [] 默认:'',副标题
* @param {Boolean} title-border= [] 默认:true,标题下方的边线。
* @param {String} content= [] 默认:'',卡片的正方内容
* @param {Boolean} black= [] 默认:null,是否暗黑
* @param {String} bg-color= [] 默认:'white',卡片的背景色
* @param {Function} click 点击卡片触发的事件
* @param {Function} action-click 点击按钮组触发的事件
* @example <tm-card title="简单示例" content="内容" :action="['操作按钮']"></tm-card>
*/
import tmButton from '@/tm-vuetify/components/tm-button/tm-button.vue';
export default {
name: 'tm-card',
components: { tmButton },
props: {
img:{
type:String,
default:''
},
imgRound:{
type:Number | String,
default:4
},
//卡片的圆角
round: {
type: Number | String,
default: 4
},
shadow: {
type: Number | String,
default: 4
},
btnColor: {
type: Array | String,
default: () => 'white'
},
action: {
type: Array,
default: () => []
},
actionRound: {
type: Number | String,
default: 2
},
actionBorder:{
type:Boolean|String,
default:true
},
statusColor: {
type: String,
default: 'black'
},
statusText: {
type: String,
default: ''
},
//卡片名称
subTitle: {
type: String,
default: ''
},
//标题
title: {
type: String,
default: ''
},
titleSize: {
type: String,
default: 'g'
},
titleBorder:{
type:Boolean|String,
default:true
},
//标题介绍
subText: {
type: String,
default: ''
},
content: {
type: String,
default: ''
},
black: {
type: Boolean | String,
default: null
},
bgColor: {
type: String,
default: 'white'
}
},
computed: {
propsAttr:function(){
return {
bgColor:this.bgColor,
statusColor:this.statusColor,
title:this.title,
subTitle:this.subTitle,
content:this.content,
actionRound:this.actionRound,
actionBorder:this.actionBorder,
round:this.round,
shadow:this.shadow,
img:this.img,
}
},
black_tmeme: function() {
if (this.black !== null) return this.black;
return this.$tm.vx.state().tmVuetify.black;
},
btnColorToarrays: function() {
let al = this.action.length;
if (this.action.length == 0 || !this.action) return [];
let colors = this.btnColor;
colors = Array.isArray(colors) ? colors : [colors];
for (let i = 0; i < al; i++) {
if (!colors[i]) {
colors.push('white');
}
}
return colors;
}
},
data() {
return {};
},
methods:{
actionClick(item,index){
this.$emit('action-click',index);
},
onclick(e){
this.$emit('click',e);
}
}
};
</script>
<style lang="scss" scoped></style>
@@ -0,0 +1,379 @@
<template>
<view class="tm-cartBarFood">
<view
@touchmove.stop.prevent="stopMove"
@click.stop="openList=!openList"
class="tm-cartBarFood-bg absolute"
v-if="openList"
:style="{
height: sywi + 'px'
}"
></view>
<view
:style="{
width: position != 'static' ? wininfo.windowWidth - offsetLeft * 2 + 'px' : 'auto',
left: position != 'static' ? offsetLeft + 'px' : '0px',
top: position == 'top' ? top_px + 'px' : 'auto',
bottom: position == 'bottom' ? bottom_px + 'px' : 'auto'
}"
class="tm-cartBarFood-body "
:class="[
position,
border === 'top' ? 'border-t-1' : '',
border === 'bottom' ? 'border-b-1' : '',
'sheetIDX'
]"
>
<view class="tm-cartBarFood-list white "
:class="[
black_tmeme ? 'grey-darken-5' : '',
black_tmeme ? 'bk' : '',
]"
>
<!-- :cartNum.sync="test" -->
<view v-show="openList" class="pb-32">
<view @touchmove.stop.prevent="stopMove" class="flex-between px-32 py-24">
<text class="text-size-s text-weight-b">已选商品</text>
<view @click="clearEmpty" class="text-size-s text-grey">
<tm-icons color="grey" size="20" name="icon-delete-fill"></tm-icons>
清空购物车
</view>
</view>
<block v-if="listData.length <= 0"><tm-empty label="没有商品,快去选购吧" model="listEmpty"></tm-empty></block>
<view class="tm-cartBarFood-list-srcoll" >
<block v-for="(item, index) in listData" :key="index">
<tm-cartCellListFood :black="black_tmeme" :key="index" @change="listAddChange($event, index)" :mdata="item" :cartNum.sync="item.buy" :color="color" dense ></tm-cartCellListFood>
</block>
</view>
<!-- #ifndef H5 -->
<view style="height:100upx;"></view>
<!-- #endif -->
<!-- #ifdef H5 -->
<view style="height:50upx;"></view>
<!-- #endif -->
</view>
</view>
<view :style="{ background: bgColor }" :class="[
'on',
black_tmeme ? 'grey-darken-5' : '',
black_tmeme ? 'bk' : '',
'round-' + round,
bgTheme,
]" class=" flex-between tm-cartBarFood-wk">
<view class="flex-start">
<view @click="openlistfun" class="tm-cartBarFood-body px-32">
<tm-badges :offset="[10,-5]" v-if="sum.num > 0" :dot="false" :label="sum.num"></tm-badges>
<view :class="[openList?'ani':'']">
<tm-icons :black="black_tmeme" :color="list.length > 0&&disabled==false ? color : colorGrey" :size="60" name="icon-shopping-cart-fill"></tm-icons>
</view>
</view>
<view class="flex flex-col">
<view class="text-weight-b" :class="[`text-${color}`]">
<text class="text-size-xs"></text>
<text class="text-size-lg">{{ sum.price }}</text>
</view>
<view v-if="label!=''&&label.length>0&&label!=true&&label!='true'" class="text-size-xs text-grey">{{label}}</view>
</view>
</view>
<view class="pr-16"><tm-button :black="black_tmeme" @click="confirm" size="n" :theme="list.length > 0&&disabled==false ? btnColor : colorGrey" font-color="white" round="24">{{btnText}}</tm-button></view>
</view>
<!-- 安全区域的高度 -->
<view v-if="safe" class="safe--hei " :class="[
black_tmeme ? 'grey-darken-5' : 'white',
black_tmeme ? 'bk' : '',
]"></view>
</view>
</view>
</template>
<script>
/**
* 底部餐饮结算工具条
* @property {Array} list = [] 默认:[],当前购物车产品列表,
* @property {String} bg-theme = [] 默认:"white",背景颜色主题名称
* @property {String | Boolean} black = [true|false] 默认:null,暗黑模式。
* @property {String} bg-color = [] 默认:'',自定义背景颜色。
* @property {String} color = [] 默认:primary,文字默认激活色。
* @property {String} btn-color = [] 默认:primary,默认按激活主题色。
* @property {String} color-grey = [] 默认:gret,购物车为空时主题色。
* @property {String} btnT-text = [] 默认:确认商品,确认按钮上的文字。
* @property {String} position = [bottom|top|static] 默认:bottom,可选位置:bottom|top|static
* @property {Number|String} top = [] 默认:0,距离顶部的距离:只有在position=='top'使用
* @property {Number|String} bottom = [] 默认:0,距离底部的距离:只有在position=='bottom'使用
* @property {String} border = [top|bottom] 默认:top,显示上边线还是下边线。可选top / bottom
* @property {String|Number} offset-left = [] 默认:0,偏移量。即离左边的间距。如果提供,自动居中出现两边间隙。
* @property {String|Boolean} safe = [true|false] 默认:true,// 是否开启底部安全区域。
* @property {Number} round = [] 默认:0,圆角
* @property {String} label = [] 默认:注意选择的超市哦,价格正文的文字说明。
* @property {Function} change 购物车增减商品时触发{num,index}。
* @property {Function} confirm 确认提交按钮时触发
*
*/
import tmIcons from "@/tm-vuetify/components/tm-icons/tm-icons.vue"
import tmEmpty from "@/tm-vuetify/components/tm-empty/tm-empty.vue"
import tmButton from "@/tm-vuetify/components/tm-button/tm-button.vue"
import tmBadges from "@/tm-vuetify/components/tm-badges/tm-badges.vue"
import tmCartCellListFood from "@/tm-vuetify/components/tm-cartCellListFood/tm-cartCellListFood.vue"
export default {
components:{tmIcons,tmEmpty,tmButton,tmCartCellListFood,tmBadges},
name: 'tm-cartBarFood',
props: {
list: {
Array,
default: () => {
return [];
}
},
// 背景颜色主题名称
bgTheme: {
type: String,
default: 'white'
},
// 是否启用暗黑主题。
black: {
type: String | Boolean,
default: null
},
// 背景颜色,自定义。
bgColor: {
type: String,
default: ''
},
// 自定义项目文字默认激活色。
color: {
type: String,
default: 'primary'
},
btnColor: {
type: String,
default: 'primary'
},
btnText:{
type:String,
default:'确认商品'
},
// 自定义项目文字默认失去焦点色。
colorGrey: {
type: String,
default: 'grey'
},
// 可选bootom / top / static
position: {
type: String,
default: 'bottom'
},
// 距离顶部的距离。默认是0,只有在position=='top'使用
top: {
type: Number | String,
default: 0
},
// 距离顶部的距离。默认是0,只有在position=='bottom'使用
bottom: {
type: Number | String,
default: 0
},
// 显示上边线还是下边线。可选top / bottom
border: {
type: String,
default: ''
},
// 只支持圆角主题。如round-5
round: {
type: String | Number,
default: 0
},
// 偏移量。即离左边的间距。如果提供,整个navbar的宽度会是100% - offsetLeft*2。
offsetLeft: {
type: String | Number,
default: 0
},
// 是否开启底部安全区域。
safe: {
type: String | Boolean,
default: true
},
disabled: {
type: String | Boolean,
default: false
},
// 是否开启点按动画,默认开启。
ani: {
type: String | Boolean,
default: true
},
//价格下方的说明。
label:{
type:String,
default:'注意选择的超市哦'
}
},
watch: {
list:{
deep:true,
handler(){
this.listData = uni.$tm.deepClone(this.list)
}
}
},
computed: {
black_tmeme: function() {
if (this.black !== null) return this.black;
return this.$tm.vx.state().tmVuetify.black;
},
top_px: function() {
return this.top;
},
bottom_px: function() {
return this.bottom;
},
wininfo: function() {
return uni.getSystemInfoSync();
},
listLen: function() {
return this.listData.length;
},
sum:function(){
let t = this;
let pr = 0;
let num =0;
this.listData.forEach(item => {
pr += item.price * item.buy;
num += item.buy;
});
return {
price:pr<=0?0:pr.toFixed(2),
num:num
}
}
},
data() {
return {
safeBottomeHeight: '0',
sywi: 0,
openList: false,
listData:[]
};
},
mounted() {
let t = this;
this.sywi = uni.getSystemInfoSync().windowHeight;
// t.safeBottomeHeight = uni.getSystemInfoSync().safeAreaInsets.bottom;
this.listData = uni.$tm.deepClone(this.list)
},
methods: {
openlistfun(){
if(this.disabled) return;
this.openList = !this.openList;
},
listAddChange(e,index) {
this.$nextTick(function(){
this.$emit('change',{num:e,index:index})
})
},
clearEmpty() {
this.$emit('clear')
},
getlist() {
if(this.list.length<=0) return;
let rlist = {
priceTotal:this.sum.price,
numTotal:this.sum.num,
list:this.list
};
return rlist;
},
confirm() {
if(this.list.length<=0||this.disabled) return;
let rlist = {
priceTotal:this.sum.price,
numTotal:this.sum.num,
list:this.list
};
this.$emit('confirm',rlist);
return rlist;
},
stopMove(){
return false;
}
}
};
</script>
<style lang="scss" scoped>
.safe--hei{
height: var(--status-bar-height);
}
.tm-cartBarFood {
.tm-cartBarFood-bg {
top: 0;
left: 0;
width: 100%;
background-color: rgba(0, 0, 0, 0.3);
}
.tm-cartBarFood-wk{
height: 100rpx;
position: relative;
z-index: 15;
&.on{
box-shadow: 0 -5px 10px rgba(0,0,0,0.05);
}
}
.tm-cartBarFood-body {
animation: scalse 0.4s linear;
.tm-cartBarFood-list {
position: absolute;
bottom: 40rpx;
width: 100%;
z-index: 10;
.tm-cartBarFood-list-srcoll{
max-height: 600rpx;
overflow-y: scroll;
}
}
&.bottom {
position: fixed;
bottom: 0;
left: 0;
z-index: 400;
}
&.top {
position: fixed;
z-index: 400;
top: 0;
left: 0;
}
}
}
.ani {
animation: scalse 0.4s linear;
}
@keyframes scalse {
0% {
transform: scale(0.9);
}
50% {
transform: scale(1.1);
}
100% {
transform: scale(1);
}
}
</style>
@@ -0,0 +1,211 @@
<template>
<view class="tm-cartCellListFood px-20 py-32 flex-top-start" :class="[
bgColor,
black_tmeme ? 'grey-darken-4' : '',
black_tmeme ? 'bk' : '',
border === 'top' ? 'border-t-1' : '',
border === 'bottom' ? 'border-b-1' : '',
]">
<view v-if="mdata[keyMap['img']]" class="tm-cartCellListFood-img" :style="{
width:imgWidth+'rpx',
height:imgWidth+'rpx'
}">
<tm-images :width="imgWidth" :height="imgWidth" :round="3" :src="mdata[keyMap['img']]"></tm-images>
</view>
<view class="tm-cartCellListFood-r fulled ">
<view class="pl-15">
<view class="title text-size-s text-weight-b text-overflow-2" style="line-height: 32rpx;" :class="[black_tmeme ? 'bk' : '',]">
{{mdata[keyMap['title']]}}
</view>
<view style="min-height: 64rpx;" >
<view v-if="!dense&&mdata[keyMap['label']]" class="tm-cartCellListFood-label text-size-s text-grey py-8">{{mdata[keyMap['label']]}}</view>
<view v-if="mdata[keyMap['saleLabel']]&&!dense" class="tm-cartCellListFood-sale text-size-s text-grey">
<block v-for="(item,index) in mdata[keyMap['saleLabel']]" :key="index">
<tm-tags :black="black_tmeme" :color="color" v-if="index<4" size="xs">{{item}}</tm-tags>
</block>
</view>
</view>
<view class="tm-cartCellListFood-price flex-between">
<view>
<text class="text-size-xs text-red"></text>
<text class="text-size-n text-red text-weight-b px-5">{{mdata[keyMap['price']]}}</text>
<text v-if="mdata[keyMap['unit']]" class="text-size-xs text-grey pr-10">/{{mdata[keyMap['unit']]}}</text>
<text v-if="mdata[keyMap['salePrice']]" class="text-delete text-size-xxs text-grey">{{mdata[keyMap['salePrice']]}}</text>
</view>
<view class="flex">
<block v-if="cNum>0">
<view :style="{
width:`${actionSize}rpx`,
height:`${actionSize}rpx`,
}" :class="[color,black_tmeme ? 'bk' : '',]" @click="jian" class="tm-cartCellListFood-actions rounded flex-center outlined">
<text class="iconfont icon-minus text-size-xxs"></text>
</view>
<view class="px-12 text-size-n " :class="[black_tmeme ? 'bk' : '',]">{{cNum}}</view>
</block>
<view :style="{
width:`${actionSize}rpx`,
height:`${actionSize}rpx`,
}" :class="[color,`border-${color}-a-1`,black_tmeme ? 'bk' : '',]" @click="jia" class="tm-cartCellListFood-actions rounded flex-center ">
<text class="iconfont icon-plus text-size-xs"></text>
</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
/**
* 餐饮购物商品列表
* @property {Number} img-width = [] 默认:140,图片元素宽度,rpx单位。
* @property {Number} cart-num = [] 默认:0,当前销售的数量,需要cartNum.sync。
* @property {String} color = [] 默认:primary,主题色
* @property {String} bg-color = [] 默认:white,背景主题色
* @property {Boolean} dense = [ture|false] 默认:false,是否隐藏中间优惠和文字说明的结构,只保留标题和价格数量按钮。
* @property {Number} action-size = [] 默认:34,增减按钮大小。单位rpx
* @property {String} border = [top|bottom] 默认:top 显示上边线还是下边线
* @property {Boolean} black = [ture|false] 默认:null,是否暗黑主题
* @property {Object} mdata = [] 默认:{},数据结构
* @property {Object} key-map = [] 默认:{},mdata的字段映射,每个人数据格式都不一样,如果你的数据和默认字段不一致,需要映射下关键字段。
* @property {Function} change 改变销售数量时触发,返回当前改变后的销售数量。
*/
import tmSliderNav from "@/tm-vuetify/components/tm-sliderNav/tm-sliderNav.vue"
import tmImages from "@/tm-vuetify/components/tm-images/tm-images.vue"
import tmTags from "@/tm-vuetify/components/tm-tags/tm-tags.vue"
export default {
components:{tmSliderNav,tmImages,tmTags},
name:"tm-cartCellListFood",
props:{
imgWidth:{
type:Number,
default:140,
},
cartNum:{
type:Number,
default:0
},
color:{
type:String,
default:'primary'
},
bgColor:{
type:String,
default:'white'
},
// 字段映射表,每个人的mdata的数据字段不一样。如果不同就映射下吧。
keyMap:{
type:Object,
default:()=>{
return {
img:'img',
title:'title',
label:'label',
price:'price',
salePrice:'salePrice',
saleLabel:'saleLabel',
unit:'unit',
buy:'buy'
}
}
},
// 精简后,不显示简介文字和优惠文字,只显示标题,价格和数量
dense:{
type:Boolean|String,
default:false
},
//增减按钮大小。单位rpx
actionSize:{
type:Number,
default:38
},
// 显示上边线还是下边线。可选top / bottom
border: {
type: String,
default: 'top'
},
black:{
type:Boolean|String,
default:null
},
mdata:{
type:Object,
default:()=>{
// return {
// img:'https://picsum.photos/300?k=2',
// title:'特色单人套餐(任选)',
// label:'这个产品是只有几个融会',
// price:36.2,
// salePrice:76.4,
// saleLabel:['7折优惠','首单减3元'],
// unit:'/斤'
// buy:0
// }
return {};
}
}
},
watch:{
'mdata.buy':function(val){
if(this.cart_num==val) return;
this.cart_num = val;
},
},
computed:{
black_tmeme: function() {
if (this.black !== null) return this.black;
return this.$tm.vx.state().tmVuetify.black;
},
cart_num:{
get:function(){
return this.cNum;
},
set:function(val){
this.cNum = val;
this.$emit("update:cartNum",val)
// #ifdef H5
this.$nextTick(function(){
this.$emit("change",val)
})
// #endif
// #ifndef H5
this.$emit("change",val)
// #endif
}
}
},
data() {
return {
cNum:0,
};
},
mounted() {
this.cNum = this.mdata.buy;
},
methods:{
jian(){
const buyNum = this.cNum;
if(buyNum<=0) {
this.cart_num = 0;
return
}
this.cart_num = buyNum-1
},
jia(){
const buyNum = this.cNum;
this.cart_num = parseInt(buyNum) + 1
},
}
}
</script>
<style lang="less">
</style>
@@ -0,0 +1,310 @@
<template>
<view @click="onclick" class=" tm-checkbox " :class="[dense?'':'pa-20',inline?'d-inline-block':'']">
<view class=" flex-start">
<slot name="default" :checkData="{label:label,checked:changValue}" :on="onclick">
<view :style="{width: sizes.wk,height: sizes.wk}" class="tm-checkbox-boey relative d-inline-block"
:class="[black?'bk':'','flex-shrink mr-10',
changValue?'ani':'',
changValue?color_tmeme+' border-'+(borderColor||color_tmeme)+'-a-1':'border-'+(borderColor||color_tmeme)+'-a-1',
disabled?'grey-lighten-2 border-grey-lighten-1-a-1':'',
round==='rounded'?'rounded':'round-'+round]">
<view :class="[changValue?'ani_toMaxToMin_on':'']" class="absolute flex-center" style="width: 100%;height: 100%;">
<tm-icons dense v-show="model === 'normal'" :color="disabled?'opacity-5 white':'white'" :size="sizes.gou" :name="changValue?icon:' '"></tm-icons>
<view v-show="model === 'round'&&changValue" class=" rounded d-inline-block"
:class="[disabled?'opacity-5 white':'white']"
:style="{width: sizes.yuan,height: sizes.yuan}"></view>
</view>
</view>
</slot>
<view v-if="label" :class="[black?'bk':'','px-10 fulled flex-start']" :style="{minHeight: sizes.wk}" class=" tm-checkbox-boey-label ">
<view class="flex-center fulled-height">
<slot name="label" :label="{label:label,checked:changValue}">
<text class=" text-size-n">{{label}}</text>
</slot>
</view>
</view>
</view>
</view>
</template>
<script>
/**
* 复选框,可以单独或者在tm-groupcheckbox中使用。
* @property {Boolean} value = [true|false] 如果想双向绑定需要value.snyc等同v-model。推荐v-model.
* @property {Function} input 等同value.snyc和v-model和change
* @property {Function} change 变化是会返回 {index,checked,value:name携带的数据}
* @property {Boolean} disabled = [true|false] 默认false,禁用
* @property {String} color = [primary|blue] 默认primary,主题色名称。
* @property {String} border-color = [] 默认 '',边线主题色,默认同color可不填。
* @property {String} model = [normal|round] 默认normal, 内部:normal打勾,round:内部为圆点
* @property {String} icon = [icon-check] 默认icon-check,自定义选中时的图标。
* @property {String|Number} round = [2|rounded] 默认2, 圆角,rounded时即圆形。
* @property {String|Number} size = [] 默认32, 大小单位upx
* @property {String|Boolean} dense = [true|false] 默认false, 是否去除外间隙。
* @property {String} label = [] 默认"", 文右边显示的选项文字
* @property {String|Boolean} black = [true|false] 默认false, 暗黑模式
* @property {String|Boolean} inline = [true|false] 默认false, 是否内联模式
* @property {String | Array | Object | Number} name = [] 默认 "", 选中时携带的自定义数据,会通过change带回。
* @example <tm-checkbox v-model="checked" label="苹果"></tm-checkbox>
*/
import tmIcons from "@/tm-vuetify/components/tm-icons/tm-icons.vue"
export default {
components:{tmIcons},
name: 'tm-checkbox',
model: {
prop: 'value',
event: 'input'
},
props: {
// 禁用。
disabled: Boolean,
//是否内联模式
inline:{
type:Boolean,
default:true
},
// 使用时::checked.sync 需要带sync
value: Boolean,
color: {
type: String,
default: 'primary'
},
borderColor: {
type: String,
default: ''
},
// 内部:normal打勾,round:内部为圆点
model: {
type: String,
default: 'normal'
},
// 自定义选中时的图标。
icon: {
type: String,
default: 'icon-check'
},
// 外部形状:== rounded时即圆形。
round: {
type: String | Number,
default: '2'
},
// 单位upx
size: {
type: String | Number,
default: 38
},
// 是否去除外间隙。
dense: {
type: Boolean | String,
default: false
},
// 名称。
label: {
type: String,
default: ''
},
black: {
type: Boolean | String,
default: false
},
name: {
type: String | Array | Object | Number,
default: ''
},
// 跟随主题色的改变而改变。
fllowTheme:{
type:Boolean|String,
default:true
}
},
data() {
return {
};
},
watch: {
value: function(newval, oldval) {
if (newval !== oldval) {
if (!this.jiancMax()) {
this.changValue = false;
return;
}
this.change();
}
}
},
computed: {
color_tmeme:function(){
if(this.$tm.vx.state().tmVuetify.color!==null&&this.$tm.vx.state().tmVuetify.color && this.fllowTheme){
return this.$tm.vx.state().tmVuetify.color;
}
return this.color;
},
changValue: {
get: function() {
return this.value;
},
set: function(newValue) {
this.$emit('input', newValue)
// 如果不想用v-model. 直接使用value,需要:value.sync
this.$emit('update:value', newValue);
}
},
sizes: function() {
return {
wk: uni.upx2px(this.size) + 'px',
gou: uni.upx2px(this.size / 3 * 2) + 'px',
yuan: uni.upx2px(this.size / 2) + 'px',
}
}
},
methods: {
// 检查是否超过了最大选择。
jiancMax() {
let t= this;
let box = [];
let selfIndex;
let __uid = this._uid;
// 同时检测是否被禁用。如果禁用了,也不能设置。
if(this.disabled) return false;
function findchild(p, index) {
let preat = p;
if (preat.$options?.name === 'tm-checkbox') {
if (preat.changValue) {
box.push({
index: index,
value: preat.name,
checked: preat.changValue
})
}
if (preat._uid === __uid) {
selfIndex = index;
}
} else {
if (preat.$children.length > 0) {
preat.$children.forEach(item => {
findchild(item, index++);
})
}
}
};
let preat = t.$tm.getParentAls('tm-groupcheckbox', t.$parent);
if (preat) {
findchild(preat, 0);
if (box.length > preat.max) {
preat.error()
return false
}
}
return true
},
onclick(e) {
if (this.disabled) return;
if (!this.jiancMax()) {
return;
}
this.changValue = !this.changValue;
},
change() {
let box = [];
let selfIndex;
let __uid = this._uid;
function findchild(p, index) {
let preat = p;
if (preat.$options?.name === 'tm-checkbox') {
if (preat.changValue) {
box.push({
index: index,
value: preat.name,
checked: preat.changValue
})
}
if (preat._uid === __uid) {
selfIndex = index;
}
} else {
if (preat.$children.length > 0) {
preat.$children.forEach(item => {
findchild(item, index++);
})
}
}
};
let preat = this.$tm.getParentAls('tm-groupcheckbox', this.$parent);
if (preat) {
findchild(preat, 0);
this.$emit('change', {
index: selfIndex,
checked: this.changValue,
value: this.name
});
preat.change(box)
} else {
this.$emit('change', {
index: 0,
checked: this.changValue,
value: this.name
});
}
}
},
}
</script>
<style lang="scss" scoped>
.tm-checkbox{
vertical-align: middle;
.tm-checkbox-boey,.tm-checkbox-boey-label{
vertical-align: middle;
}
.ani {
animation: ani 0.2s linear;
}
.ani_toMaxToMin_on {
animation: ani_toMaxToMin_on 0.35s ease-in-out;
}
@keyframes ani_toMaxToMin_on {
0% {
transform: scale(0.3);
opacity:0.7;
}
50% {
transform: scale(1.5)
}
100% {
transform: scale(1);
opacity:1;
}
}
@keyframes ani {
0% {
transform: scale(0.9)
}
50% {
transform: scale(1.1)
}
100% {
transform: scale(0.9)
}
}
}
</style>
@@ -0,0 +1,270 @@
<template>
<view class="tm-choujiang py-32">
<view class="tm-choujiang-theme-1 flex-center flex-col">
<view class="tm-choujiang-bg ">
<view
:animation="animationData"
class="tm-choujiang-bg-image"
:style="{
backgroundImage: `url(${them_data.bg})`
}"
></view>
<view :animation="animationData" class="tm-choujiang-bg-prod ">
<view
class="tm-choujiang-bg-cp text-red absolute text-align-center "
:style="{
width: intewidth,
height: inteheight,
transform: `rotate(${45 * (index + 1)}deg)`
}"
v-for="(item, index) in listData"
:key="index"
>
<tm-images v-if="item.img" :previmage="false" :width="60" :src="item.img"></tm-images>
<view class="mx-40" style="line-height: 1;">
<text v-if="item.name" class="text-size-xs ">{{ item.name }}</text>
</view>
</view>
</view>
<view class="flex-center tm-choujiang-point"><tm-images v-if="them_data.point" @click="clickImgPlay" :previmage="false" :width="120" :src="them_data.point"></tm-images></view>
</view>
<view class="tm-choujiang-pingtai flex-center"><tm-images v-if="them_data.dizuo" :previmage="false" :width="600" :src="them_data.dizuo"></tm-images></view>
</view>
</view>
</template>
<script>
/**
* 转盘抽奖
* @property {Number} theme-index = [] 默认0,主题索引,我已内置两套皮肤。
* @property {Array} theme-list = [] 默认[已内置两套],主题列表数据,已有两套。
* @property {Boolean} disabled = [] 默认 false,是否禁用
* @property {Boolean} disabledCenter = [] 默认 false,是否禁用点中间图片开始。
* @property {Number} duration = [] 默认 3000,动画时长
* @property {Number} turns = [] 默认2,转的圈数
* @property {Array} list = [] 默认 [测试的数据],奖品数据。
* @property {Function} start 游戏开始时触发。
* @property {Function} end 游戏结束触发,并返回结果数组。
* @example <tm-choujiang ></tm-choujiang>
*/
import tmImages from "@/tm-vuetify/components/tm-images/tm-images.vue"
export default {
components:{tmImages},
name: 'tm-choujiang',
props: {
themeIndex: {
type: Number,
default: 0
},
themeList: {
type: Array,
default: () => {
return [
{
bg: 'https://jx2d.cn/yuwuimages/choujiang_theme_1_0.png?k=9',
point: 'https://jx2d.cn/yuwuimages/choujiang_theme_1_1.png',
dizuo: 'https://jx2d.cn/yuwuimages/choujiang_theme_1_2.png'
},
{
bg: 'https://jx2d.cn/yuwuimages/choujiang_theme_2_0.png?k=9',
point: 'https://jx2d.cn/yuwuimages/choujiang_theme_2_1.png',
dizuo: 'https://jx2d.cn/yuwuimages/choujiang_theme_2_2.png'
}
];
}
},
disabled: {
type: Boolean,
default: false
},
// 动画时长
duration: {
type: Number,
default: 3000
},
turns:{
type:Number,
default:3
},
//是否禁用点中间图片开始。
disabledCenter:{
type: Boolean,
default: false
},
list: {
type: Array,
default: () => {
return [
{ name: '谢谢惠顾', gailv: 30, id: 1, img: 'https://jx2d.cn/yuwuimages/jiangping_2.png' },
{ name: '90', gailv: 10, id: 2, img: 'https://jx2d.cn/yuwuimages/jiangping_1.png' },
{ name: '135', gailv: 10, id: 3, img: 'https://jx2d.cn/yuwuimages/jiangping_2.png' },
{ name: '180', gailv: 10, id: 4, img: 'https://jx2d.cn/yuwuimages/jiangping_1.png' },
{ name: '225', gailv: 10, id: 5, img: 'https://jx2d.cn/yuwuimages/jiangping_2.png' },
{ name: '275', gailv: 10, id: 7, img: 'https://jx2d.cn/yuwuimages/jiangping_1.png' },
{ name: '315', gailv: 10, id: 8, img: 'https://jx2d.cn/yuwuimages/jiangping_2.png' },
{ name: '360', gailv: 10, id: 9, img: 'https://jx2d.cn/yuwuimages/jiangping_1.png' }
];
}
},
//默认返回真执行,否则不执行开始游戏。
playCallback:{
type:Function,
default:()=>{
return function(){
return true;
}
}
}
},
data() {
return {
animationData: '',
aniOff: true,
jiaodu_old:0,
listData: []
};
},
mounted() {
this.listData = this.list;
this.chuliganlv();
},
watch:{
list:{
handler(){
this.listData = this.list;
this.chuliganlv();
},
deep:true
}
},
computed: {
intewidth: function() {
if (!this.list.length) return 0;
return (uni.upx2px(400) * 3.14) / this.list.length + 'px';
},
inteheight: function() {
if (!this.list.length) return 0;
return uni.upx2px(400) / 2 + 'px';
},
them_index: function() {
return this.themeIndex;
},
them_data: function() {
return this.themeList[this.them_index];
}
},
methods: {
chuliganlv() {
var ml = new this.$tm.choujiang(this.listData);
let jieguo = ml.getResult();
let jd = 360 / this.listData.length;
let index = this.listData.findIndex(item => {
return item.id === jieguo.id;
});
return {
index: index,
data: this.listData[index],
jiaodu: 360 - (index + 1) * 45 + 180*(this.turns*2)
};
},
clickImgPlay(){
if (this.disabledCenter) return;
this.play(this.playCallback);
},
async play(callback) {
if (this.disabled) return;
if (!this.aniOff) {
console.warn('未结束动画,不能连续点击.');
return;
}
let isPlay = true;
if(typeof callback ==='function'){
isPlay = callback();
}
if(isPlay==false) return;
// 开始动画.
this.$emit('start');
let zhongjiang = this.chuliganlv();
this.$nextTick(async function() {
await this.resetInit();
this.animationData = '';
this.aniOff = false;
let duration = this.duration;
var animation = uni.createAnimation({
duration: duration,
timingFunction: 'ease'
});
this.animation = animation;
animation.rotate(zhongjiang.jiaodu).step();
this.animationData = animation.export();
await uni.$tm.sleep(duration);
// 结束动画.
this.aniOff = true;
this.$emit('end', this.listData[zhongjiang.index]);
});
},
// 重置 角度。
async resetInit() {
this.animationData = '';
var animation = uni.createAnimation({
duration: 0,
timingFunction: 'ease'
});
this.animation = animation;
animation.rotate(-45).step();
this.animationData = animation.export();
await uni.$tm.sleep(50);
}
}
};
</script>
<style lang="less" scoped>
.tm-choujiang {
.tm-choujiang-theme-1 {
.tm-choujiang-bg {
width: 600upx;
height: 600upx;
position: relative;
z-index: 2;
.tm-choujiang-bg-image {
width: 100%;
height: 100%;
background-repeat: no-repeat;
background-size: cover;
}
.tm-choujiang-point {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
}
.tm-choujiang-bg-prod {
position: absolute;
width: 400upx;
height: 400upx;
top: 100upx;
left: 100upx;
display: flex;
justify-content: center;
transform: rotate(0deg);
.tm-choujiang-bg-cp {
transform-origin: center bottom;
}
}
}
.tm-choujiang-pingtai {
position: relative;
z-index: 1;
margin-top: -120upx;
}
}
}
</style>
@@ -0,0 +1,467 @@
<template>
<view class="tm-choujiangGame py-32">
<view class="tm-choujiangGame-theme-1 flex-center flex-col">
<view class="tm-choujiangGame-bg ">
<view
@click="clickImgPlay"
class="tm-choujiangGame-bg-image"
:style="{
backgroundImage: `url(${them_data.bg})`
}"
></view>
<view class="tm-choujiangGame-bg-prod ">
<view
class="tm-choujiangGame-bg-cp text-red text-align-center "
:style="{
width: '25%',
height: '100%',
transform: `translateY(${reisetInitTop}px)`
}"
:animation="animationData0"
>
<view
v-for="(item, index) in listData"
:key="index"
style="height: 100%;"
class="flex-center flex-col"
>
<tm-images v-if="item.img" :previmage="false" :width="60" :src="item.img"></tm-images>
{{item.name}}
</view>
</view>
<view
class="tm-choujiangGame-bg-cp text-red text-align-center "
:style="{
width: '25%',
height: '100%',
transform: `translateY(${reisetInitTop}px)`
}"
:animation="animationData1"
>
<view
v-for="(item, index) in listData"
:key="index"
style="height: 100%;"
class="flex-center flex-col"
>
<tm-images v-if="item.img" :previmage="false" :width="60" :src="item.img"></tm-images>
{{item.name}}
</view>
</view>
<view
class="tm-choujiangGame-bg-cp text-red text-align-center "
:style="{
width: '25%',
height: '100%',
transform: `translateY(${reisetInitTop}px)`
}"
:animation="animationData2"
>
<view
v-for="(item, index) in listData"
:key="index"
style="height: 100%;"
class="flex-center flex-col"
>
<tm-images v-if="item.img" :previmage="false" :width="60" :src="item.img"></tm-images>
{{item.name}}
</view>
</view>
<view
class="tm-choujiangGame-bg-cp text-red text-align-center "
:style="{
width: '25%',
height: '100%',
transform: `translateY(${reisetInitTop}px)`
}"
:animation="animationData3"
>
<view
v-for="(item, index) in listData"
:key="index"
style="height: 100%;"
class="flex-center flex-col"
>
<tm-images v-if="item.img" :previmage="false" :width="60" :src="item.img"></tm-images>
{{item.name}}
</view>
</view>
</view>
<view class="flex-end tm-choujiangGame-point">
<view class="tm-choujiangGame-point-bar"
:style="{
transform: aniOff?'rotate(0)':'rotate(25deg)'
}"
>
<tm-images :previmage="false" :width="120" :src="them_data.point"></tm-images>
</view>
</view>
</view>
<!-- transform: choujiangJieGuo==null?`translateY(-100%)`:`translateY(0)` -->
<view class="tm-choujiangGame-pingtai flex-center">
<view class="tm-choujiangGame-pingtai-coll" :style="{
transition: 'all 0.5s',
transform: choujiangJieGuo==null?`translateY(-100%)`:`translateY(0)`
}">
<view class="tm-choujiangGame-pingtai-text">中奖啦~</view>
<tm-images :previmage="false" :width="380" :height="200" :src="them_data.dizuo"></tm-images>
</view>
</view>
</view>
</view>
</template>
<script>
/**
* 转盘抽奖
* @property {Number} theme-index = [] 默认0,主题索引,我已内置两套皮肤。
* @property {Array} theme-list = [] 默认[已内置两套],主题列表数据,已有两套。
* @property {Boolean} disabled = [] 默认 false,是否禁用
* @property {Boolean} disabledCenter = [] 默认 false,是否禁用点中间图片开始。
* @property {Number} duration = [] 默认 3000,动画时长
* @property {Array} list = [] 默认 [测试的数据],奖品数据。
* @property {Function} start 游戏开始时触发。
* @property {Function} end 游戏结束触发,并返回奖品数据。
* @example <tm-choujiangGame ></tm-choujiangGame>
*/
import tmImages from "@/tm-vuetify/components/tm-images/tm-images.vue"
export default {
components:{tmImages},
name: 'tm-choujiangGame',
props: {
themeIndex: {
type: Number,
default: 0
},
themeList: {
type: Array,
default: () => {
return [
{
bg: 'https://jx2d.cn/yuwuimages/choujiangGame_1.png?k=9',
point: 'https://jx2d.cn/yuwuimages/choujiangGame_1_0.png',
dizuo: 'https://jx2d.cn/yuwuimages/choujiangGame_2_1.png'
},
{
bg: 'https://jx2d.cn/yuwuimages/choujiangGame_2.png?k=9',
point: 'https://jx2d.cn/yuwuimages/choujiangGame_2_0.png',
dizuo: 'https://jx2d.cn/yuwuimages/choujiangGame_2_1.png'
}
];
}
},
disabled: {
type: Boolean,
default: false
},
//是否禁用点中间图片开始。
disabledCenter:{
type: Boolean,
default: false
},
// 动画时长
duration: {
type: Number,
default: 150
},
list: {
type: Array,
default: () => {
return [
{ name: '1', gailv: 30, id: 1, img: 'https://jx2d.cn/yuwuimages/jiangping_2.png' },
{ name: '2', gailv: 10, id: 2, img: 'https://jx2d.cn/yuwuimages/jiangping_1.png' },
{ name: '3', gailv: 10, id: 3, img: 'https://jx2d.cn/yuwuimages/jiangping_2.png' },
{ name: '4', gailv: 10, id: 4, img: 'https://jx2d.cn/yuwuimages/jiangping_1.png' },
{ name: '5', gailv: 10, id: 5, img: 'https://jx2d.cn/yuwuimages/jiangping_2.png' },
{ name: '7', gailv: 10, id: 7, img: 'https://jx2d.cn/yuwuimages/jiangping_1.png' },
{ name: '8', gailv: 10, id: 8, img: 'https://jx2d.cn/yuwuimages/jiangping_2.png' },
{ name: '9', gailv: 10, id: 9, img: 'https://jx2d.cn/yuwuimages/jiangping_1.png' }
];
}
}
},
data() {
return {
animationData0: '',
animationData1: '',
animationData2: '',
animationData3: '',
aniOff: true,
choujiangJieGuo:null,
listData: [],
};
},
mounted() {
this.listData = this.list;
},
computed: {
reisetInitTop:function(){
return -(this.listData.length-1)*uni.upx2px(160)
},
them_index: function() {
return this.themeIndex;
},
them_data: function() {
return this.themeList[this.them_index];
}
},
methods: {
clickImgPlay(){
if (this.disabledCenter) return;
this.play();
},
chuliganlv() {
var ml = new this.$tm.choujiang(this.listData);
let zhongle = [];
let xh = [
ml.getResult(),
ml.getResult(),
ml.getResult(),
ml.getResult()
]
for(let i=0 ;i<xh.length;i++){
let index = 0
for(let j=0;j<this.listData.length;j++){
if(this.listData[j].id === xh[i].id){
index = j;
break;
}
}
//(this.listData.length-1)*2
let cisu = (this.listData.length-1)*2+1 + (this.listData.length - index);
zhongle.push({
index:index,
data:xh[i],
playNum:cisu
})
}
return zhongle;
},
async play() {
let t = this;
if (this.disabled) return;
if (!this.aniOff) {
console.warn('未结束动画,不能连续点击.');
return;
}
// 开始动画.
this.$emit('start');
this.choujiangJieGuo = null;
let zhongjiang = this.chuliganlv();
this.$nextTick(async function() {
await this.resetInit();
this.aniOff = false;
let duration = this.duration;
let dhIndex=[0,0,0,0];//内部播放次数
let ydIndex =[0,0,0,0] ; //达到长度时要循环
let isok = [false,false,false,false]
async function aniOpen(ix){
if(ydIndex[ix]>t.listData.length-1){
if(ix==0){
var animation0 = uni.createAnimation({
duration: 0
});
t.animationData0 = animation0;
animation0.translateY(-(t.listData.length-1)*uni.upx2px(160)).step({duration:0});
t.animationData0 = animation0.export();
}else if(ix==1){
var animation1 = uni.createAnimation({
duration: 0
});
t.animationData1 = animation1;
animation1.translateY(-(t.listData.length-1)*uni.upx2px(160)).step({duration:0});
t.animationData1 = animation1.export();
}else if(ix==2){
var animation2 = uni.createAnimation({
duration: 0
});
t.animationData2 = animation2;
animation2.translateY(-(t.listData.length-1)*uni.upx2px(160)).step({duration:0});
t.animationData2 = animation2.export();
}else if(ix==3){
var animation3 = uni.createAnimation({
duration: 0
});
t.animationData3 = animation3;
animation3.translateY(-(t.listData.length-1)*uni.upx2px(160)).step({duration:0});
t.animationData3 = animation3.export();
}
await uni.$tm.sleep(50);
ydIndex[ix] = 0;
}else{
var animation = uni.createAnimation({
duration: duration,
timingFunction: 'linear'
});
if(ix==0){
t.animationData0 = animation;
let jl = -(t.listData.length-1)*160+ydIndex[ix]*160;
animation.translateY(uni.upx2px(jl)).step();
t.animationData0 = animation.export();
}else if(ix==1){
t.animationData1 = animation;
let jl = -(t.listData.length-1)*160+ydIndex[ix]*160;
animation.translateY(uni.upx2px(jl)).step();
t.animationData1 = animation.export();
}else if(ix==2){
t.animationData2 = animation;
let jl = -(t.listData.length-1)*160+ydIndex[ix]*160;
animation.translateY(uni.upx2px(jl)).step();
t.animationData2 = animation.export();
}else if(ix==3){
let jl = -(t.listData.length-1)*160+ydIndex[ix]*160;
animation.translateY(uni.upx2px(jl)).step();
t.animationData3 = animation.export();
}
await uni.$tm.sleep(duration);
}
dhIndex[ix] = dhIndex[ix]+1;
ydIndex[ix] = ydIndex[ix]+1;
if(dhIndex[ix] > zhongjiang[ix].playNum){
isok[ix] = true;
if(isok[0]&&isok[1]&&isok[2]&&isok[3]){
// 结束动画.
t.aniOff = true;
t.$emit('end',zhongjiang)
t.choujiangJieGuo = zhongjiang;
}
}else{
await aniOpen(ix);
}
}
for(let i =0;i<zhongjiang.length;i++){
aniOpen(i);
}
});
},
// 重置 角度
async resetInit() {
this.animationData0 = '';
var animation0 = uni.createAnimation({duration: 0});
this.animationData0 = animation0;
animation0.translateY(-uni.upx2px((this.listData.length-1)*160)).step();
this.animationData0 = animation0.export();
this.animationData1 = '';
var animation1 = uni.createAnimation({duration: 0});
this.animationData1 = animation1;
animation1.translateY(-uni.upx2px((this.listData.length-1)*160)).step();
this.animationData1 = animation1.export();
this.animationData2 = '';
var animation2 = uni.createAnimation({duration: 0});
this.animationData2 = animation2;
animation2.translateY(-uni.upx2px((this.listData.length-1)*160)).step();
this.animationData2 = animation2.export();
this.animationData3 = '';
var animation3 = uni.createAnimation({duration: 0});
this.animationData3 = animation3;
animation3.translateY(-uni.upx2px((this.listData.length-1)*160)).step();
this.animationData3 = animation3.export();
await uni.$tm.sleep(50);
}
}
};
</script>
<style lang="less" scoped>
.tm-choujiangGame {
overflow: hidden;
.tm-choujiangGame-theme-1 {
.tm-choujiangGame-bg {
width: 600upx;
height: 600upx;
position: relative;
z-index: 2;
.tm-choujiangGame-bg-image {
width: 100%;
height: 100%;
position: relative;
z-index: 3;
background-repeat: no-repeat;
background-size: cover;
}
.tm-choujiangGame-point {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
right: 0;
.tm-choujiangGame-point-bar{
position: relative;
right: 0;
z-index: 0;
top: -50upx;
transform-origin: left right;
transition:all 0.5s;
}
}
.tm-choujiangGame-bg-prod {
position: absolute;
overflow: hidden;
width: 370upx;
height: 160upx;
top: 95upx;
left: 103upx;
display: flex;
z-index: 4;
.tm-choujiangGame-bg-cp {
transform-origin: center bottom;
}
}
}
.tm-choujiangGame-pingtai {
position: relative;
z-index: 2;
margin-left: -20upx;
margin-top: -140upx;
overflow: hidden;
height: 190upx;
.tm-choujiangGame-pingtai-coll{
position: relative;
.tm-choujiangGame-pingtai-text{
position: absolute;
z-index: 3;
font-size: 48upx;
text-align: center;
width: 100%;
top: 10upx;
color: red;
font-weight: bold;
}
}
}
}
}
</style>
@@ -0,0 +1,318 @@
<template>
<view class="tm-choujiangGrid">
<view class="tm-choujiangGrid-bg"
:style="{
backgroundImage: `url(${them_data.bg})`
}"
>
<view v-if="listData.length==8" class="tm-choujiangGrid-con">
<view v-for="(item,index) in 3" :key="index" class="tm-choujiangGrid-grid">
<block v-if="index==0">
<view class="tm-choujiangGrid-item">
<view class="tm-choujiangGrid-item-img flex-center">
<tm-images :width="100" :src="listData[0].img"></tm-images>
</view>
<view :class="{on:(aniOff==false&&active==0)||(choujiangJieGuo&&aniOff&&active==0)}" class="tm-choujiangGrid-item-hover"></view>
</view>
<view class="tm-choujiangGrid-item">
<view class="tm-choujiangGrid-item-img flex-center">
<tm-images :width="100" :src="listData[1].img"></tm-images>
</view>
<view :class="{on:(aniOff==false&&active==1)||(choujiangJieGuo&&aniOff&&active==1)}" class="tm-choujiangGrid-item-hover"></view>
</view>
<view class="tm-choujiangGrid-item">
<view class="tm-choujiangGrid-item-img flex-center">
<tm-images :width="100" :src="listData[2].img"></tm-images>
</view>
<view :class="{on:(aniOff==false&&active==2)||(choujiangJieGuo&&aniOff&&active==2)}" class="tm-choujiangGrid-item-hover"></view>
</view>
</block>
<block v-if="index==1">
<view class="tm-choujiangGrid-item">
<view class="tm-choujiangGrid-item-img flex-center">
<tm-images :width="100" :src="listData[7].img"></tm-images>
</view>
<view :class="{on:(aniOff==false&&active==7)||(choujiangJieGuo&&aniOff&&active==7)}" class="tm-choujiangGrid-item-hover"></view>
</view>
<view @click="clickImgPlay" class="tm-choujiangGrid-item">
<view class="tm-choujiangGrid-item-img flex-center tm-choujiangGrid-item-point">
<tm-images :width="140" :src="them_data.point"></tm-images>
</view>
<view class="tm-choujiangGrid-item-hover"></view>
</view>
<view class="tm-choujiangGrid-item">
<view class="tm-choujiangGrid-item-img flex-center">
<tm-images :width="100" :src="listData[3].img"></tm-images>
</view>
<view :class="{on:(aniOff==false&&active==3)||(choujiangJieGuo&&aniOff&&active==3)}" class="tm-choujiangGrid-item-hover"></view>
</view>
</block>
<block v-if="index==2">
<view class="tm-choujiangGrid-item">
<view class="tm-choujiangGrid-item-img flex-center">
<tm-images :width="100" :src="listData[6].img"></tm-images>
</view>
<view :class="{on:(aniOff==false&&active==6)||(choujiangJieGuo&&aniOff&&active==6)}" class="tm-choujiangGrid-item-hover"></view>
</view>
<view class="tm-choujiangGrid-item">
<view class="tm-choujiangGrid-item-img flex-center">
<tm-images :width="100" :src="listData[5].img"></tm-images>
</view>
<view :class="{on:(aniOff==false&&active==5)||(choujiangJieGuo&&aniOff&&active==5)}" class="tm-choujiangGrid-item-hover"></view>
</view>
<view class="tm-choujiangGrid-item">
<view class="tm-choujiangGrid-item-img flex-center">
<tm-images :previmage="false" :width="100" :src="listData[4].img"></tm-images>
</view>
<view :class="{on:(aniOff==false&&active==4)||(choujiangJieGuo&&aniOff&&active==4)}" class="tm-choujiangGrid-item-hover"></view>
</view>
</block>
</view>
</view>
</view>
</view>
</template>
<script>
/**
* 转盘抽奖
* @property {Number} theme-index = [] 默认0,主题索引,我已内置两套皮肤。
* @property {Array} theme-list = [] 默认[已内置两套],主题列表数据,已有两套。
* @property {Boolean} disabled = [] 默认 false,是否禁用
* @property {Number} duration = [] 默认 3000,动画时长
* @property {Boolean} disabledCenter = [] 默认 false,是否禁用点中间图片开始。
* @property {Number} turns = [] 默认 2,转的圈数
* @property {Number} specify = [] 默认 null,为整数时,即可指定奖品,始终中奖保持在这个位置。
* @property {Array} list = [] 默认 [测试的数据],奖品数据。
* @property {Function} start 游戏开始时触发。
* @property {Function} end 游戏结束触发,并返回奖品数据。
* @example <tm-choujiangGame ></tm-choujiangGame>
*/
import tmImages from "@/tm-vuetify/components/tm-images/tm-images.vue"
export default {
components:{tmImages},
name: 'tm-choujiangGrid',
props: {
themeIndex: {
type: Number,
default: 0
},
themeList: {
type: Array,
default: () => {
return [
{
bg: 'https://jx2d.cn/yuwuimages/choujiangGrid_1.png?k=9',
point: 'https://jx2d.cn/yuwuimages/choujiangGrid_1_0.png'
},
{
bg: 'https://jx2d.cn/yuwuimages/choujiangGrid_2.png?k=9',
point: 'https://jx2d.cn/yuwuimages/choujiangGrid_2_0.png'
}
];
}
},
disabled: {
type: Boolean,
default: false
},
//是否禁用点中间图片开始。
disabledCenter:{
type: Boolean,
default: false
},
// 动画时长
duration: {
type: Number,
default: 100
},
list: {
type: Array,
default: () => {
return [
{ name: '1', gailv: 30, id: 1, img: 'https://jx2d.cn/yuwuimages/choujiangGrid_1_1.png' },
{ name: '2', gailv: 10, id: 2, img: 'https://jx2d.cn/yuwuimages/choujiangGrid_1_2.png' },
{ name: '3', gailv: 10, id: 3, img: 'https://jx2d.cn/yuwuimages/choujiangGrid_1_1.png' },
{ name: '4', gailv: 10, id: 4, img: 'https://jx2d.cn/yuwuimages/choujiangGrid_1_2.png' },
{ name: '5', gailv: 10, id: 5, img: 'https://jx2d.cn/yuwuimages/choujiangGrid_1_2.png' },
{ name: '6', gailv: 10, id: 7, img: 'https://jx2d.cn/yuwuimages/choujiangGrid_1_1.png' },
{ name: '7', gailv: 10, id: 8, img: 'https://jx2d.cn/yuwuimages/choujiangGrid_1_1.png' },
{ name: '8', gailv: 10, id: 9, img: 'https://jx2d.cn/yuwuimages/choujiangGrid_1_2.png' }
];
}
},
// 转的圈数。
turns:{
type:Number,
default:2
},
//指定中奖的奖品数组索引,从0开始
specify:{
type:Number,
default:null
}
},
data() {
return {
aniOff: true,
active:0,
choujiangJieGuo:null,
listData: [],
timeid:8564
};
},
mounted() {
this.listData = this.list;
},
destroyed() {
clearTimeout(this.timeid)
},
computed: {
them_index: function() {
return this.themeIndex;
},
them_data: function() {
return this.themeList[this.them_index];
}
},
methods: {
clickImgPlay(){
if (this.disabledCenter) return;
this.play();
},
chuliganlv() {
var ml = new this.$tm.choujiang(this.listData);
let zhongle = [];
let xh = ml.getResult();
let index = 0
for(let j=0;j<this.listData.length;j++){
if(this.listData[j].id === xh.id){
index = j;
break;
}
}
let cisu = (this.listData.length-1)*this.turns+this.turns*2 + (this.specify!==null?this.specify:index);
zhongle = {
index:index,
data:xh,
playNum:cisu
}
return zhongle;
},
play() {
let t = this;
if (this.disabled) return;
if (!this.aniOff) {
console.warn('未结束动画,不能连续点击.');
return;
}
// 开始动画.
this.$emit('start');
this.choujiangJieGuo = null;
let zhongjiang = this.chuliganlv();
this.$nextTick(function() {
this.aniOff = false;
let duration = this.duration;
let playcCisu = 0; //总次数的序列
// 循环的内部索引.
let nbIndex = 0;
function aniOpen(){
if(nbIndex>t.listData.length-1){
nbIndex = 0;
t.active = nbIndex;
}else{
t.active = nbIndex;
nbIndex += 1;
}
clearTimeout(t.timeid);
t.timeid = setTimeout(function() {
playcCisu +=1;
if(playcCisu > zhongjiang.playNum){
t.$emit('end',{index:zhongjiang.index,data:zhongjiang.data})
t.choujiangJieGuo = zhongjiang;
t.aniOff = true;
}else{
aniOpen();
}
}, duration);
}
aniOpen();
});
},
}
};
</script>
<style lang="less" scoped>
.tm-choujiangGrid {
overflow: hidden;
.tm-choujiangGrid-bg{
width:600upx;
height: 600upx;
position: relative;
margin: auto;
background-repeat: no-repeat;
background-size: cover;
.tm-choujiangGrid-con{
position: absolute;
width: 450upx;
height: 450upx;
top: 75upx;
left: 75upx;
.tm-choujiangGrid-grid{
display: flex;
justify-content: space-between;
.tm-choujiangGrid-item{
width: 137upx;
height: 130upx;
border-radius: 20upx;
overflow: hidden;
margin-left: 10upx;
position: relative;
margin-bottom: 28upx;
.tm-choujiangGrid-item-img{
width: 100%;
height: 100%;
color: black;
&.tm-choujiangGrid-item-point{
animation: scalanit 1s linear infinite;
}
}
.tm-choujiangGrid-item-hover{
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
background-color: #ffaa17;
opacity: 0;
&.on{
opacity: 0.6;
}
}
}
}
}
}
}
@keyframes scalanit {
0%{
transform: scale(0.9);
}
50%{
transform: scale(1.1);
}
100%{
transform: scale(0.9);
}
}
</style>
+162
View File
@@ -0,0 +1,162 @@
<!-- 需要配合tm-row使用也可以单独使用tm-col -->
<template >
<view class="tm-col" :class="[widths?'':'tm-col-'+grid, 'ma-' + margin,'mb-'+bma[1],'mx-'+bma[0]]"
:style="{
width:widths,
order: order,
verticalAlign: align,
}">
<view class="tm-col-body " @click="click" :style="{
textAlign:justify
}" :class="['pa-' + padding, aligns,` ${customClass} `,'round-'+round,color]"><slot></slot></view>
</view>
</template>
<script>
/**
* 栅格排版COL
* @description 请注意,可以单独使用,也可搭配row使用。
* @property {String} color = [white|blue] 主题颜色名称更多请见文档
* @property {String} align = [top|bottom|middle] 默认top,内容纵向对齐方式
* @property {String} justify = [left|right|center] 内容横向对齐方式
* @property {String|Number} width = [100%] 宽度,可以是数字其它百分比,数字时单位为upx
* @property {String|Number} grid = [1|2|3|6|12] 列宽默认1 1-12自动计算百分比。
* @property {String|Number} padding = [0] 内间距默认0
* @property {String|Number} margin = [0] 外间距默认0
* @property {String} custom-class = [] 自定义类。
* @property {Number} round = [] 默认:0,圆角。
* @property {Function} click = [] 点击事件
* @property {Number} maxCol = [] 默认:12,布局的列表,比如我想一行5个,就可以用到此属性,设置为10,然后grid=2即可。
* @property {String|Number} order = [0|1|2|3|4] 排列的顺序 默认0 可以是1-12的数字或者字符串
*/
export default {
props: {
// 自定义类。
customClass: {
type: String,
default: ''
},
// 圆角。
round: {
type: String|Number,
default: '0'
},
// 主题色。
color: {
type: String,
default: ''
},
// 自定义宽度。可以是数字,单位如:100vw,5%,auto,优先级高于grid
width: {
type: String | Number,
default: ''
},
// 列宽1-12自动计算百分比。
grid: {
type: String | Number,
default: 1
},
// 内间距。
padding: {
type: String | Number,
default: '0'
},
// 外间距。
margin: {
type: String | Number,
default: '0'
},
// 子项目横向对齐方式。 left|right|center
justify:{
type:String,
default:'center'
},
// 子项目纵向对齐方式。 top|bottom|middle
align:{
type:String,
default:'top'
},
// 排列的顺序。
order: {
type: String | Number,
default: '0'
},
maxCol:{
type:Number,
default:12
}
},
data() {
return {
widths:'',
bma:[0,0],
};
},
computed: {
maxCol_count:function() {
return this.maxCol||12;
},
aligns: function() {
if(this.justify == 'left') return 'flex-start';
if(this.justify == 'right') return 'flex-end';
if(this.justify == 'center') return 'flex-center';
},
},
async mounted() {
let pd = this.$tm.getParentAttr("tm-row",'gutter',this.$parent);
if(pd) this.bma = pd;
this.$nextTick(function(){
this.c_width();
})
},
methods: {
click(e){
this.$emit('click',e);
},
c_width() {
let t = this;
// 如果有自定义宽度,优先使用自定的宽度,否则使用grid的比例。
if (t.width.indexOf('%') > -1 || t.width.indexOf('vw') > -1 || t.width.indexOf('vh') > -1) {
t.widths = t.width;
return ;
}
if (t.width === 'auto') {
t.widths = "100%";
return;
}
if (!isNaN(parseFloat(t.width))) {
t.widths = uni.upx2px(parseInt(t.width)) + 'px';
return ;
}
t.widths = (parseInt(t.grid) / t.maxCol_count) * 100 + '%';
// console.log(t.maxCol_count);
}
},
};
</script>
<style lang="scss" scoped>
.tm-col{
height: inherit;
display: inline-block;
// #ifndef H5
height: 100%;
// #endif
.tm-col-body{
display: block;
}
}
</style>
@@ -0,0 +1,124 @@
<template>
<view class="tm-countdown d-inline-block text-size-n">
<slot name="default" :timeData="{data:time_data,finish:isfinish}">{{text}}</slot>
</view>
</template>
<script>
/**
* 倒计时
* @description 倒计时。
* @property {Number} time = [] 默认 10*1000,单位毫秒,倒计时的总时长。
* @property {String} format = [] 默认 'DD天HH小时MM分SS秒',格式。如果只想要秒:SS秒。
* @property {Boolean} autoStart = [] 默认 true,自动开始倒计时。
* @property {Function} change 时间变化时触发。
* @property {Function} finish 倒计时结束时触发。
* @example <tm-button size="s"><tm-countdown format="SS秒" :time="6*1000">
<template v-slot:default="{timeData}">
{{timeData.finish?'结束':timeData.data.seconds}}
</template>
</tm-countdown></tm-button>
*/
export default {
name:"tm-countdown",
props:{
time:{
type:Number,
default:10 * 1000
},
format:{
type:String,
default:'DD天HH小时MM分SS秒'
},
autoStart:{
type:Boolean,
default:true
}
},
data() {
return {
timid:null,
now:0,
time_data:{}
};
},
computed:{
text:function(){
let minaox = this.formatTime(this.time - this.now);
let ps = this.format;
ps = ps.replace(/(DD)/g,minaox.day);
ps = ps.replace(/(MM)/g,minaox.minutes);
ps = ps.replace(/(HH)/g,minaox.hour);
ps = ps.replace(/(SS)/g,minaox.seconds);
return ps;
},
isfinish:function(){
if(this.now == this.time) return true;
return false;
}
},
destroyed() {
clearInterval(this.time);
},
mounted() {
this.formatTime(this.time);
if(this.autoStart){
this.start();
}
},
methods: {
formatTime(my_time){
var daysRound = Math.floor(my_time/1000/60/60/24);
var hoursRound = Math.floor(my_time/1000/60/60%24);
var minutesRound = Math.floor(my_time/1000/60%60);
var secondsRound = Math.floor(my_time/1000%60);
var millisecondRound = Math.floor(my_time % 1000);
let time = {
day:daysRound>9?daysRound:'0'+daysRound,//天
hour:hoursRound>9?hoursRound:'0'+hoursRound,//小时,
minutes:minutesRound>9?minutesRound:'0'+minutesRound,//分.
seconds:secondsRound>9?secondsRound:'0'+secondsRound,//秒。
millisecond:millisecondRound>9?millisecondRound:'0'+millisecondRound//毫秒。
};
this.time_data = time;
return time;
},
// 开始或者继续。
start() {
let t = this;
clearInterval(this.timid);
this.timid = setInterval(()=>{
let lst = t.now + 50;
if(lst == t.time){
t.$emit('finish')
}
if(lst > t.time){
clearInterval(t.timid);
return;
}
t.now =lst;
t.$emit('change',t.time_data)
},50)
},
// 停止,直接结束。
stop(){
clearInterval(this.timid);
this.now = this.time;
},
// 暂停。
pause(){
clearInterval(this.timid);
},
// 重置。
resinit(){
clearInterval(this.timid);
this.now = 0;
}
},
}
</script>
<style lang="scss">
</style>
@@ -0,0 +1,137 @@
<template>
<view class="tm-coupon ">
<view :class="[`round-${round} `]">
<view :class="[disabled?'gray-100 opacity-4':'']" class="flex-start " style="height: 150rpx;">
<view :class="[color_tmeme,black?'bk':'',text?'text':'',`round-r-${round} round-l-${round}`]" class=" flex-center flex-col px-24 flex-shrink fulled-height">
<view v-if="!h_objData.img">
<text v-show="h_objData.saleSplit" class="text-size-s">{{h_objData.saleSplit}}</text>
<text class=" text-weight-b" style="font-size:60rpx;">{{h_objData.sale}}</text>
<text v-show="h_objData.suffix" class="text-size-s">{{h_objData.suffix}}</text>
</view>
<view v-if="h_objData.img">
<image style="width: 100rpx;height: 100rpx;" class="rounded" :src="h_objData.img"></image>
</view>
<text v-show="h_objData.saleLable" class="text-size-xs text-align-center">{{h_objData.saleLable}}</text>
</view>
<view :class="[color_tmeme,black?'bk':'',text?'text':'',`round-l-${round} round-r-${round}`]" class=" flex-start fulled-height fulled overflow">
<view class="opacity-2" style="height: 100%;width: 0px;border-style: dashed; border-width: 0.5px;"></view>
<view class="px-24 flex-between fulled" >
<view class="flex-col">
<text v-show="h_objData.title" class="text-size-n text-weight-b d-block">{{h_objData.title}}</text>
<text v-show="h_objData.time" class="text-size-xxs d-block">{{h_objData.time}}</text>
</view>
<view @click="onclick" class=" flex-shrink" :class="[h_objData.title&&h_objData.time?'pl-24':'']">
<view :class="[color_tmeme,black?'bk':'',text?'':'text']" class="text-size-xs d-block round-24 px-24 py-6 ">{{h_objData.btnText}}</view>
</view>
</view>
</view>
</view>
<view class="py-10 px-24 white" v-show="h_objData.label">
<text class="text-size-xs d-block text-overflow-1 opacity-6">{{h_objData.label}}</text>
</view>
</view>
</view>
</template>
<script>
/**
* 优惠券
* @property {String|Number} shadow = [] 默认:6,投影
* @property {String|Number} round = [] 默认:4,圆角
* @property {String | Boolean} text = [true|false] 默认:false,是否为文本模式,即减淡背景颜色。
* @property {String | Boolean} black = [true|false] 默认:false,是否开启暗黑模式
* @property {String | Boolean} disabled = [true|false] 默认:false,是否禁用(已使用)
* @property {String | Boolean} hdata = [] 默认:{},数据格式见文档
* @property {String} color = [] 默认:primary,主题颜色名称
* @property {Function} click 整个组件点击事件,返回项目数据
* @example <tm-coupon :hdata="d_1"></tm-coupon>
*/
export default {
name:"tm-coupon",
props:{
// 跟随主题色的改变而改变。
fllowTheme:{
type:Boolean|String,
default:false
},
shadow: {
type: String|Number,
default: 3
},
round: {
type: String|Number,
default: 3
},
// 是否为文本模式,即减淡背景颜色。
text: {
type: String | Boolean,
default: false
},
// 是否开启暗黑模式
black: {
type: String | Boolean,
default: null
},
// 是否已经使用。
disabled: {
type: String | Boolean,
default: false
},
// 主题颜色名称
color: {
type: String,
default: 'primary'
},
hdata:{
type:Object,
default:()=>{
return {}
}
}
},
computed:{
black_tmeme: function() {
if (this.black !== null) return this.black;
return this.$tm.vx.state().tmVuetify.black;
},
color_tmeme:function(){
if(this.$tm.vx.state().tmVuetify.color!==null&&this.$tm.vx.state().tmVuetify.color && this.fllowTheme){
return this.$tm.vx.state().tmVuetify.color;
}
return this.color;
},
h_objData:function(){
let ps = {
img:'',//礼品图片,提供了图片,不显示数字和数字券说明。
sale:'0',
saleSplit:'¥',
saleLable:'',
title:'',
time:'',
btnText:'去使用',
label:'',
suffix:''
}
if(typeof this.hdata !== 'object') return ps;
ps = {...ps,...this.hdata};
return ps;
}
},
data() {
return {
};
},
methods: {
onclick(e) {
if(this.disabled) return;
this.$emit('click',this.h_objData);
}
},
}
</script>
<style lang="scss">
</style>
@@ -0,0 +1,347 @@
<template>
<view
@click.stop="overCloseCHange"
v-if="show"
class="tm-dialog fixed flex-center"
:style="{
height: sysinfo + 'px'
}"
>
<view @click.stop="" :class="[show ? 'success' : '']">
<view :class="[clickOverlay ? 'clickover' : '']">
<tm-sheet :black="black_tmeme" :padding="[0, 0]" classname="overflow" :width="600" :round="round" shadow="10">
<view class="text-size-g flex-center text-weight-b px-32 pt-32 " :class="[black_tmeme ? 'bk' : '', bottomBorder ? 'border-b-1' : '']">
<slot name="title">{{ title }}</slot>
</view>
<view class="px-50 py-n12 text-size-n text-align-center" style="max-height:700rpx;overflow-y: auto;">
<slot name="default">
<view >
<text :class="[black_tmeme ? 'text-white' : 'text-grey-darken-3']">{{ content }}</text>
<view v-if="model == 'confirm'" class="pt-24">
<tm-input bg-color="grey-lighten-5" @input="suren" :black="black_tmeme" v-model="inputValSd" :border-bottom="false" :flat="true"></tm-input>
</view>
</view>
</slot>
</view>
<slot name="button">
<view v-if="theme == 'merge'" class="py-0 flex-between">
<tm-button
:fllowTheme="fllowTheme"
:height="80"
text
:black="black_tmeme"
@click="concelClick"
v-if="showCancel"
:theme="black_tmeme ? 'grey-darken-4' : color_tmeme"
round="0"
shadow="0"
style="width: 50%;"
block
>
{{ cancelText }}
</tm-button>
<tm-button
:fllowTheme="fllowTheme"
:height="80"
:black="black_tmeme"
@click="confirmClick"
:theme="color_tmeme"
round="0"
shadow="0"
:style="{
width: showCancel ? '50%' : '100%'
}"
block
>
{{ confirmText }}
</tm-button>
</view>
<view v-if="theme == 'split'" class="px-40 pb-40 flex-between">
<tm-button
:fllowTheme="fllowTheme"
text
:height="72"
:black="black_tmeme"
@click="concelClick"
v-if="showCancel"
:theme="black_tmeme ? 'grey-darken-4' : color_tmeme"
round="24"
font-size="30"
shadow="0"
style="width: 46%;"
block
>
{{ cancelText }}
</tm-button>
<tm-button
:fllowTheme="fllowTheme"
:height="72"
:black="black_tmeme"
@click="confirmClick"
:theme="color_tmeme"
round="24"
font-size="30"
shadow="0"
:style="{
width: showCancel ? '46%' : '100%'
}"
block
>
{{ confirmText }}
</tm-button>
</view>
</slot>
</tm-sheet>
</view>
</view>
</view>
</template>
<script>
/**
* 对话框
* @property {Boolean} value = [] 显示对话框,推荐使用value.sync或者v-model
* @property {Boolean} black = [] 暗黑模式。
* @property {Boolean} bottom-border = [] true,是否显示标题正文的边线。
* @property {String} confirmColor = [] 默认:primary,确认按钮的主题颜色
* @property {String} confirmText = [] 默认:确认,确认按钮的文字
* @property {Boolean} showCancel = [] 默认:true,是否显示取消按钮。
* @property {Boolean} disabled = [] 默认:false,禁用后,点击哪都关闭不了,只能通过手动设置v-model来关闭窗体。
* @property {String} cancelColor = [] 默认:primary,取消按钮的主题色。
* @property {String} cancelText = [] 默认:取消,取消按钮显示的文字。
* @property {String} title = [] 默认:提示,标题。
* @property {String} content = [] 默认:'',内容文字
* @property {String} theme = [merge|split] 默认:'merge'merge合并按钮,split分割按钮
* @property {String} model = [dialog|confirm] 默认:'dialog',对话框类型.dialog|confirm
* @property {Number|String} round = [] 默认:'4',圆角
* @property {String} input-val = [] 默认:''model=confirm,显示的输入框内容。confirm
* @property {Boolean} over-close = [] 默认:true,点击遮罩是否关闭窗体。
* @property {Function} confirm 确认按钮时触发,注意:如果类型为confirm则返回 的参数包含输入框的内容。
* @property {Function} cancel 点击取消按钮时触发。
* @example <tm-dialog v-model="show" content="这是测试的内容"></tm-dialog>
*/
import tmSheet from '@/tm-vuetify/components/tm-sheet/tm-sheet.vue';
import tmButton from '@/tm-vuetify/components/tm-button/tm-button.vue';
import tmInput from '@/tm-vuetify/components/tm-input/tm-input.vue';
export default {
components: { tmSheet, tmButton, tmInput },
name: 'tm-dialog',
model: {
prop: 'value',
event: 'input'
},
props: {
value: {
type: Boolean,
default: false
},
bottomBorder: {
type: Boolean,
default: false
},
black: {
type: Boolean | String,
default: null
},
confirmColor: {
type: String,
default: 'primary'
},
confirmText: {
type: String,
default: '确认'
},
showCancel: {
type: Boolean,
default: true
},
cancelColor: {
type: String,
default: 'primary'
},
cancelText: {
type: String,
default: '取消'
},
title: {
type: String,
default: '消息提示'
},
content: {
type: String,
default: ''
},
// 样式。
theme: {
type: String,
default: 'merge' //merge|split merge合并按钮,split分割按钮
},
// 对话框类型.dialog|confirm
model: {
type: String,
default: 'dialog' // dialog|confirm
},
round: {
type: Number | String,
default: 8
},
inputVal: {
type: String,
default: ''
},
// 跟随主题色的改变而改变。
fllowTheme: {
type: Boolean | String,
default: true
},
overClose: {
type: Boolean | String,
default: false
},
//如果为true,需要你手动关闭。点按钮关闭不了。
disabled: {
type: Boolean | String,
default: false
}
},
computed: {
show: {
get: function() {
return this.value;
},
set: async function(val) {
this.$emit('input', val);
this.$emit('update:value', val);
}
},
black_tmeme: function() {
if (this.black !== null) return this.black;
return this.$tm.vx.state().tmVuetify.black;
},
color_tmeme: function() {
if (this.$tm.vx.state().tmVuetify.color !== null && this.$tm.vx.state().tmVuetify.color && this.fllowTheme) {
return this.$tm.vx.state().tmVuetify.color;
}
return this.confirmColor;
}
},
data() {
return {
inputValSd: '',
sysinfo: 0,
clickOverlay: false
};
},
created() {
let sysinfo = uni.getSystemInfoSync();
// #ifdef APP || MP
if (sysinfo.windowHeight == sysinfo.screenHeight) {
this.sysinfo = sysinfo.screenHeight;
} else if (sysinfo.windowHeight < sysinfo.screenHeight) {
this.sysinfo = sysinfo.windowHeight;
}
// #endif
// #ifdef H5
if(sysinfo.screenHeight>=sysinfo.windowHeight){
this.sysinfo = sysinfo.windowHeight;
}else{
this.sysinfo = sysinfo.screenHeight;
}
// #endif
this.show = this.value;
},
methods: {
overCloseCHange() {
if (this.overClose) {
this.concelClick();
} else {
this.anifeed();
}
},
anifeed() {
let t = this;
if (this.clickOverlay) this.clickOverlay = !this.clickOverlay;
this.clickOverlay = true;
uni.$tm.sleep(100).then(() => {
t.clickOverlay = false;
});
},
confirmClick() {
if (this.model == 'confirm') {
if (!this.inputValSd) {
uni.$tm.toast('请输入内容');
this.anifeed();
return;
}
this.$emit('confirm', this.inputValSd);
} else {
this.$emit('confirm');
}
if (this.disabled == false) {
this.show = false;
}
},
suren(e) {
this.$emit('update:inputVal', this.inputValSd);
},
concelClick() {
this.$emit('concel');//错误的拼写兼容
this.$emit('cancel');//正常的拼写
if (this.disabled == false) {
this.show = false;
}
}
}
};
</script>
<style lang="scss">
.tm-dialog {
width: 100%;
z-index: 600;
background-color: rgba(0, 0, 0, 0.35);
left: 0;
top: 0;
backdrop-filter: blur(10px);
transition: all 0.35s;
.success {
animation: success 0.35s linear;
// transform: scale(1);
}
.clickover {
animation: clickover 0.35s linear;
}
}
@keyframes clickover {
0% {
transform: scale(0.95);
}
50% {
transform: scale(1.05);
}
100% {
transform: scale(1);
}
}
@keyframes success {
0% {
transform: scale(0.75);
opacity: 0;
}
75% {
transform: scale(1.05);
}
100% {
transform: scale(1);
opacity: 1;
}
}
</style>
@@ -0,0 +1,166 @@
<template>
<view class="tm-divider ">
<view class="flex-center tm-divider-wk" :class="[
vertical?' flex-col flexVer ':'',setpsClass
]" >
<view :style="{
borderBottomStyle:model,
height:vertical?height/2+'px':'1rpx',
width:vertical?'1rpx':'50%',
}" class="tm-divider-left" :class="[vertical?color_tmeme:`border-${color_tmeme}-b-1`]">
</view>
<view v-if="text" :class="[
vertical?'py-20':'px-20'
]" class="tm-divider-text text-size-xs" :style="{color:'grey'}">{{text}}</view>
<!-- 点位符 -->
<text v-if="!text"></text>
<view :style="{
borderBottomStyle:model,
height:vertical?(height/2+'px'):'1rpx',
width:vertical?'1rpx':'50%',
}" class="tm-divider-right" :class="[vertical?color_tmeme:`border-${color_tmeme}-b-1`]"></view>
</view>
</view>
</template>
<script>
/**
* 分割线
* @property {String} text = [] 默认:'',显示的文本。
* @property {String} color = [] 默认:'#eeeeee',线的颜色16进制或者rgb,rgba
* @property {Number} height = [] 默认:0, 竖向高度时,起作用。
* @property {Boolean} vertical = [] 默认:false 是否竖向
* @property {String} model = [solid|dashed|dotted] 默认:solid 线的类型。
* @example <tm-divider text="我是分割线"></tm-divider>
*/
export default {
name: "tm-divider",
props: {
// 不为空时,显示文本。
text: {
type: String,
default: ''
},
// 颜色16进制或者rgb,rgba
color: {
type: String,
default: 'grey'
},
// 竖向高度时,起作用。
height: {
type: Number,
default: 100
},
// 竖向高度时,起作用。
width: {
type: Number,
default: 0
},
// 是否竖
vertical: {
type: Boolean,
default: false
},
// solid|dashed|dotted
model: {
type: String,
default: 'solid'
},
// 跟随主题色的改变而改变。
fllowTheme: {
type: Boolean | String,
default: false
}
},
computed: {
wd: {
get: function() {
if (this.width) return this.width;
return this.width_s;
},
set: function(val) {
this.width_s = val;
}
},
color_tmeme: function() {
if (this.$tm.vx.state().tmVuetify.color !== null && this.$tm.vx.state().tmVuetify.color && this
.fllowTheme) {
return this.$tm.vx.state().tmVuetify.color;
}
return this.color;
},
},
data() {
return {
width_s: 0,
height_s: 0,
setpsClass: ''
};
},
async mounted() {
await this.init();
},
methods: {
async init() {
this.$nextTick(async function() {
let tbs = await this.$Querey(".tm-divider");
this.wd = tbs[0].width ? tbs[0].width : this.wd;
})
},
setWidth(width) {
this.$nextTick(async function() {
this.wd = width;
this.setpsClass = 'setpsClass'
if (this.text) {
let tbs_text = await this.$Querey(".tm-divider-text");
this.wd = this.wd - tbs_text[0].width;
}
})
}
},
}
</script>
<style lang="scss" scoped>
.tm-divider {
display: block;
width: auto;
position: relative;
.tm-divider-wk {
&.setpsClass {
position: absolute;
// left: -100upx;
line-height: 0;
// left: 0;
}
}
.flexVer {
width: 1px;
}
.tm-divider-text {
flex-shrink: 0;
}
.tm-divider-left,
.tm-divider-right {
width: 50%;
height: 1px;
border-bottom-width: 1px;
}
}
</style>
@@ -0,0 +1,329 @@
<template>
<view class="tm-dragGrid relative fulled" :style="{height:totalH+'px'}">
<view class="absolute "
:class="[ani&&!disabled?'aniOn':'',nowMove_index==index||endDrage?'':'tranAni']"
:style="{
width:itemWidth+'px',
height:h+'px',
left:item.left+'px',
top:item.top+'px',
zIndex:nowMove_index==index?5:0
}"
v-for="(item,index) in listData"
:key="index"
:id="'tm-dragGrid-' + index"
@touchstart="m_start($event,index)"
@mousedown="m_start($event,index)"
@touchmove.stop.prevent="m_move($event,index)"
@mousemove.stop.prevent="m_move($event,index)"
@touchend="m_end($event,index)"
@mouseup="m_end($event,index)"
@longpress="startEdit"
>
<slot name="default" :item="item.data">
<view v-if="model==0" class="flex-center flex-col" style="width:70%">
<view style="width:100%"><tm-badges @click.stop="delitem(item)" v-if="!disabled" :offset="[0,-5]" icon="icon-times"></tm-badges></view>
<tm-icons :size="45" :name="item.data.icon" :color="item.data.color"></tm-icons>
<text class="text-size-s pt-10">{{item.data.text}}</text>
</view>
<view v-if="model==1" style="width:100%;height:100%">
<tm-badges @click.stop="delitem(item)" v-if="!disabled" :offset="[-5,-5]" icon="icon-times"></tm-badges>
<view :class="['bg-gradient-'+item.data.color+'-accent']" class=" flex-center round-4 text-size-s" style="width: 90%;height:100rpx">
{{item.data.text}}
</view>
</view>
</slot>
</view>
</view>
</template>
<script>
/**
* 宫格拖动排序
* @property {Number} col = [] 4,一行排列几个
* @property {Number} width = [] 0,组件宽度,可以不设置默认取父组件宽度
* @property {Number} itemHeight = [] 120,项目的高度
* @property {Boolean} ani = [] true,是否开启拖动显示内容抖动动画
* @property {Boolean} disabled = [] false,是否开启拖动
* @property {Boolean} longTap = [] true,是否允许长按启动编辑模式。
* @property {Number} model = [0|1] 00图标模式,1方块样式。
* @property {Array} list = [] [],排序的列表,只要是数组就可,至于内容是什么格式无所谓。
* @property {Function} change 拖放排序时触发,返回更改后的列表数据。
* @property {Function} remove 删除一个项目时触发。
*/
import tmIcons from '@/tm-vuetify/components/tm-icons/tm-icons.vue';
import tmBadges from '@/tm-vuetify/components/tm-badges/tm-badges.vue';
export default {
name:"tm-dragGrid",
components:{tmBadges,tmIcons},
props:{
//几列,一行排几个。
col:{
type:Number,
default:4
},
//组件宽度,可以不设置默认取父组件宽度
width:{
type:Number,
default:0
},
//项目的高度
itemHeight:{
type:Number,
default:120
},
//是否开启拖动显示内容抖动动画
ani:{
type:Boolean,
default:true
},
disabled: {
type: String | Boolean,
default: false
},
list:{
type:Array,
default:()=>{}
},
//是否开户长按开始编辑。
longTap:{
type:Boolean,
default:true
},
model:{
type:Number,
default:0,
}
},
data() {
return {
w:0,
h:0,
row:0,
totalH:0,
listData:[],
itemWidth:0,
endDrage: false,
Drage__id: '', //正在被拖动的id;
nowMove_index: null, //现在正在移动的索引
x:0,
y:0,
prarentTop:0,
prarentLeft:0,
grid:0,
isDrage:false,
};
},
computed:{
},
created() {
this.grid = this.list.length;
},
mounted() {
this.inits();
},
watch:{
list:{
deep:true,
handler(){
this.inits();
}
}
},
methods: {
startEdit(){
if(this.longTap==false) return;
this.$emit("update:disabled",false)
},
delitem(item){
this.list.splice(item.index,1)
this.$emit('remove',item)
this.$emit('change', this.listData);
},
m_start(event,index){
event.preventDefault()
event.stopPropagation()
if (this.disabled) return;
this.nowMove_index = index;
this.endDrage = false;
this.isDrage = true;
if (event.type.indexOf('mouse') == -1 && event.changedTouches.length == 1) {
var touch = event.changedTouches[0];
this.y = touch.pageY - event.currentTarget.offsetTop - this.prarentTop
this.x = touch.pageX - event.currentTarget.offsetLeft - this.prarentLeft
} else {
this.y = event.pageY - event.currentTarget.offsetTop - this.prarentTop
this.x = event.pageX - event.currentTarget.offsetLeft - this.prarentLeft
}
// #ifdef MP
uni.vibrateShort({})
// #endif
},
m_move(event,index){
let t = this
if (this.disabled) return;
event.preventDefault()
event.stopPropagation()
if(!this.isDrage) return;
if (t.endDrage==true) return;
//当前元素的top位置。
let chy = 0;
let chx = 0;
if (event.type.indexOf('mouse') == -1 && event.changedTouches.length == 1) {
var touch = event.changedTouches[0];
chy = touch.pageY - t.y - this.prarentTop
chx = touch.pageX - t.x - this.prarentLeft
} else {
chy = event.pageY - t.y - this.prarentTop
chx = event.pageX - t.x - this.prarentLeft
}
t.listData.splice(index, 1, {
...t.listData[index],
top: chy,
left: chx,
})
t.nowMove_index = index;
const currenit_index = index;
const currentSort = t.listData[currenit_index].sort;
const currenit_id = t.listData[currenit_index].__id;
// 计算当前移动的index.
let moveIndex = Math.round(chx / t.itemWidth) + Math.round(chy / t.h)*t.col;
moveIndex = moveIndex < 0 ? 0 : moveIndex;
moveIndex = moveIndex > t.listData.length - 1 ? t.listData.length - 1 : moveIndex;
moveIndex = Math.abs(moveIndex)
index = moveIndex;
let elList = [...t.listData]
for (let i = 0; i < elList.length; i++) {
if (currentSort < moveIndex) {
if (elList[i].sort > currentSort && elList[i].sort <= moveIndex) {
elList[i].sort -= 1;
};
} else if (currentSort > moveIndex) {
if (elList[i].sort < currentSort && elList[i].sort >= moveIndex) {
elList[i].sort += 1;
};
}
};
elList[currenit_index].sort = moveIndex;
elList = elList.map(el => {
if (el.__id != currenit_id) {
el.left = el.sort % t.col * t.itemWidth;
el.top = parseInt(el.sort / t.col) * t.h;
}
return el;
})
t.listData = elList;
},
m_end(event,index){
if (this.disabled) return;
let t = this;
event.preventDefault()
event.stopPropagation()
this.isDrage = false;
this.endDrage = true;
if (this.nowMove_index == null) return;
let elList = [...t.listData]
elList = this.setTL(elList);
elList.sort((a,b)=>a.sort-b.sort)
t.listData = [...elList]
this.nowMove_index = null;
this.moveChange();
},
moveChange(e, index) {
if (this.disabled) return;
//change后修改的数据 。
const elList = [...this.listData]
elList.sort((a,b)=>a.sort-b.sort-b)
this.$emit('change',elList);
},
inits() {
if(this.grid==0) return;
this.$nextTick(async function() {
let p = await this.$Querey(".tm-dragGrid", this).catch(e => {})
this.grid = this.list.length;
this.listData = [];
//组件的宽度
this.w = uni.upx2px(this.width) || p[0].width || 300;
this.prarentTop = p[0].top;
this.prarentLeft = p[0].left;
//项目的高度。
this.h = uni.upx2px(this.itemHeight)
//项目的宽度
this.itemWidth = this.w / this.col
//行数。
this.row = Math.ceil(this.grid / this.col);
//总高度
this.totalH = this.h * this.row
//构造一个list宫格列表出来。
for(let i=0;i<this.grid;i++){
this.listData.push({
"data":this.list[i],
"__id":uni.$tm.guid(),
"top":0,
"left":0,
"sort":i,
"index":i
})
}
let list = this.setTL([...this.listData]);
this.listData = [...list];
})
},
//计算位置
setTL(el){
for(let i = 0;i < el.length;i++){
el[i].left = el[i].sort % this.col * this.itemWidth;
el[i].top = parseInt(el[i].sort / this.col) * this.h;
}
return el;
}
},
}
</script>
<style lang="scss" scoped>
.aniOn{
animation:doudong 0.5s linear infinite;
background:linear-gradient();
background: -webkit-linear-gradient();
}
.tranAni{
transition:all 0.15s
}
@keyframes doudong {
0%{
transform: rotate(-2deg) translateX(2rpx) translateY(2rpx);
}
25%{
transform: rotate(0deg) translateX(-2rpx) translateY(-2rpx);
}
50%{
transform: rotate(0deg) translateX(0rpx) translateY(-2rpx);
}
75%{
transform: rotate(0deg) translateX(0rpx) translateY(2rpx);
}
100%{
transform: rotate(-2deg) translateX(2rpx) translateY(2rpx);
}
}
</style>
@@ -0,0 +1,272 @@
<template>
<view class="tm-dragList ">
<view :style="{height:h*list.length+'px',width:w+'px'}" class="relative"
:class="[disabled?'opacity-7':'']">
<view class="fulled-height overflow" :class="[bgColor,black_tmeme?'grey-darken-4 bk':'',
'absolute',
'tm-dragList-item','shadow-'+(nowMove_index==index?16:0),'flex-between']" v-for="(item,index) in listData"
:key="index" :style="{
transition: nowMove_index==index||endDrage?'all 0s':'all 0.25s',
top: item.top+'px',
height:h+'px',width:w+'px',zIndex:nowMove_index==index?5:0}">
<view class=" flex-start fulled" :class="[black_tmeme?'border-grey-darken-5-b-1':'border-b-1']"
:style="{height:(h-1)+'px'}"
>
<view v-if="item['icon']" class="flex-shrink pl-32 fulled-height flex-center">
<tm-icons :black="black_tmeme" :color="item['color']||'black'" :fllowTheme="fllowTheme" dense
:name="item['icon']" :size="40"></tm-icons>
</view>
<view class="pl-32 text-size-n" :class="[black_tmeme?'bk':'']">{{item.text}}</view>
</view>
<view
:style="{height:(h-1)+'px',width: '100rpx'}"
@touchstart="m_start($event,index)"
@mousedown="m_start($event,index)"
@touchmove.stop.prevent="m_move($event,index)"
@mousemove.stop.prevent="m_move($event,index)"
@touchend="m_end($event,index)"
@mouseup="m_end($event,index)"
class="flex-shrink flex-end " :class="[black_tmeme?'border-grey-darken-5-b-1':'border-b-1']">
<text class="iconfont icon-menu pr-32 text-size-n"
:class="[black_tmeme?' bk text-grey-darken-2':'text-grey']"></text>
</view>
</view>
</view>
</view>
</template>
<script>
/**
* 拖放排序
* @property {String | Boolean} black = [true|false] 默认:null,是否开启暗黑模式
* @property {String | Boolean} disabled = [true|false] 默认:false,是否禁用,禁用后无法操作。
* @property {Number} width = [] 默认:0,组件的宽度,rpx,可不提供,默认为父组件的宽度。
* @property {Number} height = [] 默认:120,列表项目的高度,rpx,
* @property {String} bgColor = [] 默认:white,项目的背景色
* @property {String} right-icon = [] 默认:'',项目右边可拖动的图标
* @property {String} rang-key = [] 默认:'text',列表项目读取文本的key
* @property {String} list = [] 默认:[],列表数据[{text: "菜单选项",icon: 'icon-menu',color:'red'}]
* @param {Function} change 拖动排序后,触发,返回新的排序后list数据。
*/
import tmIcons from '@/tm-vuetify/components/tm-icons/tm-icons.vue';
export default {
name: "tm-dragList",
components: {
tmIcons
},
props: {
disabled: {
type: String | Boolean,
default: false
},
// 跟随主题色的改变而改变。
fllowTheme: {
type: Boolean | String,
default: true
},
// 是否开启暗黑模式
black: {
type: String | Boolean,
default: null
},
width: {
type: Number,
default: 0
},
height: {
type: Number,
default: 120
},
list: {
type: Array,
default: () => {
return []
}
},
rangKey: {
type: String,
default: 'text'
},
rightIcon: {
type: String,
default: "icon-menu"
},
bgColor: {
type: String,
default: "white"
},
},
destroyed() {
clearTimeout(999)
},
watch: {
list: {
deep: true,
handler() {
this.jishunTopData();
}
}
},
data() {
return {
w: 0,
h: 0,
totalH: 0,
y: 0,
new_index: null, //即将被替换的索引(实质性被替换)
nowMove_index: null, //现在正在移动的索引
listData: [], //被处理过的数据。
new_item: [], //虚拟列表,内部排列好,但未在页面中渲染。
endDrage: false,
Drage__id: '', //正在被拖动的id;
h_top: 0,
};
},
computed: {
black_tmeme: function() {
if (this.black !== null) return this.black;
return this.$tm.vx.state().tmVuetify.black;
},
},
async mounted() {
let t = this;
this.jishunTopData();
},
methods: {
jishunTopData() {
this.$nextTick(async function() {
this.listData = [];
let p = await this.$Querey(".tm-dragList", this).catch(e => {})
this.w = uni.upx2px(this.width) || p[0].width;
this.h = uni.upx2px(this.height)
this.totalH = this.h * this.list.length
let list = [];
for (let i = 0; i < this.list.length; i++) {
let p = this.list[i];
p['top'] = i * this.h;
p['i'] = i;
p['__id'] = uni.$tm.guid();
this.listData.push(p)
}
this.new_item = [...this.listData];
})
},
m_start(event, index) {
event.preventDefault()
event.stopPropagation()
if (this.disabled) return;
this.nowMove_index = index;
this.endDrage = false;
this.new_item = [...this.listData];
if (event.type.indexOf('mouse') == -1 && event.changedTouches.length == 1) {
var touch = event.changedTouches[0];
this.y = touch.pageY - event.currentTarget.offsetTop - this.listData[index].top
} else {
this.y = event.pageY - event.currentTarget.offsetTop - this.listData[index].top
}
// #ifdef MP
uni.vibrateShort({})
// #endif
},
m_move(event, index) {
if (this.disabled) return;
let t = this
event.preventDefault()
event.stopPropagation()
if (t.nowMove_index == null) return;
//当前元素的top位置。
let ch = 0;
if (event.type.indexOf('mouse') == -1 && event.changedTouches.length == 1) {
var touch = event.changedTouches[0];
ch = touch.pageY - t.y
} else {
ch = event.pageY - t.y
}
t.listData.splice(index, 1, {
...t.listData[index],
top: ch
})
const currenit_index = index;
const currentSort = t.listData[currenit_index].i;
const currenit_id = t.listData[currenit_index].__id;
// 计算当前移动的index.
let moveIndex = Math.round(ch / t.h);
moveIndex = moveIndex < 0 ? 0 : moveIndex;
moveIndex = moveIndex > t.listData.length - 1 ? t.listData.length - 1 : moveIndex;
moveIndex = Math.abs(moveIndex)
index = moveIndex;
let elList = [...t.listData]
for (let i = 0; i < elList.length; i++) {
if (currentSort < moveIndex) {
if (elList[i].i > currentSort && elList[i].i <= moveIndex) {
elList[i].i -= 1;
};
} else if (currentSort > moveIndex) {
if (elList[i].i < currentSort && elList[i].i >= moveIndex) {
elList[i].i += 1;
};
}
};
elList[currenit_index].i = moveIndex;
elList = elList.map(im => {
if (im.__id != currenit_id) {
im.top = im.i * t.h;
}
return im;
})
t.listData = elList;
t.new_index = moveIndex;
// #ifdef MP
uni.vibrateShort({})
// #endif
},
m_end(event, index) {
if (this.disabled) return;
let t = this;
event.preventDefault()
event.stopPropagation()
this.nowMove_index = null;
this.endDrage = true;
if (this.new_index == null) return;
let elList = t.listData
elList = elList.map(im => {
im.top = im.i * t.h;
return im;
})
elList.sort((a,b)=>a.i-b.i)
t.listData = [...elList]
this.moveChange();
},
moveChange(e, index) {
if (this.disabled) return;
//change后修改的数据 。
this.$emit('change', this.listData);
}
},
}
</script>
<style lang="scss">
</style>
@@ -0,0 +1,107 @@
//格式如下。如果是单选或者多选项,id为必须。
// input或者slider模式,value,必须,其它格式见下面
[{
title: '',
disabled: true,
children: [{
title: "默认排序",
model: "checkbox",
children: [{
title: "默认排序",
id: '1-1'
},
{
title: "最近更新",
id: '1-2'
},
{
title: "信誉值最高",
id: '1-3'
},
{
title: "信誉值最高",
id: '1-4'
}
]
},
{
title: "价格选择",
model: "radio",
children: [{
title: "默认排序",
id: '2-1'
},
{
title: "最近更新",
id: '2-2'
},
{
title: "信誉值最高",
id: '2-3'
},
{
title: "最近更新",
id: '2-4'
},
{
title: "信誉值最高",
id: '2-5'
}
]
}
]
}, {
title: '',
children: [{
title: "价格排序",
model: "radio",
children: [{
title: "默认排序",
id: '3-1'
},
{
title: "最近更新",
id: '3-2'
},
{
title: "信誉值最高",
id: '3-3'
}
]
}]
},
{
title: '',
children: [{
title: '',
model: "input",
type: "number",
value: ""
},
{
title: '',
model: "slider",
suffix: '',
max: 1000,
value: 0
},
{
title: "品牌排序",
model: "checkbox",
children: [{
title: "默认排序",
id: '4-1'
},
{
title: "最近更新",
id: '4-2'
},
{
title: "信誉值最高",
id: '4-3'
}
]
}
]
}
]
@@ -0,0 +1,547 @@
<template>
<view class="relative">
<view class="tm-dropDownMenu absolute fulled" :style="{zIndex:101}">
<view
class="tm-dropDownMenu-bar"
:class="[
!black_tmeme && bgColor != 'white' ? bgColor : black_tmeme && bgColor == 'white' ? 'grey-darken-4' : bgColor,
black_tmeme ? '' : 'shadow-' + bgColor + '-' + shadow,
black_tmeme ? 'bk' : ''
]"
>
<tm-row align="center" justify="center">
<tm-col color="none" justify="center" align="middle" @click="changeIndex(index)" v-for="(item, index) in formartData" :key="index" :width="itemLength + '%'">
<view class="flex-center" :style="{height: height+'rpx',lineHeight:height+'rpx'}">
<text class=" pr-10" :style="{fontSize:fontSize+'rpx'}" :class="[activeIndex == index ? 'text-' + activeColor : 'text-' + unColor]">{{ item.title }}</text>
<tm-icons
style="line-height: 0;"
dense
:color="activeIndex == index ? activeColor : unColor"
size="24"
:name="activeIndex == index ? 'icon-sort-up' : 'icon-sort-down'"
></tm-icons>
</view>
</tm-col>
</tm-row>
</view>
<view v-if="formartData[activeIndex]" class="tm-dropDownMenu-body py-24 " :class="[black_tmeme ? 'grey-darken-5 bk' : 'white', 'shadow-' + shadow]">
<view v-if="formartData[activeIndex]['children']" :style="{maxHeight:maxHeight+'rpx',overflowY: 'auto'}">
<block v-for="(item, index) in formartData[activeIndex].children" :key="index">
<block v-if="item['children']&&rendIdx>=index" >
<view class="pa-24 text-size-s text-weight-b optAniopt" v-if="item['title']">{{ item.title }}</view>
<view class="optAniopt">
<block v-if="item.model == 'checkbox'">
<tm-groupcheckbox>
<block v-for="(item2, index2) in item.children" :key="index2">
<tm-checkbox :disabled="item2['disabled'] || item['disabled'] ? true : false" dense v-model="item2.checked">
<view class="px-10" :class="[item2['disabled'] || item['disabled'] ? 'opacity-6' : '']">
<tm-button
:fllowTheme="false"
:black="black_tmeme"
:theme="item2.checked ? color: (black_tmeme?'grey-darken-3':'grey-lighten-2')"
:font-color="item2.checked ? color : 'grey'"
dense
style="width: auto"
font-size="24"
height="70"
item-class="mx-14 my-10"
plan
block
icon="icon-check-circle"
:shadow="2"
:height="64"
:round="2"
>
{{ item2.title }}
</tm-button>
</view>
</tm-checkbox>
</block>
</tm-groupcheckbox>
</block>
<block v-if="item.model == 'radio'">
<tm-groupradio>
<block v-for="(item2, index2) in item.children" :key="index2">
<tm-radio :disabled="item2['disabled'] || item['disabled'] ? true : false" dense v-model="item2.checked">
<view class="px-10" :class="[item2['disabled'] || item['disabled'] ? 'opacity-6' : '']">
<tm-button
:fllowTheme="false"
:black="black_tmeme"
:theme="item2.checked ? color: (black_tmeme?'grey-darken-3':'grey-lighten-2')"
:font-color="item2.checked ? color : 'grey'"
dense
style="width: auto"
font-size="24"
height="70"
item-class="mx-14 my-10"
plan
block
icon="icon-check-circle"
:shadow="2"
:height="64"
:round="2"
>
{{ item2.title }}
</tm-button>
</view>
</tm-radio>
</block>
</tm-groupradio>
</block>
<block v-if="item.model == 'list'">
<tm-groupradio key="test">
<block v-for="(item2, index2) in item.children" :key="index2">
<tm-radio :inline="false" :disabled="item2['disabled'] || item['disabled'] ? true : false" dense v-model="item2.checked">
<view class="fulled">
<tm-listitem
:disabled="item2['disabled'] || item['disabled'] ? true : false"
:title-color="item2.checked ? color : 'grey-darken-3'"
:rightIconColor="item2.checked ? color : 'grey-lighten-3'"
:margin="[24, 12]"
:title="item2.title"
fontSize="28"
:shadow="0"
:borderBottom="true"
:rightIconSize='30'
:rightIcon="item2.checked ? 'icon-check-circle' : ''"
></tm-listitem>
</view>
</tm-radio>
</block>
</tm-groupradio>
</block>
<block v-if="item.model == 'listCheckbox'">
<tm-groupcheckbox >
<block v-for="(item2, index2) in item.children" :key="index2">
<tm-checkbox :inline="false" :disabled="item2['disabled'] || item['disabled'] ? true : false" dense v-model="item2.checked">
<view class="fulled">
<tm-listitem
:disabled="item2['disabled'] || item['disabled'] ? true : false"
:title-color="item2.checked ? color : 'grey-darken-3'"
:rightIconColor="item2.checked ? color : 'grey-lighten-3'"
:margin="[24, 12]"
:title="item2.title"
fontSize="28"
:shadow="0"
:borderBottom="true"
:rightIconSize='30'
:rightIcon="item2.checked ? 'icon-check-circle' : ''"
></tm-listitem>
</view>
</tm-checkbox>
</block>
</tm-groupcheckbox>
</block>
</view>
</block>
<block v-else>
<block v-if="item.model == 'input'&&rendIdx>=index" >
<view class="pa-24 text-size-s text-weight-b optAniopt" v-if="item['title']">{{ item.title }}</view>
<tm-input
:fllowTheme="fllowTheme"
border-color="grey-lighten-1"
:disabled="chiludis(item)"
:black="black_tmeme"
:color="color"
:border-bottom="false"
:input-type="item.type || 'text'"
:value.sync="item.value"
></tm-input>
</block>
<block v-if="item.model == 'slider'&&rendIdx>=index" >
<view class="pa-24 text-size-s text-weight-b optAniopt" v-if="item['title']">
{{ item.title }}
<text class="px-24 " :class="[`text-${color}`]">
{{ item.value ? item.value : '未设置' }}{{ item.value ? (item['suffix'] ? item.suffix : '') : '' }}
</text>
</view>
<view class="px-42 py-24 optAniopt">
<tm-slider
:fllowTheme="fllowTheme"
:disabled="chiludis(item)"
:black="black_tmeme"
:color="color"
:max="item.max ? item.max : 100"
v-model="item.value"
>
<template v-slot:tips>
{{ item.value }}
</template>
</tm-slider>
</view>
</block>
<block v-if="item.model == 'pickers'&&rendIdx>=index" >
<view class="pa-24 text-size-s text-weight-b optAniopt" v-if="item['title']">
{{ item.title }}
</view>
<view class="optAniopt">
<tm-pickers
:default-value.sync="item.value"
rang-key="title"
:btn-color="color"
:list="item.data"
>
<tm-input :value="pickTostring(item.value)" prefixp-icon="icon-calendaralt-fill" disabled :placeholder="item['placeholder']?item['placeholder']:'请选择'" suffix-icon="icon-sort-down"></tm-input>
</tm-pickers>
</view>
</block>
</block>
</block>
</view>
<view class="flex-between px-32 pt-32">
<tm-button :fllowTheme="fllowTheme" @click="getData" :theme="color" block style="width: 48%;" height="80">确认</tm-button>
<tm-button
:fllowTheme="fllowTheme"
@click="resetinit"
:black="black_tmeme"
block
:theme="color"
:font-color="color"
text
shadow="0"
style="width: 48%;"
height="80"
>
重置
</tm-button>
</view>
</view>
</view>
<view @click="activeIndex=-1" v-if="activeIndex>-1" class="fixed fulled" :style="{height:height_bg+'px',top:vtop+'px',width:barwidth,background:'rgba(0,0,0,0.33)',zIndex:100}">
</view>
</view>
</template>
<script>
/**
* 下拉选项
* @property {String} color = [] 默认:primary ,主题色下方选项子组件的主题色。
* @property {String} un-color = [] 默认:black ,默认未激活时。bar条上的文字颜色
* @property {String} active-color = [] 默认:primary ,默认激活时。bar条上的文字颜色
* @property {String} bg-color = [] 默认:white ,导航条背景主题色。
* @property {Number} shadow = [] 默认:10 ,导航条的投影。
* @property {Array} list = [] 默认:[] ,数据格式见文档
* @property {Number} maxHeight = [] 默认:650 ,弹出的标签页,最大内容高度,超过自动滚动。
* @property {Number} height = [] 默认:88 ,标签导航的高度
* @property {Number} font-size = [] 默认:28 ,标签导航的文字大小
* @property {Array} default-selected = [] 默认:[] ,默认赋值选中的选项,注意可以是id数组或者对象数组,对象数组情况下必须含id标签符,且是唯一的。
* @property {Boolean} black = [] 默认:false ,暗黑模式。
* @property {Function} change 切换选项页面时触发。
* @property {Function} confirm 点击确认按钮时触发,返回所有选中的项。
* @example <tm-dropDownMenu color="orange" :list="list"></tm-dropDownMenu>
*/
import tmRow from '@/tm-vuetify/components/tm-row/tm-row.vue';
import tmCol from '@/tm-vuetify/components/tm-col/tm-col.vue';
import tmButton from '@/tm-vuetify/components/tm-button/tm-button.vue';
import tmIcons from '@/tm-vuetify/components/tm-icons/tm-icons.vue';
import tmInput from '@/tm-vuetify/components/tm-input/tm-input.vue';
import tmGroupcheckbox from '@/tm-vuetify/components/tm-groupcheckbox/tm-groupcheckbox.vue';
import tmCheckbox from '@/tm-vuetify/components/tm-checkbox/tm-checkbox.vue';
import tmGroupradio from '@/tm-vuetify/components/tm-groupradio/tm-groupradio.vue';
import tmRadio from '@/tm-vuetify/components/tm-radio/tm-radio.vue';
import tmSlider from '@/tm-vuetify/components/tm-slider/tm-slider.vue';
import tmListitem from '@/tm-vuetify/components/tm-listitem/tm-listitem.vue';
import tmPickers from '@/tm-vuetify/components/tm-pickers/tm-pickers.vue';
export default {
components: {tmPickers, tmRow, tmCol, tmButton, tmIcons, tmInput, tmGroupcheckbox, tmCheckbox, tmGroupradio, tmRadio, tmSlider, tmListitem },
name: 'tm-dropDownMenu',
props: {
// 主题色下方选项子组件的主题色
color: {
type: String,
default: 'primary'
},
// 默认未激活时。bar条上的文字颜色
unColor: {
type: String,
default: 'black'
},
// 默认激活时。bar条上的文字颜色
activeColor: {
type: String,
default: 'primary'
},
// 背景颜色。
bgColor: {
type: String,
default: 'white'
},
list: {
type: Array,
default: () => {
return [];
}
},
maxHeight:{
type:Number|String,
default:650
},
height:{
type:Number|String,
default:88
},
fontSize:{
type:Number|String,
default:28
},
//菜单的投影。
shadow: {
type: Number | String,
default: 10
},
// 可以是id索引也可以是对象数组,可以混着来。
defaultSelected: {
type: Array,
default: () => {
return [];
}
},
black: {
type: Boolean | String,
default: null
},
// 跟随主题色的改变而改变。
fllowTheme: {
type: Boolean | String,
default: true
}
},
computed: {
itemLength: function() {
if (this.list.length == 0) return 100;
return 100 / this.list.length;
},
black_tmeme: function() {
if (this.black !== null) return this.black;
return this.$tm.vx.state().tmVuetify.black;
}
},
watch:{
list:{
deep:true,
handler(){
this.$nextTick(function() {
this.formartData = this.chulidata();
});
}
}
},
data() {
return {
activeIndex: -1,
formartData: [],
oldList: [],
test: [],
height_bg:0,
vtop:0,
maxLeng:40,//最大渲染级别
rendIdx:0,
barwidth:'100%'
};
},
created() {
this.height_bg = uni.getSystemInfoSync().screenHeight;
},
mounted() {
this.$nextTick(function() {
this.formartData = this.chulidata();
this.oldList = [...this.list]
let t = this;
uni.$tm.sleep(40).then(e=>{
uni.createSelectorQuery().in(this).select('.tm-dropDownMenu').boundingClientRect().exec(function(v){
// #ifdef H5
t.vtop = v[0].top+v[0].height+uni.getSystemInfoSync().windowTop;
// #endif
// #ifndef H5
t.vtop = v[0].top+v[0].height;
console.log(v[0]);
// #endif
t.barwidth = v[0].width+'px'
})
})
});
},
methods: {
pickTostring(item){
let p = [];
item.forEach(el=>{
if(typeof(el)=="string"){
p.push(el)
}else if(typeof el == 'object'){
p.push(el.title);
}
})
return p.join("-")
},
chiludis(item) {
return item?.disabled || false;
},
chulidata(list) {
// 处理相关数据格式以保持 一致。
let t = this;
let p = this.$tm.deepClone(list||this.list);
for (let j = 0; j < p.length; j++) {
p[j]['dot'] = 0;
if (p[j]['children']) {
let ic = p[j].children;
if (ic.length > 0) {
for (let k = 0; k < ic.length; k++) {
let children = ic[k]['children'];
if (children) {
if (ic[k]['model'] == 'checkbox'|| ic[k]['model'] == 'listCheckbox' || ic[k]['model'] == 'list' || (ic[k]['model'] == 'radio' && children.length > 0)) {
for (let z = 0; z < children.length; z++) {
let im = children[z];
if (!im.hasOwnProperty('checked')) {
im['checked'] = false;
}
for (let i = 0; i < t.defaultSelected.length; i++) {
let lsitem = t.defaultSelected[i];
if (typeof lsitem === 'object') {
if (lsitem['id'] == im['id']) {
im['checked'] = true;
}
} else {
if (lsitem == im['id']) {
im['checked'] = true;
}
}
}
}
}
}
}
}
}
}
return p;
},
// 重置只重置当前打开的页面数量,并不重置其它页面数据。
resetinit(index) {
let pd = this.formartData[this.activeIndex];
if (pd['children']) {
let ic = pd.children;
if (ic.length > 0) {
for (let k = 0; k < ic.length; k++) {
let children = ic[k]['children'];
if (children) {
if (ic[k]['model'] == 'checkbox'||ic[k]['model'] == 'listCheckbox'||ic[k]['model'] == 'list' || (ic[k]['model'] == 'radio' && children.length > 0)) {
for (let z = 0; z < children.length; z++) {
let im = children[z];
im['checked'] = false;
}
}
} else {
if (ic[k]['model'] == 'slider') {
ic[k].value = 0;
} else if (ic[k]['model'] == 'input') {
ic[k].value = '';
} else if (ic[k]['model'] == 'pickers') {
ic[k].value = [];
} else if (ic[k]['model'] == 'pickersDate') {
ic[k].value = "";
}
}
}
}
}
const p = this.chulidata(this.oldList);
this.formartData.splice(this.activeIndex, 1, p[this.activeIndex]);
},
changeIndex(index) {
let t = this;
let itmod = 659;
clearInterval(itmod)
if (this.activeIndex === index) {
this.activeIndex = -1;
} else {
this.activeIndex = index;
}
this.$emit('change', this.activeIndex);
this.rendIdx = 0;
clearInterval(itmod)
itmod = setInterval(function(){
t.rendIdx+=1;
if(t.rendIdx>t.maxLeng||t.activeIndex==-1){
clearInterval(itmod)
}
},10)
},
// 获取选中以及填写的数据。
getData() {
let p = [...this.formartData];
let xz = [];
for (let i = 0; i < p.length; i++) {
if (p[i]['children']) {
for (let j = 0; j < p[i].children.length; j++) {
let ic = p[i].children[j];
let ps = [];
if (ic.model == 'checkbox' || ic.model == 'radio' || ic.model == 'listCheckbox' || ic.model == 'list') {
if (ic['children']) {
for (let k = 0; k < ic.children.length; k++) {
if (ic.children[k].checked === true) {
ps.push(ic.children[k]);
}
}
}
} else if (ic.model == 'input' || ic.model == 'slider') {
ps.push(ic);
} else if(ic.model == 'pickers'){
ps.push(ic);
}
let pyz = { ...ic };
delete pyz.children;
xz.push({
...pyz,
children: ps
});
}
}
}
this.$emit('confirm', xz);
this.activeIndex = -1;
}
}
};
</script>
<style lang="scss" scoped>
.tm-dropDownMenu {
position: relative;
.tm-dropDownMenu-bar {
position: relative;
z-index: 303;
}
.tm-dropDownMenu-body {
background-color: rgba(0, 0, 0, 0.35);
min-height: 150upx;
position: absolute;
z-index: 304;
width: 100%;
}
}
.optAniopt{
animation: opt 0.2s linear;
}
@keyframes opt{
0%{opacity: 0;}
100%{opacity: 1;}
}
</style>
@@ -0,0 +1,380 @@
<template>
<view class="canvasId-wk">
<!-- #ifdef H5 || APP -->
<canvas
:style="{
width: w,
height: h
}"
v-if="cid"
class="ec-canvas"
:id="cid"
:canvasId="cid"
@touchstart="touchStart"
@touchmove="touchMove"
@touchend="touchEnd"
@error="$emit('error',$event)"
></canvas>
<!-- #endif -->
<!-- #ifndef MP-WEIXIN || H5 || APP || MP-ALIPAY -->
<canvas
:style="{
width: w,
height: h
}"
v-if="canvasId"
class="ec-canvas"
id="ec-canvas"
canvasId="ec-canvas"
@touchstart="touchStart"
@touchmove="touchMove"
@touchend="touchEnd"
@error="$emit('error',$event)"
></canvas>
<!-- #endif -->
<!-- #ifdef MP-WEIXIN || MP-ALIPAY-->
<canvas
:style="{
width: w,
height: h
}"
v-if="canvasId"
type="2d"
class="ec-canvas"
id="ec-canvas"
canvasId="ec-canvas"
@touchstart="touchStart"
@touchmove="touchMove"
@touchend="touchEnd"
@error="$emit('error',$event)"
></canvas>
<!-- #endif -->
</view>
</template>
<script>
/**
* Echart图表
* @param {Number|String} width = [] 默认100%,纯数字时,单位为rpx
* @param {Number|String} height = [] 默认500,纯数字时,单位为rpx,不允许百分比。
* @param {Object} echarts = [] 默认null,百度echart.js插件,默认可以使用本库自带,详见文档。
* @param {Function} init 图表初始化后执行触发事件返回{width,height,chart}
* @param {Function} error 出错时触发。
*/
import WxCanvas from '@/tm-vuetify/tool/function/uni-echarts-canvas.js';
import * as echarts from '@/tm-vuetify/tool/function/echarts.min.js';
function wrapTouch(e) {
for (let i = 0; i < e.mp.touches.length; i += 1) {
const touch = e.mp.touches[i];
touch.offsetX = touch.x;
touch.offsetY = touch.y;
}
return e;
}
function compareVersion(v1, v2) {
v1 = v1.split('.');
v2 = v2.split('.');
const len = Math.max(v1.length, v2.length);
while (v1.length < len) {
v1.push('0');
}
while (v2.length < len) {
v2.push('0');
}
for (let i = 0; i < len; i++) {
const num1 = parseInt(v1[i]);
const num2 = parseInt(v2[i]);
if (num1 > num2) {
return 1;
} else if (num1 < num2) {
return -1;
}
}
return 0;
}
export default {
name:"tm-echarts",
props: {
width: {
type: String | Number,
default: '100%'
},
height: {
type: String | Number,
default: '500'
},
canvasId: {
type: String,
default: 'ec-canvas'
},
lazyLoad: {
type: Boolean,
default: false
},
disableTouch: {
type: Boolean,
default: false
},
throttleTouch: {
type: Boolean,
default: false
}
},
data() {
return {
echarts,
cid:'ec-canvas'
};
},
created() {
this.cid = this.$tm.guid();
},
destroyed() {
try{
this.echarts=null;
this.chart.clear()
this.chart = null;
}catch(e){
//TODO handle the exception
}
},
computed: {
w: function() {
if (this.width == 0 || this.width == '') {
return '100%';
}
let reg = /(vw|vh|rem|em|\%|upx|rpx|auto|px)/g;
if (reg.test(this.width)) {
return this.width;
}
return this.width + 'rpx';
},
h: function() {
let reg = /(vw|vh|rem|em|\%|upx|rpx|auto|px)/g;
if (reg.test(this.height)) {
return this.height;
}
return this.height + 'rpx';
}
},
mounted() {
if (!this.echarts) {
console.warn('未引用echarts');
return;
}
if (!this.lazyLoad) {
this.$nextTick(function() {
try{
this.init();
}catch(e){
//TODO handle the exception
console.error("echarts错误提醒:",e)
}
});
}
},
methods: {
//初始化
init() {
// #ifdef MP-WEIXIN
const version = wx.version.version.split('.').map(n => parseInt(n, 10));
const isValid = version[0] > 1 || (version[0] === 1 && version[1] > 9) || (version[0] === 1 && version[1] === 9 && version[2] >= 91);
if (!isValid) {
console.error(
'微信基础库版本过低,需大于等于 1.9.91。' + '参见:https://github.com/ecomfe/echarts-for-weixin' + '#%E5%BE%AE%E4%BF%A1%E7%89%88%E6%9C%AC%E8%A6%81%E6%B1%82'
);
return;
}
// #endif
let canvasId = this.canvasId;
// #ifdef H5 || APP-PLUS || APP-VUE
canvasId = this.cid;
// #endif
// #ifndef MP-WEIXIN || MP-ALIPAY
this.ctx = uni.createCanvasContext(canvasId, this);
const canvas = new WxCanvas(this.ctx, canvasId, false);
this.echarts.setCanvasCreator(() => canvas);
const query = uni.createSelectorQuery().in(this);
query.select(`#${canvasId}`)
.boundingClientRect(res => {
if (!res) {
setTimeout(() => this.init(), 100);
return;
}
const { width, height } = res;
const canvasDpr = uni.getSystemInfoSync().pixelRatio
this.chart = this.echarts.init(canvas, null, {
width: width,
height: height
});
canvas.setChart(this.chart);
const { handler } = this.chart.getZr();
this.handler = handler;
this.processGesture = handler.proxy.processGesture || (() => {});
this.$emit('init', {
width: res.width,
height: res.height,
chart: this.chart
});
})
.exec();
// #endif
// #ifdef MP-WEIXIN || MP-ALIPAY
const query = uni.createSelectorQuery().in(this)
query
.select('.ec-canvas')
.fields({ node: true, size: true })
.exec(res => {
const canvasNode = res[0].node
this.canvasNode = canvasNode
const canvasDpr = uni.getSystemInfoSync().pixelRatio
const canvasWidth = res[0].width
const canvasHeight = res[0].height
const ctx = canvasNode.getContext('2d')
const canvas = new WxCanvas(ctx, canvasId, true,canvasNode);
this.echarts.setCanvasCreator(() => {
return canvas
})
this.chart = this.echarts.init(canvas, null, {
width: canvasWidth,
height: canvasHeight,
devicePixelRatio:canvasDpr
});
canvas.setChart(this.chart);
const { handler } = this.chart.getZr();
this.handler = handler;
this.processGesture = handler.proxy.processGesture || (() => {});
this.$emit('init', {
width: canvasWidth,
height: canvasHeight,
chart: this.chart
});
})
// #endif
},
//配置图表数据
setOption(dJson) {
if (!this.chart){
uni.$tm.toast("chart未初始化")
return false;
}
this.chart.setOption({...dJson},{notMerge:true});
return true;
},
//获取图表对象。
getChart(FunName,arg){
if (!this.chart){
uni.$tm.toast("chart未初始化")
return false;
}
return this.chart;
},
resize(){
let t = this;
return new Promise((res,rej)=>{
if (!t.chart){
uni.$tm.toast("chart未初始化")
rej(false);
return false;
}
let canvasId = t.canvasId;
// #ifdef H5 || APP-PLUS || APP-VUE
canvasId = t.cid;
// #endif
const query = uni.createSelectorQuery().in(t);
query.select(`.canvasId-wk`)
.boundingClientRect(op => {
const { width, height } = op;
t.chart.resize({width:width,height:height})
res(true);
}).exec()
})
},
canvasToTempFilePath(opt) {
const { canvasId } = this;
this.ctx.draw(true, () => {
wx.canvasToTempFilePath({
canvasId,
...opt
});
});
},
touchStart(e) {
const { disableTouch, chart } = this;
if (disableTouch || !chart || !e.mp.touches.length) return;
const touch = e.mp.touches[0];
this.handler.dispatch('mousedown', {
zrX: touch.x,
zrY: touch.y
});
this.handler.dispatch('mousemove', {
zrX: touch.x,
zrY: touch.y
});
this.processGesture(wrapTouch(e), 'start');
},
touchMove(e) {
const { disableTouch, throttleTouch, chart, lastMoveTime } = this;
if (disableTouch || !chart || !e.mp.touches.length) return;
if (throttleTouch) {
const currMoveTime = Date.now();
if (currMoveTime - lastMoveTime < 240) return;
this.lastMoveTime = currMoveTime;
}
const touch = e.mp.touches[0];
this.handler.dispatch('mousemove', {
zrX: touch.x,
zrY: touch.y
});
this.processGesture(wrapTouch(e), 'change');
},
touchEnd(e) {
const { disableTouch, chart } = this;
if (disableTouch || !chart) return;
const touch = e.mp.changedTouches ? e.mp.changedTouches[0] : {};
this.handler.dispatch('mouseup', {
zrX: touch.x,
zrY: touch.y
});
this.handler.dispatch('click', {
zrX: touch.x,
zrY: touch.y
});
this.processGesture(wrapTouch(e), 'end');
}
}
};
</script>
<style scoped>
.ec-canvas {
width: 100%;
height: 375rpx;
}
</style>
@@ -0,0 +1,78 @@
<template>
<view class="tm-empty flex-center flex-col my-32">
<view class="py-32">
<tm-icons @click="$emit('click')" :color="color" :name="icon?icon:listIcon[model].name" :size="size"></tm-icons>
</view>
<view class="text-size-n" :class="[
`text-${color}`,
'py-12'
]">{{label?label:listIcon[model].label}}</view>
<view>
<slot></slot>
</view>
</view>
</template>
<script>
/**
* 数据空
* @property {String} model = [bug|refresh] 默认bug,待扩展。
* @property {String} color = [] 默认grey-lighten-1,主题颜色。
* @property {Number} size = [] 默认120,图标大小,单位upx
* @property {String} label = [] 默认 '',自定义错误文字。
* @property {String} icon = [] 默认 '',自定义错误图片。
* @property {Function} click 点击图标或者图片时触发。
* @example <tm-empty></tm-empty>
*/
import tmIcons from "@/tm-vuetify/components/tm-icons/tm-icons.vue"
export default {
components:{tmIcons},
name: "tm-empty",
props: {
model: {
type: String,
default: 'refresh'
},
color: {
type: String,
default: 'grey-lighten-1'
},
size: {
type: String|Number,
default: 120
},
label:{
type:String,
default:''
},
//自定义图标图片。
icon:{
type:String,
default:''
}
},
data() {
return {
listIcon: {
bug: {
name: 'icon-bug-report',
label: '软件出现了bug'
},
refresh:{
name: 'icon-redo',
label: '刷新试下'
},
listEmpty:{
name: 'icon-box-fill',
label: '数据为空哦'
}
}
};
}
}
</script>
<style lang="scss">
</style>
@@ -0,0 +1,46 @@
//此库移植自:https://github.com/sitonlotus/vue-digital-flop
let lastTime = 0
const prefixes = 'webkit moz ms o'.split(' ') // 各浏览器前缀
let requestAnimationFrame
let cancelAnimationFrame
let prefix
// #ifdef H5
requestAnimationFrame = window.requestAnimationFrame
cancelAnimationFrame = window.cancelAnimationFrame
// 通过遍历各浏览器前缀,来得到requestAnimationFrame和cancelAnimationFrame在当前浏览器的实现形式
for (let i = 0; i < prefixes.length; i++) {
if (requestAnimationFrame && cancelAnimationFrame) {
break
}
prefix = prefixes[i]
requestAnimationFrame = requestAnimationFrame || window[prefix + 'RequestAnimationFrame']
cancelAnimationFrame = cancelAnimationFrame || window[prefix + 'CancelAnimationFrame'] || window[prefix +
'CancelRequestAnimationFrame']
}
// #endif
// 如果当前浏览器不支持requestAnimationFrame和cancelAnimationFrame,则会退到setTimeout
if (!requestAnimationFrame || !cancelAnimationFrame) {
requestAnimationFrame = function(callback) {
const currTime = new Date().getTime()
// 为了使setTimteout的尽可能的接近每秒60帧的效果
const timeToCall = Math.max(0, 16 - (currTime - lastTime))
const id = setTimeout(() => {
callback(currTime + timeToCall)
}, timeToCall)
lastTime = currTime + timeToCall
return id
}
cancelAnimationFrame = function(id) {
clearTimeout(id)
}
}
export {
requestAnimationFrame,
cancelAnimationFrame
}
+273
View File
@@ -0,0 +1,273 @@
<template>
<view class="tm-flop vertical-align-middle d-inline-block ">
<slot name="default" :value="displayValue">{{ displayValue }}</slot>
</view>
</template>
<script>
/**
* 数字翻牌
* @property {Number} startVal = [] 0,起始值
* @property {Number} endVal = [] 0,最终值
* @property {Number} duration = [] 3000,从起始值到结束值数字变动的时间
* @property {Boolean} autoplay = [] true,是否自动播放
* @property {Number} decimals = [] 0,保留的小数位数
* @property {String} decimal = [] '.',小数点分割符号
* @property {String} separator = [] ',',上了三位数分割的符号
* @property {String} prefix = [] '',前缀
* @property {String} suffix = [] '',后缀
* @property {Boolean} isFrequent = [] false,是否隔一段时间数字跳动,这里的跳动是隔一段时间设置初始值
* @property {Number} frequentTime = [] 5000,跳动间隔时间
* 此库移植自:https://github.com/sitonlotus/vue-digital-flop
*/
import tmTranslate from '@/tm-vuetify/components/tm-translate/tm-translate.vue';
import { requestAnimationFrame, cancelAnimationFrame } from './requestAnimationFrame';
export default {
name: 'tm-flop',
components: {
tmTranslate
},
props: {
/**
* @description 起始值
*/
startVal: {
type: Number,
required: false,
default: 0
},
/**
* @description 最终值
*/
endVal: {
type: Number,
required: false,
default: 2021
},
/**
* @description 从起始值到结束值数字变动的时间
*/
duration: {
type: Number,
required: false,
default: 3000
},
/**
* @description 是否自动播放
*/
autoplay: {
type: Boolean,
required: false,
default: true
},
/**
* @description 保留的小数位数
*/
decimals: {
type: Number,
required: false,
default: 0,
validator(value) {
return value >= 0;
}
},
decimal: {
type: String,
required: false,
default: '.'
},
/**
* @description 三位三位的隔开效果
*/
separator: {
type: String,
required: false,
default: ','
},
/**
* @description 前缀
* @example '¥' 人民币前缀
*/
prefix: {
type: String,
required: false,
default: ''
},
/**
* @description 后缀
* @example
*/
suffix: {
type: String,
required: false,
default: ''
},
/**
* @description 是否具有连贯性
*/
useEasing: {
type: Boolean,
required: false,
default: true
},
/**
* @description 是否隔一段时间数字跳动,这里的跳动是隔一段时间设置初始值
*/
isFrequent: {
type: Boolean,
required: false,
default: false
},
/**
* @description 跳动间隔时间
*/
frequentTime: {
type: Number,
required: false,
default: 5000
}
},
data() {
return {
localStartVal: this.startVal,
displayValue: this.formatNumber(this.startVal),
printVal: null,
paused: false,
localDuration: this.duration,
startTime: null,
timestamp: null,
remaining: null,
rAF: null,
timer: null
};
},
computed: {
countDown() {
return this.startVal > this.endVal;
}
},
watch: {
startVal() {
if (this.autoplay) {
this.start();
}
},
endVal() {
if (this.autoplay) {
this.start();
}
}
},
mounted() {
if (this.autoplay) {
this.start();
}
if (this.isFrequent && this.frequentTime) {
this.timer = setInterval(() => {
this.start(this.randomNum(0, this.endVal));
}, this.frequentTime);
}
this.$emit('mountedCallback');
},
destroy(){
this.destroyed();
},
methods: {
easingFn(t = 0, b = 0, c = 0, d = 0) {
let p = (c * (-Math.pow(2, (-10 * t) / d) + 1) * 1024) / 1023 + b;
return p;
},
randomNum(a, b) {
return Math.round(Math.random() * (b - a) + a);
},
start(startVal) {
this.localStartVal = startVal || this.startVal;
this.startTime = null;
this.localDuration = this.duration;
this.paused = false;
this.rAF = requestAnimationFrame(this.count);
},
pauseResume() {
if (this.paused) {
this.resume();
this.paused = false;
} else {
this.pause();
this.paused = true;
}
},
pause() {
cancelAnimationFrame(this.rAF);
},
resume() {
this.startTime = null;
this.localDuration = +this.remaining;
this.localStartVal = +this.printVal;
requestAnimationFrame(this.count);
},
reset() {
this.startTime = null;
cancelAnimationFrame(this.rAF);
this.displayValue = this.formatNumber(this.startVal);
},
count(timestamp) {
if (!this.startTime) this.startTime = timestamp;
this.timestamp = timestamp;
const progress = timestamp - this.startTime;
this.remaining = this.localDuration - progress;
if (this.useEasing) {
if (this.countDown) {
this.printVal = this.localStartVal - this.easingFn(progress, 0, this.localStartVal - this.endVal, this.localDuration) || 0;
} else {
this.printVal = this.easingFn(progress, this.localStartVal, this.endVal - this.localStartVal, this.localDuration);
}
} else {
if (this.countDown) {
this.printVal = this.localStartVal - (this.localStartVal - this.endVal) * (progress / this.localDuration);
} else {
this.printVal = this.localStartVal + (this.endVal - this.localStartVal) * (progress / this.localDuration);
}
}
if (this.countDown) {
this.printVal = this.printVal < this.endVal ? this.endVal : this.printVal;
} else {
this.printVal = this.printVal > this.endVal ? this.endVal : this.printVal;
}
this.displayValue = this.formatNumber(this.printVal);
if (progress < this.localDuration) {
this.rAF = requestAnimationFrame(this.count);
} else {
this.$emit('callback');
}
},
isNumber(val) {
return !isNaN(parseFloat(val));
},
formatNumber(num) {
num = num.toFixed(this.decimals);
num += '';
const x = num.split('.');
let x1 = x[0];
const x2 = x.length > 1 ? this.decimal + x[1] : '';
const rgx = /(\d+)(\d{3})/;
if (this.separator && !this.isNumber(this.separator)) {
while (rgx.test(x1)) {
x1 = x1.replace(rgx, '$1' + this.separator + '$2');
}
}
return this.prefix + x1 + x2 + this.suffix;
}
},
destroyed() {
cancelAnimationFrame(this.rAF);
this.timer && clearInterval(this.timer);
}
};
</script>
<style></style>
@@ -0,0 +1,431 @@
<!-- 悬浮按钮 -->
<template>
<view class="nobody fulled"
:class="[absolute?'fulled':'',fixed?'d-inline-block':'',absolute&&position=='bottom'?'flex-end-center height100':'']">
<view
:class="[absolute?'fulled':'',absolute?'relative':'',absolute&&position=='top'?'flex-center':'',absolute&&position=='bottom'?'flex-end-center height100':'',fixed?'d-inline-block':'']">
<view class="flotbtnId" :style="pos" :class="[absolute?'absolute':'',fixed?'fixed':'']">
<slot name="default">
<tm-button
v-if="isRender"
@contact="$emit('contact', $event)"
@error="$emit('error', $event)"
@getphonenumber="$emit('getphonenumber', $event)"
@getuserinfo="$emit('getuserinfo', $event)"
@launchapp="$emit('launchapp', $event)"
@opensetting="$emit('opensetting', $event)"
@longpress="$emit('longpress', $event)"
@touchcancel="$emit('touchcancel', $event)"
:open-type="openType"
@click="click"
:showValue="showText" vertical :label="label" :fontSize="fontSize"
:iconSize="iconSize" :theme="color_tmeme" round="rouned" :font-color="fontColor" :bgcolor="bgcolor"
:size="size" :width="width" :height="width" :icon="icon" fab>
</tm-button>
</slot>
<view v-if="(show || showActions)&&actions.length>0" class="menulistAction" :class="[actionsPos]">
<view class="menulistAction_item" v-for="(item,index) in actions" :key="index">
<tm-button :fllowTheme="false" fab @click="itemChange(index)" iconSize="40" :icon="item.icon" :theme="item.color" size="m"></tm-button>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
/**
* 悬浮按钮
* @property {Array} actions = [] 默认:[],悬浮按钮展开的子按钮。格式[{icon,color}]
* @property {String} actions-pos = [top | left | bottom | right] 默认:top,子菜单按钮显示的方向
* @property {String|Boolean} show-actions = [false|true] 默认:false,始终展开子菜单。点击子菜单后不消失.
* @property {String|Boolean} click-actions-hiden = [false|true] 默认:true,点击菜单后是否隐藏所有子菜单。
* @property {String} size = [xs|s|m|n|l|g] 默认:n, 按钮大小。
* @property {String|Number} width = [] 默认:NaN, 自定义按钮大小。
* @property {String|Boolean} show-text = [false|true] 默认:false, 是否显示文字。下下排列结构上图标,下文字。
* @property {String} label = [] 默认:'', 主按钮下方的文字
* @property {String} icon = [] 默认:icon-plus, 默认图标.
* @property {String} bgcolor = [] 默认:"", 自定义-背景颜色
* @property {String|Number} font-size = [22|23|24|26|28] 默认:22, 文字大小
* @property {String} font-color = [] 默认:'', 文字颜色
* @property {String|Number} icon-size = [] 默认:'36', 图标大小。
* @property {String} color = [] 默认:primary,主题颜色
* @property {String|Boolean} absolute = [] 默认:false, 相对父组件定位。
* @property {String|Boolean} fixed = [] 默认:true, 绝对定位,根据屏幕定位。
* @property {String|Boolean} safe = [true|false] 默认:true,// 是否开启底部安全区域。
* @property {String} position = [ topLeft | topRight | bottomRight | bottomLeft|top|bottom|left|right] 默认:bottomRight, 在absolute模式下没有left和right剧中。fixed模式包含所有模式。
* @property {Array} offset = [] 默认: [16, 16], 单位upx,自定义偏移量,left,right
* @property {Function} click 主按钮点击触发的事件。
* @property {Function} change 当有子菜单按钮时,点击子按钮触发事件,并返回顺序Index
* @example <tm-flotbutton label="发文" show-text="true" ></tm-flotbutton>
*/
import tmButton from "@/tm-vuetify/components/tm-button/tm-button.vue"
export default {
name:"tm-flotbutton",
components:{tmButton},
props: {
// 子菜单如果有。
actions: {
type: Array,
default: () => {
return [];
}
},
// 同原生btn相同。contact|getPhoneNumber|getUserInfo|launchapp|share|openSetting
openType: {
type: String,
default: ''
},
// 子菜单按钮显示的方向。top | left | bottom | right
actionsPos: {
type: String,
default: 'top'
},
// 始终展开子菜单。
showActions: {
type: String | Boolean,
default: false
},
// 点击菜单后是否隐藏所有子菜单。
clickActionsHiden: {
type: String | Boolean,
default: true
},
// 同button xs,s,m,n,l,g
size: {
type: String,
default: 'n'
},
width: {
type: String | Number,
default: 100
},
height: {
type: String | Number,
default: 100
},
// 是否显示询问文字
showText: {
type: String | Boolean,
default: false
},
// 底部文字。需要和上方同时使用。
label: {
type: String,
default: ''
},
icon: {
type: String,
default: 'icon-plus'
},
// 自定义-背景颜色
bgcolor: {
type: String | Array,
default: ''
},
// 文字大小。
fontSize: {
type: Number | String,
default: 22
},
// 定义文字颜色
fontColor: {
type: String,
default: 'white'
},
// 图标大小。
iconSize: {
type: Number | String,
default: 36
},
// 主题颜色
color: {
type: String,
default: 'primary'
},
// 根据像组件定位四个角和上下居中位置。6个位置。
absolute: {
type: String | Boolean,
default: false
},
//是否开启询问安全距离。如果开启了,会计算底部偏差。
safe:{
type: String | Boolean,
default: true
},
// 要的屏幕的宽高定位四个角和上下左右共8个位置
fixed: {
type: String | Boolean,
default: true
},
// topLeft | topRight | bottomRight | bottomLeft 。在absolute模式下没有left和right剧中。只有top和bottom剧中模式。fixed模式包含所有模式。
position: {
type: String,
default: 'bottomRight'
},
// 单位upx
offset: {
type: Array,
default: () => {
return [16, 16];
}
},
// 跟随主题色的改变而改变。
fllowTheme:{
type:Boolean|String,
default:true
}
},
computed: {
offsets: {
get: function() {
return this.offset;
},
set: function() {
try {
this.offset = [uni.upx2px(this.offset[0]), uni.upx2px(this.offset[1])];
} catch (e) {
this.offset = [0, 0];
}
}
},
color_tmeme:function(){
if(this.$tm.vx.state().tmVuetify.color!==null&&this.$tm.vx.state().tmVuetify.color && this.fllowTheme){
return this.$tm.vx.state().tmVuetify.color;
}
return this.color;
},
pos: {
get: function() {
return this.thisPos;
},
set:function(){
this.thisPos = this.posfun();
}
}
},
data() {
return {
position_info: [],
show: false,
isRender:false,
thisPos:''
};
},
async mounted() {
this.init();
},
methods: {
init(){
let t = this;
this.$nextTick(function(){
t.isRender=true;
t.position_info = [{width:uni.upx2px(this.width),height:uni.upx2px(this.height)}];
t.thisPos = t.posfun();
})
// #ifdef H5
window.addEventListener('scroll',function(){
t.thisPos = t.posfun();
})
// #endif
},
getsafeJl(){
let sy = uni.getSystemInfoSync();
// #ifdef MP
return Math.abs(sy.screenHeight - sy.safeArea.bottom);
// #endif
// #ifdef H5
return Math.abs(sy.windowHeight - sy.safeArea.height);
// #endif
// #ifdef APP
return Math.abs(sy.safeArea.bottom - sy.safeArea.height);
// #endif
return 24;
},
posfun(){
if (this.absolute && !this.fixed) {
if (this.position == 'topLeft') {
return `transform:translateY(${this.offset[1]}px);left:${this.offset[0]}px`;
} else if (this.position == 'topRight') {
return `transform:translateY(${this.offset[1]}px);right:${this.offset[0]}px;`
} else if (this.position == 'bottomRight') {
return `transform:translateY(${this.offset[1]}px);right:${this.offset[0]}px;`;
} else if (this.position == 'bottomLeft') {
return `btransform:translateY(${this.offset[1]}px);left:${this.offset[0]}px`;
} else if (this.position == 'top') {
return `transform:translateX(${this.offset[0]}px) translateY(${this.offset[1]}px)`
} else if (this.position == 'bottom') {
return `transform:translateX(${this.offset[0]}px) translateY(${this.offset[1]}px)`
}
}
if (!this.absolute && this.fixed) {
if (this.position == 'topLeft') {
return `top:${ this.offset[1]}px;left:${this.offset[0]}px`;
} else if (this.position == 'topRight') {
return `top: ${this.offset[1]}px;right: ${this.offset[0]}px`;
} else if (this.position == 'bottomRight') {
let safbo = this.getsafeJl()
if(!this.safe) safbo=0
return `bottom: ${this.offset[1]+safbo}px;right: ${this.offset[0]}px;`;
} else if (this.position == 'bottomLeft') {
let sy = uni.getSystemInfoSync();
let safbo = this.getsafeJl()
if(!this.safe) safbo=0
return `bottom:${ this.offset[1]+safbo}px;left:${this.offset[0]}px;`;
} else if (this.position == 'top') {
let js = uni.getSystemInfoSync();
let left = js.windowWidth;
if (this.position_info.length > 0) {
let w = this.position_info[0].width;
return `transform:translateX(${this.offset[0]}px) translateY(${this.offset[1]}px);top:0;left:${(left-w)/2}px;`;
}
} else if (this.position == 'bottom') {
let safbo = this.getsafeJl()
if(!this.safe) safbo=0
let js = uni.getSystemInfoSync();
let left = js.windowWidth;
if (this.position_info.length > 0) {
let w = this.position_info[0].width;
console.log(w);
return `transform:translateX(${this.offset[0]}px) translateY(${this.offset[1]}px);bottom:${safbo}px;left:${(left-w)/2}px`;
} else if (this.position == 'left') {
let js = uni.getSystemInfoSync();
let left = js.windowHeight;
if (this.position_info.length > 0) {
let w = this.position_info[0].height;
return `transform:translateX(${this.offset[0]}px) translateY(${this.offset[1]}px);left:0;top:${(left-w)/2}px`;
}
} else if (this.position == 'right') {
let js = uni.getSystemInfoSync();
let left = js.windowHeight;
if (this.position_info.length > 0) {
let w = this.position_info[0].height;
return {
transform: `translateX(${this.offset[0]}px) translateY(${this.offset[1]}px)`,
right: 0,
top: (left - w) / 2 + 'px',
};
`transform:translateX(${this.offset[0]}px) translateY(${this.offset[1]}px);right:0;top:{(left-w)/2}px`;
}
}
}
}
},
click(e) {
this.$emit('click', e);
this.show = !this.show;
},
itemChange(index) {
this.$emit('change', index);
if (!this.clickActionsHiden) return;
this.show = !this.show;
}
},
}
</script>
<style lang="scss" scoped>
.nobody {
.menulistAction {
.menulistAction_item {
position: relative;
// #ifdef H5
animation: sscsl 0.5s;
// #endif
}
&.top {
position: absolute;
bottom: 130upx;
left: 10upx;
.menulistAction_item {
margin-bottom: 10px;
}
}
&.left {
position: absolute;
bottom: 10upx;
display: flex;
flex-flow: row;
right: 140upx;
.menulistAction_item {
margin-left: 10px;
}
}
&.bottom {
position: absolute;
top: 140upx;
display: flex;
flex-flow: column;
left: 10upx;
.menulistAction_item {
margin-top: 10px;
}
}
&.right {
position: absolute;
bottom: 10upx;
display: flex;
flex-flow: row;
left: 140upx;
.menulistAction_item {
margin-left: 10px;
}
}
}
}
@keyframes sscsl {
from {
transform: scale(0.5);
}
to {
transform: scale(1);
}
}
.height100 {
height: 100%;
}
.fixed,
.absolute {
z-index: 400;
bottom: 0;
}
</style>
@@ -0,0 +1,206 @@
<template>
<view class="tm-flowLayout relative">
<view class="tm-flowLayout-hidden absolute fulled" >
<view v-for="(item, index) in list2" :key="index" style="width: 350rpx;" :class="'tm-flowLayout-hidden-'+index" >
<tm-images @error="loadimg($event,false,index)" @load="loadimg($event,true,index)" :src="item.image"></tm-images>
</view>
</view>
<view class="tm-flowLayout-cm flex-between">
<view class="tm-flowLayout-left" style="width: 48.6%;">
<block v-for="(item, index) in dataList[0]" :key="index">
<view @click.stop="onclick(index,0)" class="tm-flowLayout-item mb-20 fulled" :class="['tm-flowLayout-item_'+index]" >
<slot name="left" :hdata="{item:item,dirIndex:0,childrenIndex:index}">
<tm-images v-if="item.image" :src="item.image"></tm-images>
</slot>
</view>
</block>
</view>
<view class="tm-flowLayout-right" style="width: 48.6%;">
<block v-for="(item, index) in dataList[1]" :key="index">
<view @click.stop="onclick(index,1)" class="tm-flowLayout-item mb-20 fulled" :class="['tm-flowLayout-item_'+index]">
<slot name="right" :hdata="{item:item,dirIndex:1,childrenIndex:index}">
<tm-images v-if="item.image" :src="item.image"></tm-images>
</slot>
</view>
</block>
</view>
</view>
<view v-if="isLoading" class="flex-shrink fulled">
<tm-loadding></tm-loadding>
</view>
</view>
</template>
<script>
/**
* 瀑布流组件
* @property {Function} click 点击项目时触发
* @property {Function} load 当前列表加载完成发出的事件。
* @property {String} model = [rank|desc] desc下正时排序,所有加载完成再进行显示,rank随机,哪个先加载哪个先排前面。
*/
import tmImages from "@/tm-vuetify/components/tm-images/tm-images.vue"
import tmLoadding from "@/tm-vuetify/components/tm-loadding/tm-loadding.vue"
import tmIcons from "@/tm-vuetify/components/tm-icons/tm-icons.vue"
export default {
components:{tmImages,tmLoadding,tmIcons},
name: 'tm-flowLayout',
props:{
model:{
type:String,
default:'rank' //desc下正时排序,所有加载完成再进行显示,rank随机,哪个先加载哪个先排前面。
}
},
data() {
return {
list2:[],
dataList:[
[],[]
],
colHeight:[0,0],
minWidth:0,
isLoading:true,
};
},
async mounted() {
let p = await this.$Querey('.tm-flowLayout-left').catch(e=>{});
this.minWidth = p[0].width;
},
methods: {
onclick(index,dirIndex){
this.$emit('click',
{
childrenIndex:index,
dirIndex:dirIndex,
item:this.dataList[dirIndex][index]
});
},
//修改或者替换列表数据
changeItemData(dirIndex,childrenIndex,item){
this.dataList[dirIndex].splice(childrenIndex,1,item);
},
//删除一项列表数据
delItemData(dirIndex,childrenIndex){
this.colHeight.splice(dirIndex,1,this.colHeight[dirIndex] - this.dataList[dirIndex][childrenIndex].height)
this.dataList[dirIndex].splice(childrenIndex,1);
this.list2.splice(this.dataList[dirIndex][childrenIndex].index,1);
this.$nextTick(function(){
this.sucessRank();
})
},
//向列表添加数据
pushData(list){
let prIdx_i = this.list2.length;
if(!Array.isArray(list)||typeof list =='undefined'){
return false;
}
for(let i=0;i<list.length;i++){
this.list2.push({
index:i+prIdx_i,
isLoad:false,
image:"",
width:0,
height:0,
...list[i]
})
}
},
//获取内部列表数据
getDataList(){
return this.dataList;
},
async loadimg(event,isLoad,index) {
// console.log(event,index);
this.isLoading = true;
let ps = this.list2[index];
ps.isLoad = true;
if(isLoad==false){
ps.width = this.minWidth;
ps.height = this.minWidth;
}else{
ps.width = this.minWidth;
ps.height = ps.height+event.height;
}
if(this.list2.length==0&&this.dataList[0].length==0&&this.dataList[1].length==0){
this.isLoading = false;
return;
}
this.list2.splice(index,1,ps);
if(this.model=='desc'){
this.sucessRank();
}else if(this.model == 'rank'){
let indexCol = this.colHeight[0]<=this.colHeight[1]?0:1;
this.dataList[indexCol].push(this.list2[index]);
this.colHeight.splice(indexCol,1,this.colHeight[indexCol]+this.list2[index].height);
if(this.isAllLoading()===false) return;
this.isLoading = false;
}
},
isAllLoading(){
let isAllload = true;
for(let i=0;i<this.list2.length;i++){
if(this.list2[i].isLoad===false){
isAllload = false;
break;
}
}
if(isAllload===true){
// 当前列表加载完成发出的事件
this.$emit('load',this.dataList)
}
return isAllload;
},
sucessRank(){
if(this.isAllLoading()===false) return;
this.isLoading = false;
for(let i=0;i<this.list2.length;i++){
// let p = await this.getLeftRightHeight();
let index = this.colHeight[0]<=this.colHeight[1]?0:1;
this.dataList[index].push(this.list2[i]);
this.colHeight.splice(index,1,this.colHeight[index]+this.list2[i].height)
}
},
//清空瀑布流数据
clear(){
this.list2 = [];
this.dataList = [[],[]];
this.isLoading = false;
},
getLeftRightHeight(index){
let t = this;
return new Promise((resolve,rejecvt)=>{
let q = uni.createSelectorQuery().in(t);
q.select(".tm-flowLayout-left").boundingClientRect();
q.select(".tm-flowLayout-right").boundingClientRect();
q.exec((res)=>{
resolve([res[0].height,res[1].height])
})
})
}
},
};
</script>
<style lang="scss">
.tm-flowLayout {
.tm-flowLayout-hidden{
width: 100%;
height: 100px;
overflow-y: auto;
left: -300%;
top: 0;
}
.tm-flowLayout-cm{
align-items: flex-start;
}
}
</style>
+217
View File
@@ -0,0 +1,217 @@
<template>
<view class="tm-form fulled">
<slot></slot>
</view>
</template>
<script>
/**
* 表单组件
* @property {String} url = [] 提交时的网址。
* @property {String} method = [POST | GET] 提交时的的方法
* @property {Object} header = [] 提交时的头部对象数据
* @property {Function} submit 验证成功会返回数据集合,验证失败返回false,点击提交按钮时触发。表单内部一定放一个tm-button,注意navtie-type="form"表单专用属性,<tm-button navtie-type="form">提交/tm-button>
* @property {Function} request 请求成功触发的事件,返回请求的数据。
*
*/
export default {
name:"tm-form",
props:{
//提交时的网址。如果填写提交时,不会请求数据。
url:{
type:String,
default:''
},
//请求方法
method:{
type:String,
default:'POST' //POST | GET
},
//头部数据
header:{
type:Object,
default:()=>{
return {};
}
}
},
data() {
return {
};
},
mounted() {
this.$nextTick(async function(){
//#ifdef APP-VUE || APP-PLUS
await uni.$tm.sleep(50);
//#endif
this.init()
})
},
methods: {
findeChildren(cl){
let t = this;
let mubiao = ['tm-input','tm-button','tm-groupradio','tm-groupcheckbox','tm-upload','tm-rate','tm-slider','tm-stepper','tm-switch']
let jg = [];
function start(item){
let tag = item.$options?.name;
let index = mubiao.findIndex(citem=>{
return tag == citem;
})
if(index>-1){
if(tag=='tm-button'&&item.$props.navtieType=='form'){
item.onclick = function(){
t.$nextTick(function(){
t.formSub(jg);
})
}
}else{
if(tag!='tm-button'&&item.$props.name!=''){
jg.push(item)
}
}
}else{
if(Array.isArray(item.$children)){
item.$children.forEach(zcl=>{
start(zcl)
})
}
}
}
start(cl);
},
init() {
let cl = this;
//#ifdef H5 || APP-VUE || APP-PLUS
cl = this.$children[0];
//#endif
this.$nextTick(function(){
this.findeChildren(cl);
})
},
async formSub(cl){
let bdData={};
let verify=true;
for(let i=0;i<cl.length;i++){
let item = cl[i];
let tagname = item.$options?.name;
if(tagname=='tm-upload'){
bdData[item.$props.name] = item.getFile();
}else if(tagname=='tm-input'){
if(item.$props.required){
if(!item.verifyInput()){
verify=false;
break;
return;
}
}
bdData[item.$props.name] = item.$props.value;
}else if(tagname=='tm-groupradio' || tagname== 'tm-groupcheckbox'){
bdData[item.$props.name] = item.getVal();
}else if(tagname=='tm-slider' || tagname=='tm-stepper' || tagname=='tm-rate' || tagname=='tm-switch'){
bdData[item.$props.name] = item.$props.value;
}
}
if(verify===true){
this.$emit('submit',bdData)
if(this.url!==""){
let rulst = null;
if(this.method.toLocaleLowerCase()=='get'){
rulst = await uni.$tm.request.get(this.url,bdData,this.header)
}else if(this.method.toLocaleLowerCase()=='post'){
rulst = await uni.$tm.request.post(this.url,bdData,this.header)
}
this.$emit('request',rulst)
}
}else{
this.$emit('submit',false)
}
},
findeChildren_off(cl){
let t = this;
let mubiao = ['tm-input','tm-button','tm-groupradio','tm-groupcheckbox','tm-upload','tm-rate','tm-slider','tm-stepper','tm-switch']
let jg = [];
function start(item){
let tag = item.$options?.name;
let index = mubiao.findIndex(citem=>{
return tag == citem;
})
if(index>-1){
if(tag=='tm-button'&&item.$props.navtieType=='form'){
}else{
if(tag!='tm-button'&&item.$props.name!=''){
jg.push(item)
}
}
}else{
if(Array.isArray(item.$children)){
item.$children.forEach(zcl=>{
start(zcl)
})
}
}
}
start(cl);
return jg;
},
//手动获取当前表单对象数据,Promise风格。
getFormData(){
let t = this;
return new Promise((su,fl)=>{
t.$nextTick(function(){
let clPren = t.$children[0];
//#ifdef H5
clPren = t.$children[0].$children[0].$children[0];
//#endif
let cl = t.findeChildren_off(clPren);
let bdData={};
let verify=true;
for(let i=0;i<cl.length;i++){
let item = cl[i];
let tagname = item.$options?.name;
if(tagname=='tm-upload'){
bdData[item.$props.name] = item.getFile();
}else if(tagname=='tm-input'){
bdData[item.$props.name] = item.$props.value;
}else if(tagname=='tm-groupradio' || tagname== 'tm-groupcheckbox'){
bdData[item.$props.name] = item.getVal();
}else if(tagname=='tm-slider' || tagname=='tm-stepper' || tagname=='tm-rate' || tagname=='tm-switch'){
bdData[item.$props.name] = item.$props.value;
}
}
su(bdData);
})
})
}
},
}
</script>
<style lang="scss">
</style>
@@ -0,0 +1,29 @@
<template>
<view class="tm-formGeneration">
</view>
</template>
<script>
export default {
name:'tm-formGeneration',
data() {
return {
formJson:[
{
"name":"input",
"type":"text",
"subName":"user",
"title":"登录用户名",
"suffix":"",
"required":true,
}
]
};
}
}
</script>
<style lang="scss">
</style>
@@ -0,0 +1,72 @@
<template>
<view
class="tm-fullView fulled "
:class="[$tm.vx.state().tmVuetify.black ? 'grey-darken-6 bk' : '',classCus,text?'text':'',bgColor]"
:style="{
width: width?(width + 'px'):'100%',
minHeight: height?(height + 'px'):'100%'
}"
>
<slot name="default" :info="{width:width,height:height,sysinfo:sysinfo}"></slot>
</view>
</template>
<script>
/**
* 满屏
* @description 满屏,即占满一屏,用于单页使用。
* @property {String|Array} class-name = [] 默认 '',自定义类
* @property {String} bg-color = [] 默认 'grey',背景颜色
* @property {Boolean} text = [true|false] 默认 true,淡背景模式。
* @example <tm-fullView ></tm-fullView>
*/
export default {
name: 'tm-fullView',
props:{
className:{
type:String|Array,
default:''
},
bgColor:{
type:String,
default:'grey'
},
text:{
type:Boolean|String,
default:true
}
},
computed:{
classCus:function(){
if(typeof this.className === 'string') return this.className;
if(typeof this.className === 'object' && Array.isArray(this.className)){
return this.className.join(" ");
}
}
},
data() {
return {
width: 0,
height: 0,
sysinfo:null,
};
},
created(){
this.sysinfo = uni.getSystemInfoSync();
this.width = this.sysinfo.windowWidth;
this.height = this.sysinfo.windowHeight;
},
mounted() {
}
};
</script>
<style lang="scss" scoped>
.tm-fullView{
.tm-fullView-body{
margin: 0;
padding:0;
}
}
</style>
+38
View File
@@ -0,0 +1,38 @@
<template>
<view class="tm-gap" :class="[vertical?'d-inline-block':'d-block']"
:style="{
width:vertical?gutter+'rpx':'auto',
height:!vertical?gutter+'rpx':'auto',
}"
></view>
</template>
<script>
/**
* 间隙
* @param {Number} gutter = [] 24 间隙
* @param {Boolean} vertical = [true/false] 默认:false 是否竖向。
*/
export default {
name:"tm-gap",
props:{
gutter:{
type:Number|String,
default:24
},
vertical:{
type:Boolean|String,
default:false
}
},
data() {
return {
};
}
}
</script>
<style lang="scss">
</style>
+177
View File
@@ -0,0 +1,177 @@
<template>
<view class="tm-grid ">
<tm-row :custom-class="border ? ' border-grey-lighten-4-a-1 round-5 overflow ' +(black_tmeme?' bk ':'') : ''">
<tm-col
:maxCol="maxGrid"
:custom-class="(border ? ((index+1)<=(colNum-1)*grid?'border-grey-lighten-4-b-1 ':'') + ((index + 1) % grid ? 'border-grey-lighten-4-r-1 ' : ' ') : '') + (black_tmeme?' bk ':'')"
v-for="(item, index) in listData" :key="index" :grid="col" justify="center"
align="middle"
:color="bgColor"
>
<view @click.stop="onclick(index,item)" class="tm-grid-hover flex-center flex-col " :style="{height:height_s+'px'}">
<view class=" pb-6 flex-shrink px-10 " style="">
<view class="tm-grid-icon flex-shrink" v-if="item.dot" >
<tm-badges :color="item.color?item.color:color_tmeme" @click="clickDot(index,item)" v-if="item.dotIcon" :offset="[10,0]" :dot="false" :icon="item.dotIcon" ></tm-badges>
<tm-badges @click="clickDot(index,item)" v-if="!item.dotIcon" :offset="[10,0]" :dot="true" ></tm-badges>
</view>
<tm-icons :prefx="item['prefx']?item['prefx']:''" :color="item.color?item.color:color_tmeme" :size="item.iconSize?item.iconSize:iconSize"
:name="item.icon?item.icon:''"></tm-icons>
</view>
<view class="flex-center fulled">
<text class="text-size-s text-align-center"
:class="[item.fontColor?'text-'+item.fontColor:'text-'+fontColor]">{{ item.text?item.text:'' }}</text>
</view>
</view>
</tm-col>
</tm-row>
</view>
</template>
<script>
/**
* 九宫格
* @description 九宫格,快速建立宫格列表,如果想要个性化推荐tm-row tm-col建立。
* @property {Boolean} border = [] 默认:false,是否显示边框
* @property {Boolean} black = [] 默认:null,暗黑模式
* @property {String} color = [] 默认:primary,图标主题色。
* @property {String} bg-color = [grey-lighten-5] 默认:'',宫格背景主题色。
* @property {String} font-color = [] 默认:grey-darken-1,文字颜色
* @property {Number} icon-size = [] 默认:40,图标大小
* @property {Number} height = [] 默认:140,高度,单位upx
* @property {Number} maxGrid = [] 默认:12,布局的列表,比如我想一行5个,就可以用到此属性,设置为10,然后grid=2即可。
* @property {Number} grid = [] 默认:4,一行几个。
* @property {Array} list = [] 默认:[],列表数据。
* @property {Function} change 项目被点击,返回的参数:{index:索引,data:项目的数据}
* @property {Function} click-dot 右上角的角标被点击触发。
* @example <tm-grid :list="[{ text: '应用配置', icon: 'icon-aliwangwang' , size:40 }]"></tm-grid>
*/
import tmRow from "@/tm-vuetify/components/tm-row/tm-row.vue"
import tmCol from "@/tm-vuetify/components/tm-col/tm-col.vue"
import tmBadges from "@/tm-vuetify/components/tm-badges/tm-badges.vue"
import tmIcons from "@/tm-vuetify/components/tm-icons/tm-icons.vue"
export default {
components:{tmRow,tmCol,tmBadges,tmIcons},
name: 'tm-grid',
props: {
black:{
type:String|Boolean,
default:null
},
// 是否显示边框。
border: {
type: Boolean,
default: false
},
// 主题色。
color: {
type: String,
default: 'primary'
},
// 背景主题色。
bgColor: {
type: String,
default: ''
},
fontColor: {
type: String,
default: 'grey-darken-1'
},
// 一行几个。
grid: {
type: Number,
default: 4
},
iconSize: {
type: Number,
default: 40
},
height: {
type: Number,
default: 140
},
list: {
type: Array,
default: () => {
return [];
}
},
// 跟随主题色的改变而改变。
fllowTheme:{
type:Boolean|String,
default:true
},
//默认计算方式是12列布局。
maxGrid:{
type:Number,
default:12
}
},
computed: {
black_tmeme: function() {
if (this.black !== null) return this.black;
return this.$tm.vx.state().tmVuetify.black;
},
color_tmeme:function(){
if(this.$tm.vx.state().tmVuetify.color!==null&&this.$tm.vx.state().tmVuetify.color && this.fllowTheme){
return this.$tm.vx.state().tmVuetify.color;
}
return this.color;
},
col: function() {
return isNaN(this.maxGrid / this.grid) ? 4 : this.maxGrid / this.grid;
},
listData: function() {
let gs = this.grid - Math.floor(this.list.length / this.grid);
return this.list;
},
gridNum:function(){
return this.grid;
},
colNum:function(){
let ts = parseInt(this.listData.length/this.grid);
ts = this.listData.length/this.grid>ts?ts+1:ts;
return ts;
},
height_s:function(){
return uni.upx2px(this.height)||70
}
},
data() {
return {
hoverClass: ''
};
},
methods: {
onclick(index, item) {
this.$emit('change', {
index: index,
data: item
})
},
clickDot(index, item) {
this.$emit('click-dot', {
index: index,
data: item
})
}
},
};
</script>
<style lang="scss" scoped>
.tm-grid{
.tm-grid-icon{
}
}
</style>
@@ -0,0 +1,105 @@
<template>
<view class="tm-groupButton " :class="[`mx-${margin[0]}`,`my-${margin[1]}`]">
<view class="overflow fulled " :class="[`round-${round}`]">
<slot></slot>
</view>
</view>
</template>
<script>
/**
* 按钮组
* @param {Number} round = [] 默认4,圆角
* @param {Number} margin = [] 默认 [0,24] 上下间距
* @param {Number} active-color = [] 默认 primary 点按激活的颜色主题
* @property {Function} change 点按按钮选中时的触发,返回当前索引值。
*/
export default {
name:"tm-groupButton",
props: {
round: {
type: Number|String,
default: 4
},
margin: {
type: Array,
default: ()=>[0,24]
},
activeColor: {
type: String,
default: 'primary'
},
},
data() {
return {
activeIndex:null,
};
},
mounted() {
this.$nextTick(function(){
this.inits();
})
},
updated(){
this.$nextTick(function () {
this.inits();
})
},
methods: {
inits() {
let t = this;
let ch = [];
// #ifndef H5
ch = this.$children;
// #endif
// #ifdef H5
ch = this.$children[0].$children[0].$children;
// #endif
ch.forEach((item, index) => {
if (item.$options.name === 'tm-button') {
let str = '';
if(index==0){
str = ' round-l-'+t.round+' round-r-0'
}else if(index==ch.length-1){
str = ' round-r-'+t.round+' round-l-0'
}else{
str = ' round-l-0 round-r-0'
}
item.customClass_puted=" ma-0 "+str;
item.customDense_puted=true;
item.onclick=function(){
t.colorDefault();
item.$emit('click',...arguments)
t.$emit('change',index)
t.activeIndex=index;
item.color_tmeme = t.activeColor
}
if(index!==0){
item.setConfigStyle('border-l-1')
}
}
});
},
colorDefault(){
let t = this;
let ch = [];
// #ifndef H5
ch = this.$children;
// #endif
// #ifdef H5
ch = this.$children[0].$children[0].$children;
// #endif
ch.forEach((item, index) => {
if (item.$options.name === 'tm-button') {
item.color_tmeme = item.theme
}
});
}
}
}
</script>
<style lang="scss">
</style>
@@ -0,0 +1,124 @@
<template>
<view class="tm-groupcheckbox " :class="[customClass]">
<slot></slot>
</view>
</template>
<script>
/**
* 复选框组
* @description 此组件必须,配合tm-checkbox组件使用,不可单独使用。
* @property {Number} max = [999] 最大选择数量
* @property {String} name = [] 默认:'',提交表单时的的字段名称标识
* @property {String} customClass = [] 默认:'',自定义class
* @property {Function} change 任何一个checkekBox改变将会触发此事件,并携带选中的数组数据。
* @property {Function} error 如果超过最大选择数量将会触发此事件。
* @example <tm-groupcheckbox><tm-checkbox v-model="checked" label="苹果"></tm-checkbox></tm-groupcheckbox>
*
*/
export default {
name:'tm-groupcheckbox',
props:{
// 最大选择数量
max:{
type:Number,
default:9999
},
//提交表单时的的字段名称
name:{
type:String,
default:''
},
customClass:{
type:String,
default:''
}
},
data() {
return {
};
},
mounted() {
this.$nextTick(function(){
this.$emit('change',this.getVal())
})
},
methods: {
change(e) {
this.$emit('change',e)
},
// 获取选中的结果 。
getVal(){
let box = [];
function findchild(p,index){
let preat = p;
if(preat.$options?.name==='tm-checkbox'){
if(preat.changValue){
box.push({index:index,value:preat.name,checked:preat.changValue})
}
}else{
if(preat.$children.length>0){
preat.$children.forEach(item=>{
findchild(item,index++);
})
}
}
};
findchild(this,0);
return box;
},
// 反选。如果一个都没选择。反选就相当于全选。如果是全选,则结果是全部不选。
// 执行完毕后。不能立即使用getVal获取结果需要this.$nextTick
reverseVal(){
function findchild(p,index){
let preat = p;
if(preat.$options?.name==='tm-checkbox'){
preat.changValue = !preat.changValue;
}else{
if(preat.$children.length>0){
preat.$children.forEach(item=>{
findchild(item,index++);
})
}
}
};
findchild(this,0);
},
// 清除选中。
clear(){
function findchild(p,index){
let preat = p;
if(preat.$options?.name==='tm-checkbox'){
preat.changValue = false;
}else{
if(preat.$children.length>0){
preat.$children.forEach(item=>{
findchild(item,index++);
})
}
}
};
findchild(this,0);
},
// 只有当超过最选选项时才会发出错误。
error(){
uni.showToast({
title:"超过选择限制",icon:'none'
})
this.$emit('error',"超过选择限制")
}
},
}
</script>
<style lang="scss">
</style>
@@ -0,0 +1,166 @@
<template>
<view>
<view class="tm-grouplist overflow "
:class="['ma-' + margin, ' round-' + round, 'shadow-' + shadow, black_tmeme ? 'bk' : '']">
<view v-if="title&&title!='true'" class="px-32 py-16 text-weight-b " :class="[`text-size-${fontSize}`,titleTheme, black_tmeme ? 'bk' : '']">
<slot name="title" :title="title">{{ title }}</slot>
</view>
<view v-show="chuliWsok==true">
<slot></slot>
</view>
</view>
</view>
</template>
<script>
/**
* 列表组
* @description (可嵌套使用)需要配合tm-listitem使用
* @property {Boolean} black = [true|false] 默认:null,暗黑模式
* @property {Number} margin = [] 默认:32,单位upx,外间距。
* @property {Number} shadow = [] 默认:10,单位upx,投影
* @property {Number} round = [] 默认:4,单位upx, 外部圆角
* @property {String} title = [] 默认:'',标题。
* @property {String} font-size = [xxs|xs|s|n|g|lg|xl] 默认:'n',标题大小,注意是css库中的xxs,xs,s,n,g,lg,xl
* @property {Boolean} border-bottom = [] 默认:true ,是否显示底边线。
* @property {String} title-theme = [] 默认:primary,标题的主题色彩名。
* @property {Function} change 点击了列表那一个元素。并返回Index
* @example <tm-grouplist title="配置">
<tm-listitem title="应用栏" v-for="(item,index) in 4" :key="index"></tm-listitem>
</tm-grouplist>
*/
export default {
name: 'tm-grouplist',
props: {
black: {
type: String | Boolean,
default: null
},
borderBottom: {
type: String | Boolean,
default: true
},
// 单位upx
margin: {
type: String | Number,
default: 32
},
// 外部圆角
round: {
type: String | Number,
default: 3
},
// 投影
shadow: {
type: String | Number,
default: 4
},
// 组标题。
title: {
type: String,
default: ''
},
// 标题的主题色彩名。
titleTheme: {
type: String,
default: 'primary'
},
fontSize:{
type:String,
default:'n'
}
},
computed:{
black_tmeme:function(){
if(this.black!==null) return this.black;
return this.$tm.vx.state().tmVuetify.black;
}
},
data() {
return {
targ:'tm-grouplist',
chuliWsok:false,//处理完宽度计算信息再进行显示内部内容。
};
},
created() {
// #ifdef H5
this.chuliWsok = true;
// #endif
},
mounted() {
this.inits();
},
watch:{
},
updated(){
this.inits();
},
methods: {
inits(){
let t = this;
this.$nextTick(function () {
let ch = this.$children;
// #ifdef H5
ch = [];
let cld = this.$children[0].$children
while(cld){
if(cld[0].$children.length>0){
let ods = cld[0].$children;
ch =ods[ods.length-1].$children;
break;
}else{
cld = cld[0]?.$children
}
}
// #endif
ch.forEach((item,index)=>{
if(item.$options.name==="tm-listitem"){
item.setConfig({
margin: [0,0],
padding: [32,24],
shadow: 0,
round: 0,
borderBottom: index===ch.length-1?false:t.borderBottom
})
}
})
// #ifndef H5
setTimeout(function() {
t.chuliWsok = true;
}, 30);
// #endif
})
},
// 事件一定是子项目tm-listitem为group模式时,才会触发。
change(vue_uid) {
// 具体点了哪一个项目,并展开了。
let t = this;
let ch = this.$children;
// #ifdef H5
ch = this.$children[0].$children[0].$children;
ch = ch[1].$children;
// #endif
// 在H5下无法找到$children。因此始终是-1
let index = ch.findIndex(item => {
return vue_uid == item._uid;
});
this.$emit('change', index);
}
}
};
</script>
<style lang="scss" >
</style>
@@ -0,0 +1,84 @@
<template>
<view class="tm-groupradio" :class="[customClass]">
<slot></slot>
</view>
</template>
<script>
/**
* 单选框组
* @description 此组件必须,配合tm-radio组件使用,不可单独使用。
* @property {Function} change 任何一个tm-radio改变将会触发此事件,并携带选中的数组数据。
* @property {String} name = [] 默认:'',提交表单时的的字段名称标识
* @property {String} customClass = [] 默认:'',自定义class
*/
export default {
name:'tm-groupradio',
props:{
//提交表单时的的字段名称
name:{
type:String,
default:''
},
customClass:{
type:String,
default:''
}
},
data() {
return {
};
},
methods: {
change(e) {
this.$emit('change',e)
},
// 获取选中的结果。
getVal(){
let box = [];
function findchild(p,index){
let preat = p;
if(preat.$options?.name==='tm-radio'){
if(preat.value){
box.push({index:index,value:preat.value,checked:preat.value})
}
}else{
if(preat.$children.length>0){
preat.$children.forEach(item=>{
findchild(item,index++);
})
}
}
};
findchild(this,0);
return box;
},
// 清除选中。
clear(){
console.log(9)
function findchild(p,index){
let preat = p;
if(preat.$options?.name==='tm-radio'){
if(preat.value===true){
preat.changValue = false;
}
}else{
if(preat.$children.length>0){
preat.$children.forEach(item=>{
findchild(item,index++);
})
}
}
};
findchild(this,0);
},
},
}
</script>
<style lang="scss">
</style>
@@ -0,0 +1,210 @@
<template>
<view class="tm-helpTips relative d-inline-block">
<view @click.stop="toogle">
<slot></slot>
</view>
<view v-show="showMenu" class="tm-menu-block absolute" :class="[direction,mentDirection]">
<view v-if="direction==='bottom'" class=" px-32" :class="[tipFangx]">
<text class="iconfont icon-sort-up " style="font-size: 46upx;line-height: 0.5;margin-bottom: -4upx;"
:class="[!black_tmeme?`text-${color_tmeme} text`:'',black_tmeme?'text-grey-darken-3':'']"></text>
</view>
<view :class="[color_tmeme,`shadow-${color_tmeme}-1`,black_tmeme?'bk':'']" class=" pa-16 round-2 text-align-left flex-top-start" >
<view>
<text class="text-size-s">{{label}}</text>
</view>
<view @click.stop="toogle" class=" text-align-center flex-shrink pl-32">
<tm-icons :black="black_tmeme" :size="24" :color="iconColor" dense name="icon-times"></tm-icons>
</view>
</view>
<view v-if="direction==='top'" class=" px-32" :class="[tipFangx]">
<text class="iconfont icon-sort-down " style="font-size: 46upx;line-height: 0.5;margin-top: -4upx;"
:class="[!black_tmeme?`text-${color_tmeme} text`:'',black_tmeme?'text-grey-darken-3':'']"></text>
</view>
</view>
<tm-maskFlow :blur='false' v-model="showMenu" bgColor='none'></tm-maskFlow>
</view>
</template>
<script>
/**
* 弹出菜单
* @property {Boolean} black = [true|false] 默认:false,暗黑模式。
* @property {Boolean} show = [true|false] 默认:false,始终显示菜单。
* @property {String} icon-color = [] 默认:white,关闭图标颜色主题名称
* @property {String} color = [] 默认:primary,主题背景色
* @property {String} tip-direction = [left | center | right] 默认:center,指示三角形的方向
* @property {String} direction = [top | bottom] 默认:bottom,弹出方向,top | bottom
* @property {String} ment-direction = [left | center | right] 默认:center,弹出在父组件上的对齐方式,默认居中。可选left,right,center
* @property {String} label = [] 默认:[],需要显示的内容
* @property {String} rang-key = [] 默认:title,菜单列表数组对象时,需要提供key
* @property {Function} change 点击列表项目时触发,返回:{index:index,value:item}
* @example tm-helpTips direction="top" label="每点击一次增加一个积分哦,你知道了吗?">
<tm-icons name="icon-question-circle"></tm-icons>
</tm-helpTips>
*
*/
import tmIcons from "@/tm-vuetify/components/tm-icons/tm-icons.vue"
import tmMaskFlow from "@/tm-vuetify/components/tm-maskFlow/tm-maskFlow.vue"
export default {
components:{tmIcons,tmMaskFlow},
name: "tm-helpTips",
props: {
black: {
type:Boolean | String,
default:null
},
// 三角形的方向。left | center | right
tipDirection: {
type: String,
default: 'center'
},
// 始终显示菜单
show: {
type: Boolean,
default: false
},
// 关闭图标颜色主题名称
iconColor: {
type: String,
default: 'white'
},
// 主题色
color: {
type: String,
default: 'primary'
},
// 弹出方向,top | bottom
direction: {
type: String,
default: 'bottom'
},
// 弹出在父组件上的对齐方式,默认居中。可选left,right,center
mentDirection: {
type: String,
default: 'center'
},
label: {
type: String,
default: () => {
return "";
}
},
// 如果list提供的是对象数组,需要提供对象的key。如果是string对象,无需提供。默认title
rangKey: {
type: String,
default: 'title'
},
// 跟随主题色的改变而改变。
fllowTheme:{
type:Boolean|String,
default:true
}
},
computed: {
black_tmeme: function() {
if (this.black !== null) return this.black;
return this.$tm.vx.state().tmVuetify.black;
},
color_tmeme:function(){
if(this.$tm.vx.state().tmVuetify.color!==null&&this.$tm.vx.state().tmVuetify.color && this.fllowTheme){
return this.$tm.vx.state().tmVuetify.color;
}
return this.color;
},
tipFangx: function() {
if (this.tipDirection === 'left') return 'flex-start ';
if (this.tipDirection === 'center') return 'flex-center';
if (this.tipDirection === 'right') return 'flex-end ';
}
},
data() {
return {
showMenu: false,
};
},
mounted() {
this.showMenu = this.show;
},
methods: {
toogle() {
if (!this.label || this.label.length == 0) return;
this.showMenu = !this.showMenu;
this.$emit("update:show",this.showMenu)
},
open() {
if (!this.label || this.label.length == 0) return;
this.showMenu = true;
this.$emit("update:show",true)
},
off() {
if (!this.label || this.label.length == 0) return;
this.showMenu = false;
this.$emit("update:show",false)
}
},
}
</script>
<style lang="scss" scoped>
.tm-helpTips {
position: relative;
text-align: center;
.tm-menu-block {
z-index: 502;
width: 320upx;
&.bottom {
top: 100%;
bottom: inherit;
animation: roteScale 0.3s ease-in-out;
}
&.top {
top: inherit;
bottom: 100%;
animation: roteScaleTop 0.3s ease-in-out;
}
&.center {
left: calc(50% - 160upx);
}
&.left {
left: 0upx;
}
&.right {
right: 0upx;
}
}
}
@keyframes roteScale{
0%{
transform: scale(0.9) translateY(-20rpx);
opacity: 0;
}
100%{
transform: scale(1) translateY(0rpx);
opacity: 1;
}
}
@keyframes roteScaleTop{
0%{
transform: scale(0.9) translateY(20rpx);
opacity: 0;
}
100%{
transform: scale(1) translateY(0rpx);
opacity: 1;
}
}
</style>
+187
View File
@@ -0,0 +1,187 @@
<template>
<!-- 图标 -->
<view @click="onclick" v-if="name" class="tm-icons vertical-align-top" style="display: inline-block;font-size: 0;" >
<view class="flex-center " :style="{
width: sizes,
height: sizes,
lineHeight:sizes,
}">
<block v-if="vtype == false">
<image :src="name" :style="{
width: sizes,
height: sizes
}" mode="scaleToFill"></image>
</block>
<block v-if="vtype == true">
<!-- #ifdef APP-NVUE -->
<text :style="{
fontSize: sizes,
fontFamily:'iconfontTM'
}" class="icons "
:class="[ black_tmeme ? colors+'-bk':colors, dense ? '' : 'pa-10', black ? 'opacity-6' : '']">{{iconName}}</text>
<!-- #endif -->
<!-- #ifdef MP -->
<text :style="{
fontSize: sizes,
}" class="icons mt--4"
:class="[prefx_computed, black_tmeme ? 'bk' : '', iconName, colorTheme ? colors : '', dense ? '' : 'pa-10', black ? 'opacity-6' : '']"></text>
<!-- #endif -->
<!-- #ifndef MP -->
<text :style="{
fontSize: sizes,
}" class="icons "
:class="[prefx_computed, black_tmeme ? 'bk' : '', iconName, colorTheme ? colors : '', dense ? '' : 'pa-10', black ? 'opacity-6' : '']"></text>
<!-- #endif -->
</block>
</view>
</view>
</template>
<script>
/**
* 图标组件
* @property {Boolean} dense = [true|false] 默认false,是否去除边距
* @property {String} prefx = [iconfont] 默认iconfont,默认图标的前缀,对自定义图标时有好处。
* @property {String} name = [] 默认'',图标名称,注意不带前缀。
* @property {String | Number} size = [] 默认28, 图标大小,单位是upx
* @property {String} color = [primary] 默认primary, 图标主题颜色名
* @property {Function} click 图标点击事件。
* @example <tm-icons name='icon-clear'></tm-icons>
*/
export default {
props: {
dense: {
//是否小边距
type: Boolean,
default: false
},
black: {
type: Boolean,
default: null
},
prefx: {
type: String, //前缀
default: 'iconfont'
},
name: {
type: String, //图标名称。
default: ''
},
size: {
type: String | Number, //图标名称。
default: 28
},
color: {
type: String, //颜色名称或者颜色值。
default: 'primary'
},
//强制转换图标类型,不通过内置判定,解决自己引用图片svg图标时当作字体图标的错误。
iconType: {
type: String,
default: '' //img|icon
},
// 跟随主题色的改变而改变。
fllowTheme: {
type: Boolean | String,
default: false
}
},
computed: {
iconName: function() {
// #ifdef APP-NVUE || APP-PLUS-NVUE
let fontList = require('@/tm-vuetify/scss/iconfonts/iconfont.json');
let name = this.name.replace('icon-', '');
// fontList.glyphs
let itemIcon = fontList.glyphs.find((item, index) => {
return item.font_class == name;
})
return eval('"\\u' + itemIcon.unicode + '"');
// #endif
return this.name;
},
prefx_computed(){
let prefix = this.name.split('-')[0];
if(prefix=='icon') return 'iconfont';
if(prefix=='mdi') return 'mdi';
return prefix;
},
black_tmeme: function() {
return this.$tm.vx.state().tmVuetify.black;
},
vtype: function() {
if (this.name[0] == "." ||
this.name[0] == "/" ||
this.name.substring(0, 4) == 'http' ||
this.name.substring(0, 5) == 'https' ||
this.name.substring(0, 3) == 'ftp'
) {
return false;
}
return true;
},
sizes: function() {
if (typeof this.size === 'string') {
if (/[rpx|upx|rem|em|vx|vh|px]$/.test(this.size)) {
return this.size;
}
return uni.upx2px(parseInt(this.size)) + 'px';
}
if (typeof this.size === 'number' && !isNaN(this.size)) {
return uni.upx2px(this.size) + 'px';
}
},
color_tmeme: function() {
if (this.$tm.vx.state().tmVuetify.color !== null && this.$tm.vx.state().tmVuetify.color && this
.fllowTheme) {
return this.$tm.vx.state().tmVuetify.color;
}
return this.color;
},
colors: {
get: function() {
if (!this.color_tmeme) return 'text-primary';
if (!this.$TestColor(this.color_tmeme)) {
return this.color_tmeme;
}
return 'text-' + this.color_tmeme;
},
set: function() {
if (!this.$TestColor(this.color_tmeme)) {
this.colorTheme = false;
return this.color_tmeme;
}
this.colorTheme = true;
}
}
},
data() {
return {
colorTheme: true
};
},
methods: {
onclick(e) {
this.$emit('click', e);
}
}
};
</script>
<style lang="scss" scoped>
.tm-icons {
vertical-align: middle;
.icons {
&.black {
color: #fff;
}
}
}
</style>
@@ -0,0 +1,201 @@
<template>
<view @click="click" class="tm-images overflow fulled " :class="[round=='rounded'?'rounded':`round-${round}`]">
<view class="fulled fulled-height tm-images-load flex-center">
<view class="d-inline-block load">
<text v-if="isLoad" class="iconfont icon-loading text-size-n text-grey"></text>
</view>
<view class="d-inline-block" v-if="isError">
<slot name="error">
<text class="iconfont icon-exclamationcircle-f text-size-xl text-grey-lighten-2"></text>
</slot>
</view>
<image v-show="!isLoad" @error="error"
:class="[round=='rounded'?'rounded':`round-${round}`]"
:style="{
width:w+'px',
height:h+'px'
}"
@load="loadPic" :src="src_path" :mode="model"></image>
</view>
</view>
</template>
<script>
/**
* 图片
* @property {Function} load 加载成功时触发返回图片宽高。
* @property {Function} click 点击图片事件,返回图片地址参数。
* @property {Function} error 图片加载出错时触发。
* @property {String} src = [] 默认:"",必填。图片地址。测试图片:https://picsum.photos/300
* @property {Number} width = [] 默认:0,宽度,非必填,单位rpx
* @property {Number} height = [] 默认:0,高度,非必填,单位rpx
* @property {Number} round = [] 默认:0,圆角,非必填
* @property {Boolean|String} previmage = [true|false] 默认:true,点击图片是否预览。
* @property {String} model = [scaleToFill|aspectFit|aspectFill|widthFix|heightFix|top|bottom|center|left|right|top left|top right|bottom left|bottom right] 默认:scaleToFill,图片展现模式,同官方。
* @example <tm-images src="https://picsum.photos/300"></tm-images>
*/
export default {
name: "tm-images",
props: {
src: {
type: String,
default: ""
},
//自动,宽度撑满容器宽度,高度自动。
// 自定宽度,
width: {
type: Number,
default: 0
},
// 自定高度。
height: {
type: Number,
default: 0
},
// 是否开启预览模式,即点击图片可以预览。
previmage: {
type: Boolean | String,
default: true
},
model: {
type: String,
default: 'scaleToFill'
},
round: {
type: Number|String,
default: 0
}
},
data() {
return {
w: 0,
h: 0,
isLoad:false,
isError:false
};
},
computed:{
w_px:function(){
return uni.upx2px(this.width);
},
h_px:function(){
return uni.upx2px(this.height);
},
src_path:function(){
if(
this.src.substring(0,4)=='http'||
this.src.substring(0,4)=='blob'||
this.src.substring(0,5)=='https'||
this.src.substring(0,3)=='ftp'||
this.src.indexOf('data:image')>-1
){
return this.src;
}
return '/'+this.src;
}
},
mounted() {
this.isLoad = true;
},
methods: {
error(e) {
this.isLoad = false;
this.isError = true;
this.$emit('error', e);
},
async loadPic(e) {
let wh = e.detail;
this.isLoad = false;
this.isError = false;
this.$nextTick(async function(){
this.$Querey(".tm-images",this,30).then(tb=>{
let sw = tb[0].width||wh.width;
let sh = tb[0].height||wh.height;
let bl = wh.width / wh.height;
if (this.w_px == 0 && this.h_px == 0) {
this.w = sw;
this.h = sw / bl;
this.$emit('load', {
width: this.w,
height: this.h
})
return;
}
if (this.w_px == 0 && this.h_px > 0) {
this.w = this.h_px * bl;
this.h = this.h_px
this.$emit('load', {
width: this.w,
height: this.h
})
return;
}
if (this.w_px > 0 && this.h_px == 0) {
this.w = this.w_px;
this.h = this.w_px / bl
this.$emit('load', {
width: this.w,
height: this.h
})
return;
}
if (this.w_px > 0 && this.h_px > 0) {
this.w = this.w_px;
this.h = this.h_px;
this.$emit('load', {
width: this.w,
height: this.h
})
return;
}
})
})
},
click(e) {
this.$emit("click", this.src_path);
if (this.previmage&&!this.isError) {
uni.previewImage({
current: this.src_path,
urls: [this.src_path],
fail:(res)=>{
}
})
}
}
},
}
</script>
<style lang="scss" scoped>
.tm-images{
line-height: 0;
.tm-images-load{
min-width: 60rpx;
min-height: 60rpx;
.load{
animation: xhRote 0.8s infinite linear;
}
}
}
@keyframes xhRote{
0%{
transform: rotate(0deg);
}
100%{
transform: rotate(360deg);
}
}
</style>
+533
View File
@@ -0,0 +1,533 @@
<template>
<view class="d-block tm-input overflow"
:class="[
black_tmeme?(bgTheme?'grey-darken-5':''):bgTheme,
flat?'':`px-${padding[0]}`,
'round-'+ bgRound,
`shadow-${color}-${bgShadow}`
]"
>
<view
:class="(flat?'':` py-${padding[1]} `)+` ${borderBottom?black_tmeme?'border-grey-darken-4-b-1 ':'border-grey-lighten-4-b-1':''}`"
>
<view @click="onclickInput" :class="[vertical?'tm-input-col':'flex-between ']" :style="{
width: '100%',height: 'auto',
alignItems:inputType=='textarea'?'flex-start':'center'
}">
<!-- 左边内容 -->
<view v-if="leftIcon||title" class="tm-input-left flex-start flex-shrink" :class="[vertical?'pb-24':'']">
<!-- icon -->
<view v-if="leftIcon" class="pr-16 vertical-align-middle flex-center">
<tm-icons dense :name="leftIcon" :color="color_tmeme"></tm-icons>
</view>
<!-- 标题 -->
<view v-if="title" class="d-inline-block "
:style="{fontSize:title_size}"
:class="[titleClass,black_tmeme?'bk text-grey-lighten-3':'']">
<text v-if="required" class="text-red">*</text>
<slot name="default" :title="title">
{{title}}
</slot>
</view>
</view>
<view class="flex-between " :class="[disabled?'opacity-6':'',]" style="width: 100%;">
<!-- input主体 -->
<view class="tm-input-center relative fulled" >
<view class="flex-start tm-input-center-wk"
:class="['round-'+round,showIndent?'px-16':'',
black_tmeme?(bgColor?'grey-darken-4 text-grey-lighten-3':'text-grey-lighten-3'):bgColor,
`text-${textColor}`,isFocus&&focusShow?(black_tmeme?`border-${color_tmeme}-a-1`:`${color_tmeme} outlined `):``,
`border-${black_tmeme?(borderColor?'grey-darken-4':''):borderColor}-a-1`,
]">
<view class="flex-shrink px-16 flex-center" v-if="prefixpIcon" style="line-height: 0;">
<tm-icons dense :name="prefixpIcon" :size="28" :color="(prefixpIconColor||color_tmeme)" ></tm-icons>
</view>
<view :style="{fontSize:font_size}" class="flex-shrink pr-24" :class="[titleClass,black_tmeme?'bk text-grey-lighten-3':'']" v-if="prefixpText">
{{prefixpText}}
</view>
<input always-embed v-if="inputType!='textarea'" @confirm="$emit('confirm',$event)" @input="input"
@keyboardheightchange="$emit('keyboardheightchange',$event)" @blur="blur"
@focus="focusFun" :focus="focus_fs" :maxlength="maxlength" :adjust-position="adjustPosition"
:auto-focus="autoFocus" :confirm-type="confirmType" :disabled="disabled"
:password="password" :type="inputType" :value="value" class="tm-input-center-input "
:class="['text-align-'+align,showError?'text-red':'',' py-5 ']" :placeholder="placeholder"
:placeholder-class="black_tmeme? 'text-grey-darken-1 ':' ' +` text-size-n ` + placeholderClass"
:style="{
fontSize:font_size,
height:height_rpx+'rpx'
}"
/>
<!-- uniapp的bug当输入框禁用时点击此处来获取新的焦点以让键盘收起 -->
<view v-if="disabled" class="absolute fulled t-0 r-0" :style="{
height:height_rpx+'rpx'
}"></view>
<textarea always-embed v-if="inputType=='textarea'" @confirm="$emit('confirm',$event)" @input="input"
@keyboardheightchange="$emit('keyboardheightchange',$event)" @blur="blur"
@focus="focusFun" :focus="focus_fs" :maxlength="maxlength" :adjust-position="adjustPosition"
:auto-focus="autoFocus" :confirm-type="confirmType" :disabled="disabled"
:value="value" class="tm-input-center-input " :style="{
height:height_rpx+'rpx',
fontSize:font_size,
}"
:class="[maxlength>0?'pb-46':'','text-align-'+align,showError?'text-red':'','pt-16 fulled']" :placeholder="placeholder"
:placeholder-class="black_tmeme? 'text-grey-darken-1 ':' text-grey-lighten-1 ' +` text-size-n `+ placeholderClass" >
</textarea>
<!-- 清除图标 -->
<view v-if="clear&&valdata.length!=''" class="flex-center pl-16">
<tm-icons @click.stop="clearVal" name="icon-times-circle-fill" :color="color_tmeme"></tm-icons>
</view>
<view v-if="suffixIcon" class="flex-center">
<tm-icons :size="26" :name="suffixIcon" :color="(suffixIconColor||color_tmeme)"></tm-icons>
</view>
</view>
<view v-if="maxlength>0&&inputType=='textarea'"
:style="{bottom:'16rpx',right:'16rpx'}"
class="tm-input-center-numXz text-align-right text-size-xxs pt-12 text-grey absolute fulled">{{valueLen}}/{{maxlength}}</view>
</view>
<!-- 右边 -->
<view class="tm-input-right flex-end flex-shrink">
<!-- 后缀文字比如单位 -->
<text v-if="suffix" class=" text-grey-darken-4 pl-10" :style="{fontSize:font_size}">{{suffix}}</text>
<!-- 后台图标 -->
<view v-if="rightIcon" class="pl-10" style="line-height: 0;">
<tm-icons dense :name="rightIcon" color="grey-lighten-1"></tm-icons>
</view>
<!-- 插入的按钮等内容 -->
<slot name="rightBtn"></slot>
</view>
</view>
</view>
<!-- detail出错成功等信息 -->
<view v-if="showError" class="text-size-xs text-red pt-12">{{errorText}}</view>
</view>
</view>
</template>
<script>
/**
* 输入框
* @property {Number} maxlength = [-1] 默认:-1,最大输入字符数。
* @property {Boolean} black = [] 默认:false,暗黑模式。
* @property {Function} verify = [] 默认: (val) => {check: val.length <= 0 ? false : true,text: "必填项不能为空。"},校验规则函数。
* @property {String} title-class = [] 默认: text-grey-darken-4,自定左边标题或者上标题的类。
* @property {Boolean} required = [] 默认: false, 是否是必填。如果必填写将会触发基础的校验,不能为空。
* @property {Boolean} adjust-position = [] 默认: false, 是否上推键盘。
* @property {Boolean} auto-focus = [] 默认: false, 自动获得焦点。
* @property {String} confirm-type = [done|go|next|send|search] 默认: done, 键盘右下角确认按钮文字。
* @property {Boolean} disabled = [] 默认: false, 禁用。
* @property {Boolean} focus-show = [] 默认: false, 是否显示聚焦状态。
* @property {Boolean} show-indent = [] 默认: false, 是滞使输入框内容两边缩进。默认是。
* @property {Boolean} password = [] 默认: false, 密码模式
* @property {String} input-type = [digit|text|number|password|idcard|textarea] 默认: text, 输入模式
* @property {String} value = [] 默认: "", 输入内容,同v-model
* @property {String} right-icon = [] 默认: "", 后缀图标
* @property {String} left-icon = [] 默认: "", 外层前缀图标
* @property {String} prefixp-icon = [] 默认: "", 输入框内部前缀图标
* @property {String} prefixp-icon-color = [] 默认: "", 默认空,使用主题color颜色
* @property {String} suffix = [] 默认: "", 后缀文字
* @property {String} suffix-icon = [] 默认: "", 输入框内后缀图标
* @property {String} suffix-icon-color = [] 默认: "", 默认使用主题图标
* @property {String} title = [] 默认: "", 左边标题。
* @property {String} title-font-size = [xxs/xs/s/n/g/lg/xl] 默认: "n",同类的字号,xxs,xs,s,n,g,lg,xl
* @property {Number|String} font-size = [xxs/xs/s/n/g/lg/xl/任意数字] 默认: "n",同类的字号,xxs,xs,s,n,g,lg,xl,也可以是数字单位rpx
* @property {String} align = [left|center|right] 默认: "", 输入框文字对齐方式。left,center,right
* @property {Boolean} clear = [false|true] 默认: false, 显示清除图标。
* @property {String} color = [] 默认: primary, 主题色名称
* @property {String} bg-color = [grey-lighten-5|white] 默认: grey-lighten-5, 输入框背景色。
* @property {String} border-color = [] 默认: "", 输入框边框类型主题颜色名称。
* @property {Boolean} border-bottom = [false|true] 默认: true, 是否显示下划线
* @property {String} text-color = [black|primary] 默认: black, 输入框文字颜色。
* @property {String} placeholder = [] 默认: 请输入, 占位文字
* @property {Boolean} vertical = [false|true] 默认: false, 是否上下排列
* @property {Number} round = [] 默认: 2, 输入框圆角。
* @property {Boolean} showIndent = [] 默认: true, 是否输入框内部两边缩进
* @property {Number} bg-round = [] 默认: 0, 整体框圆角。
* @property {Number} bg-shadow = [] 默认: 0, 整体框投影。
* @property {String} bg-theme = [] 默认:white, 整体框背景
* @property {Boolean} flat = [] 默认: false, 是否去除所有边框
* @property {Number|String} height = [] 默认: 68,
* @property {String} name = [] 默认:'',提交表单时的的字段名称标识
* @property {String} prefixp-text = [] 默认:'',输入框内前缀文字
* @property {String} placeholder-class = [] 默认:'',点位符的自定义类。
* @property {Array} padding = [] 默认:[32,12],左右,上下内间距。
* @property {Function} click 点击输入框时触发发的函数。
* @property {Function} clear 清空时触发携带相关数据
* @property {Function} input 输入时触发携带相关数据
* @example <tm-input ></tm-input>
*
*/
import tmSheet from "@/tm-vuetify/components/tm-sheet/tm-sheet.vue"
import tmIcons from "@/tm-vuetify/components/tm-icons/tm-icons.vue"
export default {
components:{tmSheet,tmIcons},
name:"tm-input",
props: {
//提交表单时的的字段名称
name:{
type:String,
default:''
},
prefixpText:{
type:String,
default:''
},
flat: {
type: Boolean,
default: false
},
//是否输入框内部两边缩进。默认是
showIndent:{
type:Boolean,
default:true
},
maxlength: {
type: Number,
default: -1
},
black: {
type:Boolean|String,
default:null
},
//是否显示聚焦状态
focusShow: {
type:Boolean|String,
default:false
},
titleFontSize:{
type:String,
default:'n',//同类的字号,xxs,xs,s,n,g,lg,xl
},
height:{
type:Number|String,
default:68
},
// 校验规则函数。
verify: {
type: Function,
default: ()=>{
return (val) => {
return {
check: val?.length <= 0 ? false : true,
text: "必填项不能为空。"
};
}
}
},
titleClass: {
type: String,
default: 'text-grey-darken-4'
},
// 是否是必填。如果必填写将会触发基础的校验,不能为空。
required: Boolean,
adjustPosition: Boolean,
autoFocus: Boolean,
confirmType: String,
disabled: Boolean,
password: Boolean,
inputType: {
type: String,
default: 'text'
},
value: {
type: String|Number,
default: ''
},
// 右边外层后缀图标。
rightIcon: {
type: String,
default: ''
},
// 输入框内部前缀图标。
prefixpIcon: {
type: String,
default: ''
},
prefixpIconColor: {
type: String,
default: ''
},
// 左边外层图标。
leftIcon: {
type: String,
default: ''
},
// 后缀文字
suffix: {
type: String,
default: ''
},
// 输入框后缀图标
suffixIcon: {
type: String,
default: ''
},
suffixIconColor:{
type: String,
default: ''
},
// 左边标题。
title: {
type: String,
default: ''
},
fontSize:{
type:Number|String,
default:'n'
},
// 输入框文字对齐方式。left,center,right
align: {
type: String,
default: 'left'
},
// 显示清除图标。
clear: Boolean,
// 主题色名称。
color: {
type: String,
default: 'primary'
},
//输入框背景色。grey-lighten-5
bgColor: {
type: String,
default: ''
},
// 输入框边框类型主题颜色名称。
borderColor: {
type: String,
default: ''
},
// 是否显示下划线
borderBottom: {
type: Boolean,
default: true
},
// text输入框文字颜色。
textColor: {
type: String,
default: 'black'
},
placeholder: {
type: String,
default: "请输入"
},
placeholderClass:{
type:String,
default:'text-grey-lighten-1'
},
// 是否上下排列输入框。
vertical: Boolean,
round:{
type:Number|String,
default:2
},
bgRound:{
type:Number|String,
default:0
},
bgShadow:{
type:Number|String,
default:0
},
bgTheme:{
type:String,
default:'white'
},
//获取焦点。
focus:{
type:Boolean,
default:false
},
padding:{
type:Array,
default:()=>{
return [32,12];
}
},
// 跟随主题色的改变而改变。
fllowTheme:{
type:Boolean|String,
default:true
}
},
data() {
return {
showError: false,
errorText: "请正确填写",
FOCUS_Auto:false,
isFocus:false,
};
},
computed: {
height_rpx:function(){
return this.height;
},
font_size:function () {
let font = {
'xxs':'20rpx',
'xs':'22rpx',
's':'24rpx',
'm':'26rpx',
'n':'28rpx',
'g':'32rpx',
'lg':'36rpx',
'xl':'40rpx'
}
if(typeof this.fontSize=='string') return font[this.fontSize];
return this.fontSize+'rpx';
},
title_size:function () {
let font = {
'xxs':'20rpx',
'xs':'22rpx',
's':'24rpx',
'm':'26rpx',
'n':'28rpx',
'g':'32rpx',
'lg':'36rpx',
'xl':'40rpx'
}
if(typeof this.titleFontSize=='string') return font[this.titleFontSize];
return this.titleFontSize+'rpx';
},
focus_fs:{
get:function(){
return this.FOCUS_Auto;
},
set:function(val){
this.FOCUS_Auto = val;
}
},
black_tmeme: function() {
if (this.black !== null) return this.black;
return this.$tm.vx.state().tmVuetify.black;
},
color_tmeme:function(){
if(this.$tm.vx.state().tmVuetify.color!==null&&this.$tm.vx.state().tmVuetify.color && this.fllowTheme){
return this.$tm.vx.state().tmVuetify.color;
}
return this.color;
},
valueLen:function(){
// 为了兼容ios不能使用this.valdata.length.
let p = String(this.valdata);
return p?.split('').length||0 ;
},
valdata:{
get:function(){
return this.value;
},
set:function(val){
this.$emit('input', val)
this.$emit('update:value', val)
if (this.required) {
this.$nextTick(function(){
this.verifyInput();
})
}
}
}
},
mounten(){
this.FOCUS_Auto = this.focus;
},
methods: {
onclickInput(e){
this.$emit('click',e)
},
input(e) {
this.valdata = e.target.value;
},
// 校验是否通过。
verifyInput() {
let verify = this.verify.bind(this, this.valdata||'');
verify = verify.call(this,this.valdata||'')
if(typeof verify ==='function'){
verify = verify.call(this,this.valdata||'')
}
if (typeof verify !== 'object') verify = {};
this.showError = !(verify.check??true);
this.errorText = verify.text??"";
return verify.check??true;
},
//清除校验显示 的内容。
clearVerify() {
this.showError = false;
this.errorText = "";
},
blur(e) {
this.isFocus=false;
this.$emit('blur', e)
},
focusFun(e){
this.isFocus=true;
this.$emit('focus',e)
},
clearVal(e){
this.valdata ="";
this.$emit('clear', this.valdata)
}
},
}
</script>
<style lang="scss" scoped>
.tm-input {
.tm-input-center {
width: 100%;
.tm-input-center-wk{
transition: all 0.2s;
}
.tm-input-center-input {
border: none;
background: none;
box-shadow: 0;
width: 100%;
}
}
.tm-input-left {
flex-shrink: 0;
height: 100%;
min-width: 80rpx;
padding-right: 24rpx;
}
.tm-input-col {
.tm-input-left {
max-width: inherit;
padding-right: 0;
}
.tm-input-center {
.tm-input-center-input {
height: 76upx;
}
}
}
.tm-input-right {
height: 100%;
// width: 300upx;
}
}
</style>
@@ -0,0 +1,408 @@
<template>
<view class="tm-keyborad ">
<tm-poup isClickbled :isFilter="false" over-color='none'
:height="530" ref="pop"
:bg-color="black_tmeme?'grey-darken-5':'grey-lighten-5'"
v-model="showOpen">
<view>
<view v-if="model_code=='number'||model_code=='code'" class="tm-keyborad-html">
<view class=" py-10" :class="[black_tmeme?'grey-darken-5':'grey-lighten-5']">
<view class="text-size-xs text-align-center pb-20 pt-10 text-weight-b">
<slot>
<text>{{title}}</text>
</slot>
</view>
<tm-row custom-class="px-5">
<tm-col grid="3" padding="5" color="none">
<tm-button :fontSize="42" :fllowTheme="false" item-class="text-weight-b" @click="clickNumo(1,'num')" :black="black_tmeme" :round="round" block :shadow="shadown" :theme="black_tmeme?'grey-darken-4':color" height="100">1</tm-button>
</tm-col>
<tm-col grid="3" padding="5" color="none">
<tm-button :fontSize="42" :fllowTheme="false" item-class="text-weight-b" @click="clickNumo(2,'num')" :black="black_tmeme" :round="round" block :shadow="shadown" :theme="black_tmeme?'grey-darken-4':color" height="100">2</tm-button>
</tm-col>
<tm-col grid="3" padding="5" color="none">
<tm-button :fontSize="42" :fllowTheme="false" item-class="text-weight-b" @click="clickNumo(3,'num')" :black="black_tmeme" :round="round" block :shadow="shadown" :theme="black_tmeme?'grey-darken-4':color" height="100">3</tm-button>
</tm-col>
<tm-col grid="3" padding="5" color="none">
<tm-button :fontSize="42" :iconSize="42" :fllowTheme="false" item-class="text-weight-b" @click="clickNumo(null,'del')" icon="icon-caret-left" :black="black_tmeme" :round="round" block :shadow="shadown" :theme="black_tmeme?'grey-darken-4':color" :font-color="black?'grey':'black'" height="100"></tm-button>
</tm-col>
</tm-row>
<tm-row align="flex-start">
<tm-col grid="9" color="none" custom-class="px-5">
<tm-row>
<tm-col grid="4" padding="5" color="none"><tm-button :fontSize="42" :fllowTheme="false" item-class="text-weight-b" @click="clickNumo(4,'num')" :black="black_tmeme" :round="round" block :shadow="shadown" :theme="black_tmeme?'grey-darken-4':color" height="100">4</tm-button></tm-col>
<tm-col grid="4" padding="5" color="none"><tm-button :fontSize="42" :fllowTheme="false" item-class="text-weight-b" @click="clickNumo(5,'num')" :black="black_tmeme" :round="round" block :shadow="shadown" :theme="black_tmeme?'grey-darken-4':color" height="100">5</tm-button></tm-col>
<tm-col grid="4" padding="5" color="none"><tm-button :fontSize="42" :fllowTheme="false" item-class="text-weight-b" @click="clickNumo(6,'num')" :black="black_tmeme" :round="round" block :shadow="shadown" :theme="black_tmeme?'grey-darken-4':color" height="100">6</tm-button></tm-col>
</tm-row>
<tm-row>
<tm-col grid="4" padding="5" color="none"><tm-button :fontSize="42" :fllowTheme="false" item-class="text-weight-b" @click="clickNumo(7,'num')" :black="black_tmeme" :round="round" block :shadow="shadown" :theme="black_tmeme?'grey-darken-4':color" height="100">7</tm-button></tm-col>
<tm-col grid="4" padding="5" color="none"><tm-button :fontSize="42" :fllowTheme="false" item-class="text-weight-b" @click="clickNumo(8,'num')" :black="black_tmeme" :round="round" block :shadow="shadown" :theme="black_tmeme?'grey-darken-4':color" height="100">8</tm-button></tm-col>
<tm-col grid="4" padding="5" color="none"><tm-button :fontSize="42" :fllowTheme="false" item-class="text-weight-b" @click="clickNumo(9,'num')" :black="black_tmeme" :round="round" block :shadow="shadown" :theme="black_tmeme?'grey-darken-4':color" height="100">9</tm-button></tm-col>
</tm-row>
<tm-row v-if="model_code=='number'">
<tm-col :grid="decimal?4:8" padding="5" color="none"><tm-button :fontSize="42" :fllowTheme="false" item-class="text-weight-b" @click="clickNumo(0,'num')" :black="black_tmeme" :round="round" block :shadow="shadown" :theme="black_tmeme?'grey-darken-4':color" height="100">0</tm-button></tm-col>
<tm-col v-if="decimal" grid="4" padding="5" color="none"><tm-button :fontSize="42" :fllowTheme="false" item-class="text-weight-b" @click="clickNumo('.','dian')" :black="black_tmeme" :round="round" block :shadow="shadown" :theme="black_tmeme?'grey-darken-4':color" height="100">.</tm-button></tm-col>
<tm-col grid="4" padding="5" color="none"><tm-button :fontSize="42" :fllowTheme="false" item-class="text-weight-b" @click="clickNumo(null,'cancel')" :black="black_tmeme" :round="round" block :shadow="shadown" :theme="black_tmeme?'grey-darken-4':color" height="100">关闭</tm-button></tm-col>
</tm-row>
<tm-row v-if="model_code=='code'">
<tm-col grid="4" padding="5" color="none"><tm-button :fontSize="42" :fllowTheme="false" item-class="text-weight-b" @click="clickNumo(0,'num')" :black="black_tmeme" :round="round" block :shadow="shadown" :theme="black_tmeme?'grey-darken-4':color" height="100">0</tm-button></tm-col>
<tm-col grid="4" padding="5" color="none"><tm-button :fontSize="42" :fllowTheme="false" item-class="text-weight-b" @click="clickNumo('x','code-x')" :black="black_tmeme" :round="round" block :shadow="shadown" :theme="black_tmeme?'grey-darken-4':color" height="100">X</tm-button></tm-col>
<tm-col grid="4" padding="5" color="none"><tm-button :fontSize="42" :fllowTheme="false" item-class="text-weight-b" @click="clickNumo(null,'cancel')" :black="black_tmeme" :round="round" block :shadow="shadown" :theme="black_tmeme?'grey-darken-4':color" height="100">关闭</tm-button></tm-col>
</tm-row>
</tm-col>
<tm-col grid="3" color="none" custom-class="mt-5 mr-5">
<tm-button :fontSize="42" item-class="text-weight-b" :round="round" label="确认" :theme="okColor" @click.stop="clickNumo(null,'confirm')" block :shadow="shadown" height="312"></tm-button>
</tm-col>
</tm-row>
</view>
</view>
<view v-if="model_code=='car'" class="tm-keyborad-html">
<view class=" py-10" :class="[black_tmeme?'grey-darken-5':'grey-lighten-5']">
<view class="text-size-xs text-align-center pb-20 pt-10 text-weight-b">
<slot>
<text>{{title}}</text>
</slot>
</view>
<tm-row custom-class="px-5">
<tm-col grid="10" color="none">
<tm-row >
<block v-for="(item,index) in chepai[enIndex]" :key="index">
<tm-col width="12.5%" padding="5" color="none">
<tm-button :fontSize="32" :iconSize="32" :fllowTheme="false" item-class="text-weight-b"
@click="clickNumo(item,'car')"
:black="black_tmeme" :round="round"
block :shadow="shadown"
:theme="black_tmeme?'grey-darken-4':color"
height="80">
{{item}}
</tm-button>
</tm-col>
</block>
</tm-row>
</tm-col>
<tm-col grid="2" color="none">
<tm-row>
<tm-col grid="12" padding="5" color="none">
<tm-button :fontSize="32" :iconSize="32" :fllowTheme="false" item-class="text-weight-b"
@click="clickNumo(null,'del')" icon="icon-caret-left"
:black="black_tmeme" :round="round"
block shadow="0"
theme="red"
font-color="red"
text
height="80">
</tm-button>
</tm-col>
<tm-col grid="12" padding="5" color="none">
<tm-button item-class="text-weight-b"
@click="clickNumo(null,'cancel')"
:black="black_tmeme" :round="round"
block shadow="0"
:theme="okColor"
:fontSize="32" :iconSize="32"
text
height="80">
关闭
</tm-button>
</tm-col>
<tm-col grid="12" padding="5" color="none">
<tm-button item-class="text-weight-b"
@click="clickNumo(null,'car-zh')"
:black="black_tmeme" :round="round"
block :shadow="0"
:theme="okColor"
text
:fontSize="32" :iconSize="32"
height="80">
/
</tm-button>
</tm-col>
<tm-col grid="12" padding="5" color="none">
<tm-button item-class="text-weight-b"
@click="clickNumo(null,'confirm')"
:round="round"
block :shadow="shadown"
:theme="okColor"
:fontSize="32" :iconSize="32"
height="133">
确认
</tm-button>
</tm-col>
</tm-row>
</tm-col>
</tm-row>
</view>
</view>
<view v-if="model_code=='password'" class="tm-keyborad-html">
<view class=" py-10" :class="[black_tmeme?'grey-darken-5':'grey-lighten-5']">
<view class="text-size-xs text-align-center pb-20 pt-10 text-weight-b">
<slot>
<text>{{title}}</text>
</slot>
</view>
<tm-row custom-class="px-5">
<tm-col grid="10" color="none">
<tm-row >
<block v-for="(item,index) in password[enIndex]" :key="index">
<tm-col width="12.5%" padding="5" color="none">
<tm-button :fllowTheme="false" item-class="text-weight-b"
@click="clickNumo(item,'password')"
:black="black_tmeme" :round="round"
block :shadow="shadown"
:fontSize="32" :iconSize="32"
:theme="black_tmeme?'grey-darken-4':color"
height="80">
{{item}}
</tm-button>
</tm-col>
</block>
</tm-row>
</tm-col>
<tm-col grid="2" color="none">
<tm-row>
<tm-col grid="12" padding="5" color="none">
<tm-button :fllowTheme="false" item-class="text-weight-b"
@click.stop="clickNumo(null,'del')" icon="icon-caret-left"
:black="black_tmeme" :round="round"
block shadow="0"
theme="red"
font-color="red"
text
:fontSize="32" :iconSize="32"
height="80">
</tm-button>
</tm-col>
<tm-col grid="12" padding="5" color="none">
<tm-button item-class="text-weight-b"
@click.stop="clickNumo(null,'cancel')"
:black="black_tmeme" :round="round"
block shadow="0"
:theme="okColor"
:fontSize="32" :iconSize="32"
text
height="80">
关闭
</tm-button>
</tm-col>
<tm-col grid="12" padding="5" color="none">
<tm-button item-class="text-weight-b"
@click.stop="clickNumo(null,'password-fh')"
:black="black_tmeme" :round="round"
block :shadow="0"
:theme="okColor"
text
:fontSize="32" :iconSize="32"
height="80">
/
</tm-button>
</tm-col>
<tm-col grid="12" padding="5" color="none">
<tm-button item-class="text-weight-b"
@click.stop="clickNumo(null,'confirm')"
:round="round"
block :shadow="shadown"
:theme="okColor"
:fontSize="32" :iconSize="32"
height="133">
确认
</tm-button>
</tm-col>
</tm-row>
</tm-col>
</tm-row>
</view>
</view>
<!-- #ifdef H5 -->
<view style="height: var(--window-bottom);"></view>
<!-- #endif -->
</view>
</tm-poup>
</view>
</template>
<script>
/**
* 输入键盘
* @description 输入键盘分为:数字键盘,身份键盘,车牌键盘,密码键盘。
* @property {String|Number} value = [] 默认:'',推荐使用v-model,也可使用value.sync双向绑定.
* @property {Boolean} show = [] 默认:false,显示键盘,请使用show.sync双向绑定
* @property {String} color = [] 默认:white,通用按钮主题色。
* @property {String} title = [] 默认:'安全键盘放心输入',键盘顶部标题文字。
* @property {String} ok-color = [] 默认:primary, 确认按钮的主题色
* @property {Number} round = [] 默认:2, 按钮圆角。
* @property {Number} shadown = [] 默认:5, 按钮按钮投影
* @property {Boolean} black = [] 默认:false, 暗黑模式。
* @property {Boolean} decimal = [] 默认:true, 是否显示小数点。
* @property {String} model = [number|code|car|password] 默认:'number', 键盘类型数字键盘,身份证,车牌,密码number|code|car|password
* @property {Function} confirm 点击确认或者返回时触发。
* @example <tm-keyborad v-model="num" :show.sync="show"></tm-keyborad>
*/
import tmIcons from "@/tm-vuetify/components/tm-icons/tm-icons.vue"
import tmButton from "@/tm-vuetify/components/tm-button/tm-button.vue"
import tmRow from "@/tm-vuetify/components/tm-row/tm-row.vue"
import tmCol from "@/tm-vuetify/components/tm-col/tm-col.vue"
import tmPoup from "@/tm-vuetify/components/tm-poup/tm-poup.vue"
export default {
components:{tmIcons,tmButton,tmRow,tmCol,tmPoup},
name:"tm-keyborad",
model:{
prop:'value',
event:'input'
},
props:{
value:{
type:String|Number,
default:''
},
// 键盘顶部标题文字。
title:{
type:String,
default:'安全键盘放心输入'
},
show:{
type:Boolean,
default:false
},
// 通用按钮主题色。
color:{
type:String,
default:'white'
},
// 确认按钮的主题色
okColor:{
type:String,
default:'primary'
},
// 按钮圆角。
round:{
type:Number,
default:3
},
// 按钮投影
shadown:{
type:Number,
default:5
},
black:{
type:Boolean,
default:null
},
// 是否显示小数点。
decimal:{
type:Boolean,
default:true
},
// 键盘类型数字键盘,身份证,车牌,密码
model:{
type:String,
default:'number' //number|code|car|password
},
},
computed:{
black_tmeme: function() {
if (this.black !== null) return this.black;
return this.$tm.vx.state().tmVuetify.black;
},
model_code:function(){
return this.model;
}
},
watch:{
show:function(val){
this.showOpen = val;
}
},
created() {
this.showOpen = this.show
},
mounted() {
},
data() {
return {
showOpen:false,
enIndex:0,
chepai:[
[
'京','沪','津','渝','鲁','冀','晋','蒙','辽','吉','黑','苏','浙',
'皖','闽','赣','豫','湘','鄂','粤','桂','琼','川','贵','云','藏',
'陕','甘','青','宁','新','港','澳','台','新','使','学'
],
[
'1','2','A','B','C','D','E','F',
'3','4','G','H','I','J','K','L',
'5','6','M','N','O','P','Q','R',
'7','8','S','T','U','V','W','X',
'9','0','Y','Z'
]
],
password:[
[
'1','2','A','B','C','D','E','F',
'3','4','G','H','I','J','K','L',
'5','6','M','N','O','P','Q','R',
'7','8','S','T','U','V','W','X',
'9','0','Y','Z'
],
[
'\"',"\'",'.','/',"\\",']','[','!','?','_','<','>','%',';',')','(','&','+','=','~','*','#','@'
]
],
};
},
methods: {
clickNumo(val,type){
let oval = this.value;
if(type=='del'){
oval = oval.length<=0?'':oval.substring(0,oval.length-1)
}else if(type=='num' || type=='code-x'|| type=='car' || type == 'password'){
oval= oval +'' + val;
}else if(type=='dian'){
if(oval.indexOf('.')>-1 || !oval || oval.length==0){
return;
}
oval= oval +'' + val;
}else if(type=='car-zh'||type=='password-fh'){
this.enIndex= this.enIndex?0:1
}
if(type == 'confirm' || type=='cancel'){
if(this.model_code=='number'){
if(oval.substring(oval.length-1)=='.'){
oval = oval.substring(0,oval.length-1);
}
}
this.$emit('update:show',false);
if(type=='confirm'){
this.$emit('input',oval.toUpperCase())
this.$emit('update:value',oval.toUpperCase())
this.$emit('confirm',this.value);
}
return;
}
this.$emit('input',oval.toUpperCase())
this.$emit('update:value',oval.toUpperCase())
// #ifdef MP || APP
uni.vibrateShort({})
// #endif
},
},
}
</script>
<style lang="scss" scoped>
</style>
@@ -0,0 +1,393 @@
<!-- 列表项目类似单元格 -->
<template>
<view class="tm-listitem ">
<view
@click="click"
:style="[
bgColor?{background:bgColor}:''
]"
:class="[
classStyle,
`mx-${pz_themeCus.margin[0]} my-${pz_themeCus.margin[1]}`,
disabled===true?(disabledColor + (black_tmeme?' bk ':'')) :(black_tmeme?'grey-darken-4 bk ':(bgColor?'':color)),
dense?'nom':'',
`shadow-${pz_themeCus.shadow}`,
`round-${pz_themeCus.round}`,
border?'border-a-1':''
]"
>
<view :class="['px-' + pz_themeCus.padding[0], 'py-' + pz_themeCus.padding[1]]">
<view class="flex-between ">
<view class="left flex-start ">
<view class="left-tm-content mr-24 flex-center" v-if="showLeftIcon">
<slot name="left" :icon="{ icon: leftIcon, color: color_tmeme, fontsize: leftIconSize }">
<tm-icons v-if="!group" :color="color_tmeme" :name="leftIcon" :size="leftIconSize"></tm-icons>
<tm-icons v-if="group" :color="color_tmeme" :name="!showContent ? 'icon-caret-right' : 'icon-sort-down'" :size="24"></tm-icons>
</slot>
</view>
<view class="tm-content flex-col" style="width: 90%;">
<view
class="fulled flex-shrink"
:class="[disabled===true?' text-grey-darken-1 ':(black_tmeme ? 'text-white' : `text-${titleColor}`)]"
:style="{
fontSize: fontSize + 'rpx'
}"
>
<slot name="default" :title="title">{{ title }}</slot>
</view>
<view vif="label" class="fulled text-size-s text-weight-xs text-overflow-1 text-grey-lighten-1" :class="[label ? (dense ? 'pt-0' : 'pt-4') : '']">
<slot name="label" :label="label">{{ label }}</slot>
</view>
</view>
</view>
<view class="right text-grey-lighten-1 flex-end vertical-align-middle" :class="[black_tmeme ? 'bk' : '']">
<view class="text-size-s pr-10 d-inline-block">
<slot name="rightValue" :value="value">{{ value }}</slot>
</view>
<slot name="rightIcon" :icon="rightIcon">
<tm-icons style="line-height: normal;" :color="rightIconColor" dense v-if="showRightIcon" :size="rightIconSize" :name="!showContent ? rightIcon : 'icon-angle-down'"></tm-icons>
</slot>
</view>
</view>
<view @click.stop="" class="group pt-24" v-if="showContent && group === true"><slot name="group" :show="showContent"></slot></view>
</view>
<view class="tm-listitem-border" :class="[pz_themeCus.borderBottom ? 'border-grey-lighten-4-b-1 ' : '',color, 'mx-' + pz_themeCus.padding[0], black_tmeme ? 'bk' : '']"></view>
</view>
</view>
</template>
<script>
/**
* 列表
* @description 配合tm-grouplist使用时,可以组合成手风琴模式。
* @property {Boolean} black = [true|false] 默认:null,暗黑模式
* @property {Number} round = [] 默认:4,单位upx, 外部圆角
* @property {Array} margin = [] 默认:[0,0],单位upx,外间距。
* @property {Array} padding = [] 默认:[32,32],单位upx,内间距。
* @property {Number} shadow = [] 默认:6,单位upx,投影
* @property {String} url = [] 默认:'',打开的链接。不空空时,将会打开链接,如果发生错误,会触发error事件。
* @property {String} open-type = [navigate | navigateBack |reLaunch | redirect | switchTab] 默认:'navigate',打开的链接的方式。navigate | navigateBack |reLaunch | redirect | switchTab
* @property {String} class-style = [] 默认:'',自定义类。
* @property {Boolean} dense = [true|false] 默认:false,去除外间距。
* @property {Boolean} border = [true|false] 默认:false,开启边线模式。
* @property {Boolean} border-bottom = [true|false] 默认:false,是否显示下划线。
* @property {String} bg-color = [] 默认:"",自定义背景色。
* @property {String} disabled-color = [] 默认:"",禁用时的背景色
* @property {String} color = [] 默认:"white",主题色
* @property {String|Number} left-icon-size = [] 默认:"38",左边图标 大小 。
* @property {String|Number} right-icon-size = [] 默认:"24",右边图标 大小 。
* @property {String|Number} left-icon = [] 默认:"icon-gem",左边图标
* @property {Boolean} show-left-icon = [true|false] 默认:false,是否显示左边图标。
* @property {String} left-icon-color = [] 默认:"primary",左图标 主题色。
* @property {String} right-icon = [] 默认:"icon-angle-right",左图标
* @property {String} right-icon-color = [] 默认:"primary",右图标颜色。
* @property {Boolean} show-right-icon = [] 默认:false 是否显示右边图标。
* @property {String} value = [] 默认:"" 右边文字。
* @property {String} title = [] 默认:"",标题。
* @property {String} title-color = [] 默认:"grey-darken-3",标题颜色。
* @property {String} font-size = [] 默认:32,标题文字大小。
* @property {String} label = [] 默认:"",下方文字说明。
* @property {Boolean} group = [true|false] 默认:false,是否开启拓展功能,点击展开内容。slot:group的内容。
* @property {Boolean} disabled = [true|false] 默认:false,是否禁用
* @property {Function} click 点击组件时触发
* @property {Function} error (当url不为空时,打开出错时发出)
* @property {Function} change (当group为true时才会触发事件,显示和隐藏扩展内容。)
* @example <tm-listitem title="而退役" value="9"></tm-listitem>
*/
import tmIcons from "@/tm-vuetify/components/tm-icons/tm-icons.vue"
export default {
components:{tmIcons},
name: 'tm-listitem',
props: {
disabled: {
type: Boolean,
default: false
},
// 是否开启嵌套内容。
group: {
type: Boolean,
default: false
},
// 打开的链接。不空空时,将会打开链接,如果发生错误,会触发error事件。
url: {
type: String,
default: ''
},
// 打开的链接的方式。navigate | navigateBack |reLaunch | redirect | switchTab
openType: {
type: String,
default: 'navigate'
},
black: {
type: String | Boolean,
default: null
},
//class.
classStyle: {
type: String,
default: ''
},
// 去除外间距。
dense: {
type: Boolean,
default: false
},
// false
border: {
type: String | Boolean,
default: false
},
// 是否显示下划线
borderBottom: {
type: Boolean,
default: false
},
// 自定义背景色。
bgColor: {
type: String,
default: ''
},
// 主题色名称。
color: {
type: String,
default: 'white'
},
// 主题色名称。
disabledColor: {
type: String,
default: 'grey-lighten-3 text'
},
round: {
type: String | Number,
default: 3
},
leftIconSize: {
type: String | Number,
default: '32'
},
leftIcon: {
type: String,
default: 'icon-gem'
},
// 是否显示左边图标。
showLeftIcon: {
type: Boolean | String,
default: false
},
leftIconColor: {
type: String,
default: 'primary'
},
rightIcon: {
type: String,
default: 'icon-angle-right'
},
rightIconSize: {
type: String | Number,
default: '24'
},
rightIconColor: {
type: String,
default: 'grey-lighten-1'
},
// 是否显示右边图标。
showRightIcon: {
type: Boolean | String,
default: true
},
// 右边文字。
value: {
type: String,
default: ''
},
// 标题。
title: {
type: String,
default: ''
},
titleColor:{
type:String,
default:'grey-darken-3'
},
// 询部的文字说明
label: {
type: String,
default: ''
},
shadow: {
type: String | Number,
default: 2
},
fontSize: {
type: String | Number,
default: '32'
},
// 单位upx
margin: {
type: Array,
default: () => {
return [32, 20];
}
},
padding: {
type: Array,
default: () => {
return [24, 20];
}
},
flat:{
type:String|Boolean,
default:false,
},
// 跟随主题色的改变而改变。
fllowTheme:{
type:Boolean|String,
default:true
}
},
computed: {
pz_themeCus: {
get: function() {
if(this.flat===true){
return {
margin: [0, 0],
padding: [0, 0],
shadow: 0,
round: 0,
borderBottom: false
};
}
return this.pz_theme;
},
set: function(val) {
val = val || {};
this.pz_theme = {...val}
}
},
black_tmeme:function(){
if(this.black!==null) return this.black;
return this.$tm.vx.state().tmVuetify.black;
},
color_tmeme:function(){
if(this.$tm.vx.state().tmVuetify.color!==null&&this.$tm.vx.state().tmVuetify.color && this.fllowTheme){
return this.$tm.vx.state().tmVuetify.color;
}
return this.leftIconColor;
}
},
data() {
return {
pz_theme: {
margin: [0, 0],
padding: [0, 0],
shadow: 0,
round: 0,
borderBottom: false
},
showContent: false
};
},
created() {
this.pz_themeCus = {
margin: this.margin,
padding: this.padding,
shadow: this.shadow,
round: this.round,
borderBottom: this.borderBottom
};
},
mounted() {
},
methods: {
click(e) {
if(this.disabled===true) return;
let t = this;
if (this.group === true && typeof this.group === 'boolean') {
this.showContent = !this.showContent;
if (!this.$tm.getParentAls('tm-listitem', this.$parent)) {
let preat = this.$tm.getParentAls('tm-grouplist', this.$parent);
if(preat){
preat.change(t._uid);
}
}
this.$emit('change', this.showContent);
return;
}
if (!this.url) {
this.$emit('click', e);
return ;
}
if (this.openType === 'switchTab') {
uni.switchTab({
url: this.url,
fail: er => {
t.$emit('error', er);
}
});
}else{
uni.navigateTo({
url:this.url
})
}
},
setConfig(val){
this.$nextTick(function(){
this.pz_themeCus = {...this.pz_themeCus,...val};
})
}
}
};
</script>
<style lang="scss" scoped>
.tm-listitem {
position: relative;
.tm-listitem-border {
}
.nom{
margin: 0 !important;
padding: 0 !important;
}
}
.left {
max-width: 400upx;
width: 400upx;
.left-tm-content {
max-width: 100upx;
line-height: 0;
}
.tm-content {
max-width: 290upx;
justify-content: center;
align-content: center;
display: flex;
}
}
.right{
// #ifdef MP
line-height: 0;
// #endif
}
.group {
transition: all 0.6s;
}
</style>
@@ -0,0 +1,119 @@
<template>
<view @click="$emit('click',$event)" class="tm-loadding flex-center vertical-align-middle">
<!-- 加载中 -->
<view style="line-height: 0;" v-if="model=='load'" class="tm-loadding-load d-inline-block vertical-align-middle">
<tm-icons dense :name="icon?icon:'icon-loading'" :color="color?color:'grey'"></tm-icons>
</view>
<view style="line-height: 0;" v-if="model=='fail'" class="tm-loadding-error d-inline-block vertical-align-middle">
<tm-icons dense :name="icon?icon:'icon-wind-cry'" :color="color?color:'red'"></tm-icons>
</view>
<view style="line-height: 0;" v-if="model=='success'" class="tm-loadding-error d-inline-block vertical-align-middle">
<tm-icons dense :name="icon?icon:'icon-wind-smile'" :color="color?color:'green'"></tm-icons>
</view>
<text class="text-size-s pl-12" :class="['text-'+(color?color:text[model].color)]">{{label?label:text[model].text}}</text>
</view>
</template>
<script>
/**
* 加载状态
* @description 为了方便管理数据加载提示。在全为true时,fail最先展示 ,其次success,其次load.
* @property {Boolean} load = [true|false] 默认true,优先级最低。
* @property {Boolean} success = [true|false] 默认false,优先级高于load。
* @property {Boolean} fail = [true|false] 默认false,优先级高于success。
* @property {String} label = [] 默认 '',自定义文本
* @property {String} icon = [] 默认 '',自定义图标
* @property {String} color = [] 默认 '',自定义主题
* @property {Function} click 点击事件
* @example <tm-loadding ></tm-loadding>
*/
import tmIcons from "@/tm-vuetify/components/tm-icons/tm-icons.vue"
export default {
components:{tmIcons},
name:"tm-loadding",
props:{
// 优先级最低。
load:{
type:Boolean,
default:true
},
//优先级最高。
fail:{
type:Boolean,
default:false
},
//优先级高于load
success:{
type:Boolean,
default:false
},
label:{
type:String,
default:''
},
icon:{
type:String,
default:''
},
color:{
type:String,
default:''
}
},
computed:{
model:function(){
if(this.fail) return 'fail';
if(this.success) return 'success';
if(this.load) return 'load';
return 'load';
}
},
data() {
return {
text:{
load:{
text:"加载中...",
color:"grey"
},
fail:{
text:"加载失败...",
color:"red"
},
success:{
text:"加载成功...",
color:"green"
},
loadmore:{
text:"上拉加载更多",
color:"grey"
},
nomore:{
text:"没有更多了哦",
color:"grey"
}
}
};
}
}
</script>
<style lang="scss" scoped>
.tm-loadding{
.tm-loadding-load{
animation: xhRote 0.8s infinite linear;
}
}
@keyframes xhRote{
0%{
transform: rotate(0deg);
}
100%{
transform: rotate(360deg);
}
}
</style>
@@ -0,0 +1,210 @@
<template>
<view :key="cKey" class="tm-lottie flex-center">
<!-- #ifndef H5 -->
<canvas :style="{ width: w_w + 'px', height: h_h + 'px' }" id="cid" canvas-id="cid" class="cid"></canvas>
<!-- #endif -->
<!-- #ifdef H5 -->
<canvas :style="{ width: w_w + 'px', height: h_h + 'px' }" :id="cuid" :canvas-id="cuid"></canvas>
<!-- #endif -->
</view>
</template>
<script>
import lottie from 'tm-vuetify/tool/function/lottie.js';
export default {
name: 'tm-lottie',
props: {
width: {
type: Number | String,
default: 420
},
height: {
type: Number | String,
default: 420
},
url: {
type: String||Object,
default: ()=>{
return "";
}
},
loop: {
type: Boolean,
default: true
},
autoplay: {
type: Boolean,
default: true
}
},
computed: {
w_w: function() {
return uni.upx2px(this.width);
},
h_h: function() {
return uni.upx2px(this.height);
}
},
data() {
return {
cuid: 'cid',
cKey: ''
};
},
created() {
// #ifdef H5
this.cuid = this.$tm.guid();
// #endif
this.cKey = this.$tm.guid();
},
destroyed() {
// 释放内存。
lottie.destroy()
},
async mounted() {
if (this.url) {
this.$nextTick(async function() {
await this.LoadLottiePlay_Mp();
});
}
},
methods: {
async LoadLottiePlay_Mp(url) {
const canvasContext = uni.createCanvasContext(this.cuid, this);
// #ifdef MP-WEIXIN || MP-ALIPAY
// let canvas = await this.getNodeCanvasNodeRef();
// console.log(canvas);
// #endif
// 请求到的lottie json数据
let animationData=null
// 请求lottie的路径。注意开启downloadFile域名并且返回格式是json
const animationPath = url || this.url;// url || this.url
if(typeof animationPath ==='string'&&animationPath!=''){
let p = await this.rloadJson(animationPath).catch(e=>{
uni.$tm.toast(JSON.stringify(e));
})
animationData = JSON.parse(p.data.data)
}else{
animationData = url;
console.log(animationData)
}
if(!animationData) return;
// 指定canvas大小
canvasContext.canvas = {
width: this.w_w,
height: this.h_h
};
try {
// 如果同时指定 animationData 和 path 优先取 animationData
lottie.loadAnimation({
renderer: 'canvas', // 只支持canvas
loop: this.loop,
autoplay: this.autoplay,
animationData: animationData,
path: '',
rendererSettings: {
context: canvasContext,
clearCanvas: true
}
});
} catch (e) {
console.log(e);
}
},
//mp alipay
getNodeCanvasNodeRef(){
return new Promise((resolve,reject)=>{
const query = uni.createSelectorQuery().in(this)
query
.select('.cid')
.fields({ node: true, size: true })
.exec(res => {
console.log(res);
const canvasNode = res[0].node
const canvasDpr = uni.getSystemInfoSync().pixelRatio
const canvasWidth = res[0].width
const canvasHeight = res[0].height
const ctx = canvasNode.getContext('2d')
resolve(ctx,{dpr:canvasDpr,width:canvasWidth,height:canvasHeight,node:canvasNode});
})
})
},
async LoadLottiePlay_H5(url) {
const canvasContext = uni.createCanvasContext(this.cuid, this);
// 请求到的lottie json数据
let animationData=null
// 请求lottie的路径。注意开启downloadFile域名并且返回格式是json
const animationPath = url || this.url;// url || this.url
// 指定canvas大小
canvasContext.canvas = {
width: this.w_w,
height: this.h_h
};
try {
// 如果同时指定 animationData 和 path 优先取 animationData
lottie.loadAnimation({
renderer: 'canvas', // 只支持canvas
loop: this.loop,
autoplay: this.autoplay,
animationData: '',
path: animationPath,
rendererSettings: {
context: canvasContext,
clearCanvas: true
}
});
} catch (e) {
console.log(e);
}
},
play() {
lottie.play();
},
stop() {
lottie.stop();
},
pause() {
lottie.pause();
},
rloadJson(url){
return new Promise((res,rej)=>{
uni.request({
responseType:'json',
url:url,
success: (v) => {
res(v)
},
fail: (e) => {
console.log(e);
rej(e)
}
})
})
},
// type:1正向播放,-1反向
setDirection(type = 1) {
lottie.setDirection(type);
},
async registerAnimation(url) {
if (!url) return;
lottie.destroy();
this.cKey = this.$tm.guid();
await this.LoadLottiePlay_Mp(url);
lottie.resize();
}
}
};
</script>
<style lang="scss"></style>
@@ -0,0 +1,432 @@
<template>
<view class="tm-mapSelectedPoint" :class="[black_tmeme?'grey-darken-5':'']">
<map :scale="mapscale" id="MapTm" ref="MapTm" @regionchange="moveMapChange" :markers="markers" :latitude="mapCenter.lat" :longitude="mapCenter.lng" :style="{width:`${width}rpx`,height:`400rpx`}"></map>
<view class=" pa-32 ">
<view class="tm-mapSelectedPoint-contr">
<view class="pb-32"><text class=" text-size-n fulled text-overflow-1">当前:{{adress.adress||"获取失败,请移动地图选择"}}</text></view>
<view class="flex-between pb-10">
<tm-button @click="confirm" block height="80" style="width:60%">确认位置</tm-button>
<view style="width:35%" class="flex-shrink">
<tm-button :black="black_tmeme" :fllowTheme="fllowTheme" :theme="color_tmeme" @click="getLocation" text block height="80" icon="icon-position-fill">定位当前</tm-button>
</view>
</view>
</view>
<view v-if="adressList.length>0" class="grey-lighten-5 px-24 round-6 mt-32" :class="[black_tmeme?'bk grey-darken-4':'']">
<scroll-view scroll-y :style="{height: scrollHeight+'px'}">
<view @click="selecListitem(item)" v-for="(item,index) in adressList" :key="index" :class="[black_tmeme?'bk':'']" class="py-24 border-b-1 flex-between">
<view class="mr-32">
<view class="pb-12"> {{item.name}}</view>
<view class="text-size-s text-grey">{{item.address}}</view>
</view>
<view class="flex-shrink">
<tm-icons :black="black_tmeme" :fllowTheme="fllowTheme" :color="color_tmeme" size="40" v-if="activeId == item.id" name="icon-check-circle"></tm-icons>
</view>
</view>
</scroll-view>
</view>
</view>
</view>
</template>
<script>
/**
* 地图选点
* @property {Boolean|String} black = [true|false] 默认null,是否开启暗黑模式
* @property {String} color = [] 主题默认:primary,提供是请写主题色名称
* @property {String} map-key = [] 地图key:默认为作者测试的,用户不要使用我的,否则会被限制,key作废。
* @property {String} map-type = [] 地图类型:qqqq,baidu,amp高德。
* @property {Number} scale = [] 默认:14,地图绽放级别5-18
* @property {Number} width = [] 默认:700,组件的宽度。
* @property {Number} height = [] 默认:1200,组件的高度。
* @property {Object} location = [] 默认:{latitude:39.908823,longitude:116.39747},默认的定位点,北京。
* @property {Function} confirm 点击确认位置按钮时返回当前定位资料信息。
* @property {Function} change 当移动地图时的位置信息,返回的结构同confirm
*/
import tmButton from '@/tm-vuetify/components/tm-button/tm-button.vue';
import tmIcons from '@/tm-vuetify/components/tm-icons/tm-icons.vue';
export default {
name:"tm-mapSelectedPoint",
components:{tmButton,tmIcons},
props:{
width:{
type:Number|String,
default:700
},
height:{
type:Number|String,
default:1200
},
location:{
type:Object,
default:()=>{
return {
latitude:0,
longitude:0
}
}
},
scale:{
type:Number,
default:14
},
// 是否开启暗黑模式
black: {
type: String | Boolean,
default: null
},
color: {
type: String | Array,
default: 'primary'
},
// 跟随主题色的改变而改变。
fllowTheme: {
type: Boolean | String,
default: true
},
mapKey:{
type:String,
default:'U3QBZ-3YIKI-YBEGX-5WURG-5ZQE6-ZGFME'
},
mapType:{
type:String,
default:'qq' //qq,baidu,amp
}
},
watch: {
location:{
deep:true,
async handler(newValue, oldValue) {
this.moveMap(newValue.latitude,newValue.longitude)
await this.moveMarkes(newValue.latitude,newValue.longitude)
}
}
},
data() {
return {
now_latitude:0,
now_longitude:0,
mapCenter:{
lat:0,
lng:0
},
markers:[],
timeid:9566555566,
adressList:[],
adress:{
adress:'',
city:[]
},
scrollHeight:0,
activeId:'',
map:null,
};
},
computed:{
black_tmeme: function() {
if (this.black !== null) return this.black;
return this.$tm.vx.state().tmVuetify.black;
},
color_tmeme: function() {
if (this.$tm.vx.state().tmVuetify.color !== null && this.$tm.vx.state().tmVuetify.color && this
.fllowTheme) {
return this.$tm.vx.state().tmVuetify.color;
}
return this.color;
},
mapscale:function(){
return this.scale;
}
},
created() {
this.now_latitude = this.location.latitude;
this.now_longitude = this.location.longitude;
this.mapCenter = {
lat:this.location.latitude,
lng:this.location.longitude
}
},
async mounted() {
let t = this;
this.map = uni.createMapContext("MapTm", this)
let q = await this.$Querey('.tm-mapSelectedPoint-contr',this).catch(e=>{});
let h = q[0].height||80;
this.scrollHeight = uni.upx2px(this.height) - uni.upx2px(400) - h;
// #ifdef MP
uni.authorize({
scope: 'scope.userLocation',
success() {
t.mapready();
}
})
// #endif
// #ifndef MP
this.$nextTick(async function(){
await uni.$tm.sleep(200)
await this.mapready();
})
// #endif
},
methods: {
moveMap(latitude,longitude){
this.map.moveToLocation({
latitude:latitude,
longitude:longitude
})
},
async mapready(){
let t = this;
// #ifndef H5
uni.getSetting({
success: async (res) => {
if(!res.authSetting['scope.userLocation']){
uni.showModal({
title:"权限受限",
content:"你禁止了位置受限,请打开设置,允许访问地址!否则功能无法使用。",
cancelText:"我已允许",
confirmText:"前往设置",
success: async (rks) => {
if(rks.cancel==true){
await t.getLocation();
return
}
uni.openSetting({})
}
})
return;
}
if(t.mapCenter.lat !==0 && t.mapCenter.lng!==0){
t.markers = [t.createMarker( t.mapCenter.lat ,t.mapCenter.lng)]
await t.poiSidel_byTencentMap(t.mapCenter.lat ,t.mapCenter.lng)
return;
}
await t.getLocation();
},
fail: (res) => {
uni.$tm.toast("系统错误")
}
})
// #endif
// #ifdef H5
if(t.mapCenter.lat !==0 && t.mapCenter.lng!==0){
t.markers = [t.createMarker( t.mapCenter.lat ,t.mapCenter.lng)]
await t.poiSidel_byTencentMap(t.mapCenter.lat ,t.mapCenter.lng)
return;
}
await this.getLocation();
// #endif
},
confirm(){
this.$emit('confirm',{...this.adress,latitude:this.now_latitude,longitude:this.now_longitude})
},
async getLocation() {
let t = this;
//非h5通过GPS定位 。
// #ifndef H5
uni.getSetting({
success: async (res) => {
if(!res.authSetting['scope.userLocation']){
uni.showModal({
title:"权限受限",
content:"你禁止了位置受限,请打开设置,允许访问地址!否则功能无法使用。",
cancelText:"我已允许",
confirmText:"前往设置",
success: async (rks) => {
if(rks.cancel==true){
await t.getLocation();
return
}
uni.openSetting({})
}
})
return;
}
uni.getLocation({
type: 'gcj02', //返回可以用于uni.openLocation的经纬度
success:async function (res) {
const latitude = res.latitude;
const longitude = res.longitude;
t.now_latitude = latitude;
t.now_longitude = longitude;
t.mapCenter = {
lat:latitude,
lng:longitude
}
t.moveMap(t.mapCenter.lat,t.mapCenter.lng)
await t.moveMarkes(latitude,longitude)
//缓存定位点。否则失败,间隔15s小于15秒。不允许调用。
uni.setStorageSync('tmvue_map_selectePoint',{latitude:latitude,longitude:longitude})
},
fail: async (err) => {
let p = uni.getStorageSync('tmvue_map_selectePoint');
try{
if(typeof p == 'object'){
const latitude = p .latitude;
const longitude = p .longitude;
t.now_latitude = latitude;
t.now_longitude = longitude;
t.mapCenter = {
lat:latitude,
lng:longitude
}
t.moveMap(t.mapCenter.lat,t.mapCenter.lng)
await t.moveMarkes(latitude,longitude)
}
}catch(e){
uni.$tm.toast('请间隙至少15s调用,不可频繁定位。')
}
}
});
},
fail: (res) => {
uni.$tm.toast("系统错误")
}
})
// #endif
//h5通过ip定位
// #ifdef H5
uni.showLoading({
mask:true,title:'...'
})
//没有获取到地址资料。通过Ip尝试定位 。
let adressPoiObj = await uni.$tm.request.get("https://apis.map.qq.com/ws/location/v1/ip?key="+this.mapKey).catch(e=>{
uni.hideLoading();
uni.$tm.toast("地址解析错误")
})
if(adressPoiObj['status']==0){
t.now_latitude = adressPoiObj.result.location.lat;
t.now_longitude = adressPoiObj.result.location.lng;
t.map.moveToLocation({
latitude:t.now_latitude,
longitude:t.now_longitude
})
t.moveMap(t.mapCenter.lat,t.mapCenter.lng)
t.markers = [t.createMarker( adressPoiObj.result.location.lat,adressPoiObj.result.location.lng)]
await t.poiSidel_byTencentMap(adressPoiObj.result.location.lat,adressPoiObj.result.location.lng)
}
// #endif
},
createMarker(latitude,longitude){
let id = 636598;
let label = {
content:'',
color:'#000000',
fontSize:12,
bgColor:'red',
padding:5,
textAlign:'center',
}
return {
id:id,
iconPath:'/static/posiimg.png',
width:45,
height:45,
latitude:latitude,
longitude:longitude,
label:label,
}
},
moveMapChange(e){
var etype = ''
// #ifdef APP-PLUS
etype = 'end'
// #endif
// #ifdef MP-WEIXIN
etype = e.type
// #endif
clearTimeout(this.timeid);
if(etype ==='end'){
let t = this;
this.timeid = setTimeout(function() {
t.map.getCenterLocation({
success:async (res)=>{
await t.moveMarkes(res.latitude,res.longitude)
}
})
}, 350);
}
},
async selecListitem(item){
let t = this;
this.$set(this.adress,'adress',item.address)
await this.moveMarkes(item.location.latitude,item.location.longitude)
this.activeId = item.id;
},
async moveMarkes(latitude,longitude,callback){
let t = this;
t.markers = []
await uni.$tm.sleep(50)
if(t.markers.length==0){
t.markers = [t.createMarker( latitude,longitude)]
}
t.now_latitude = latitude;
t.now_longitude = longitude;
t.poiSidel_byTencentMap(latitude,longitude)
},
//mp腾讯地址获取当前地址资料。
async poiSidel_byTencentMap(latitude,longitude){
let t = this;
uni.showLoading({
mask:true,title:'...'
})
let lot=`location=${latitude},${longitude}&get_poi=1&key=${this.mapKey}`
let adressPoiObj = await uni.$tm.request.get("https://apis.map.qq.com/ws/geocoder/v1/?"+lot).catch(e=>{
uni.hideLoading();
uni.$tm.toast("地址解析错误")
})
uni.hideLoading();
if(adressPoiObj.status!==0){
uni.$tm.toast(adressPoiObj.message)
return;
}
let adress = adressPoiObj.result;
this.$set(this.adress,'adress',adress.address)
this.$set(this.adress,'city',[adress.address_component.province,adress.address_component.city,adress.address_component.district]);
this.$emit('change',{...this.adress,latitude:this.now_latitude,longitude:this.now_longitude})
let pos = adress.pois;
if(pos.length==0) return;
let list = [];
for(let i=0;i<pos.length;i++){
list.push({
address:pos[i].address,
name:pos[i].title,
id:pos[i].id,
location:{
latitude:pos[i].location.lat,
longitude:pos[i].location.lng
}
})
}
this.adressList = list;
}
},
}
</script>
<style lang="scss">
</style>
@@ -0,0 +1,132 @@
<template>
<view class="tm-maskFlow" :style="{
width:width+'px',
height:height+'px'
}" v-if="show">
<tm-translate @start="$emit('start')" :duration="duration" :wait="0" :animation-name="aniName">
<view @click.stop="closeMask" class="tm-maskFlow-mask" :style="{
background:bgColor,
width:width+'px',
height:height+'px'
}"></view>
<view @click.stop="closeMask" class="tm-maskFlow-body flex-center">
<view @click.stop="">
<slot></slot>
</view>
</view>
</tm-translate>
</view>
</template>
<script>
/**
* 黑色遮罩
* @property {String} bg-color = [rgba(0,0,0,0.35)] 默认:rgba(0,0,0,0.35),背景颜色值。
* @property {Boolean} close = [] 默认:true,点击遮罩是否关闭。
* @property {Boolean} blur = [] 默认:true,是否显示模糊背景。
* @property {Boolean} value = [] 默认:false,推荐使用value.sync或者v-model,来控制显示和关闭。
* @property {Function} change 和v-model同步,显示 和隐藏时触发,返回当前变化 参数true显示,false关闭。
* @example <tm-maskFlow v-model="show"></tm-maskFlow>
*/
import tmTranslate from "@/tm-vuetify/components/tm-translate/tm-translate.vue"
export default {
components:{tmTranslate},
name: 'tm-maskFlow',
model: {
prop: "value",
event: 'input'
},
props: {
bgColor: {
type: String,
default: 'rgba(0,0,0,0.35)'
},
close: {
type: Boolean,
default: true
},
value: {
type: Boolean,
default: false
},
duration:{
type:Number,
default:300
},
blur:{
type:Boolean|String,
default:true,
}
},
data() {
return {
width: 0,
height: 0,
aniName: 'fadeIn'
};
},
created() {
let syinfo = uni.getSystemInfoSync();
this.width = syinfo.screenWidth;
this.height = syinfo.screenHeight;
},
watch:{
show:function(){
this.$emit("input", this.value)
this.$emit("change", this.value)
this.$emit("update:value", this.value)
}
},
computed: {
show: {
get: function() {
return this.value;
},
set: function(val) {
this.$emit("input", val)
this.$emit("change", val)
this.$emit("update:value", val)
},
}
},
methods: {
closeMask() {
if (!this.close) return;
this.show = false;
}
},
}
</script>
<style lang="scss" scoped>
.tm-maskFlow {
position: fixed;
left: 0;
top: 0;
z-index: 500;
.tm-maskFlow-mask {
left: 0;
top: 0;
width: 100%;
height: 100%;
.blur{
backdrop-filter:blur(3px);
}
}
.tm-maskFlow-body {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow-y: auto;
}
}
</style>
+223
View File
@@ -0,0 +1,223 @@
<template>
<view class="tm-menu relative d-inline-block">
<view @click.stop="toogle">
<slot></slot>
</view>
<view v-if="showMenu" class="tm-menu-block absolute animation" :class="[direction,mentDirection]">
<view v-if="direction==='bottom'" class=" px-16" :class="[tipFangx]">
<text class="iconfont icon-sort-up " style="font-size: 46upx;line-height: 0.5;margin-bottom: -4upx;"
:class="{
'text-white':!black_tmeme,
'text-grey-darken-3':black_tmeme
}"></text>
</view>
<view class="round-3 overflow-x" :style="{maxHeight:maxHeight+'rpx'}">
<tm-listitem
@click="showMenu=false;$emit('change',{index:index,value:item})"
:black="black_tmeme"
v-for="(item,index) in list" :key="index"
dense
:left-icon="item['icon']"
:left-icon-color="(item['iconColor']||iconColor)"
:left-icon-size="36"
:show-left-icon="item['icon']?true:false"
:round="0"
>
<text class="text-size-n">
{{ listType?item:item[rangKey] }}
</text>
</tm-listitem>
</view>
<view v-if="direction==='top'" class=" px-16" :class="[tipFangx]">
<text class="iconfont icon-sort-down " style="font-size: 46upx;line-height: 0.5;margin-top: -4upx;"
:class="{
'text-white':!black_tmeme,
'text-grey-darken-3':black_tmeme
}"></text>
</view>
</view>
<tm-maskFlow :blur='false' v-model="showMenu" bgColor='none'></tm-maskFlow>
</view>
</template>
<script>
/**
* 弹出菜单
* @property {Boolean} black = [true|false] 默认:false,暗黑模式。
* @property {Boolean} show = [true|false] 默认:false,始终显示菜单。
* @property {Boolean} disabled = [true|false] 默认:false,禁用
* @property {Number} maxHeight = [] 默认:500,单位rpx
* @property {String} icon-color = [] 默认:primary,如果有图标将会使用这个图标颜色,如果在数据格式中不提供
* @property {String} tip-direction = [left | center | right] 默认:center,指示三角形的方向
* @property {String} direction = [top | bottom] 默认:bottom,弹出方向,top | bottom
* @property {String} ment-direction = [left | center | right] 默认:center,弹出在父组件上的对齐方式,默认居中。可选left,right,center
* @property {Array} list = [] 默认:[],菜单列表可以是字符串对象或者数组对象。
* @property {String} rang-key = [] 默认:title,菜单列表数组对象时,需要提供key
* @property {Function} change 点击列表项目时触发,返回:{index:index,value:item}
* @example <tm-menu :list="['菜单1','菜单2']"><tm-button>弹出菜单</tm-button></tm-menu>
*
*/
import tmGrouplist from "@/tm-vuetify/components/tm-grouplist/tm-grouplist.vue"
import tmListitem from "@/tm-vuetify/components/tm-listitem/tm-listitem.vue"
import tmMaskFlow from "@/tm-vuetify/components/tm-maskFlow/tm-maskFlow.vue"
export default {
components:{tmGrouplist,tmListitem,tmMaskFlow},
name: "tm-menu",
props: {
black: {
type:Boolean | String,
default:null
},
maxHeight:{
type:Number|String,
default:500
},
// 三角形的方向。left | center | right
tipDirection: {
type: String,
default: 'center'
},
// 始终显示菜单
show: {
type: Boolean,
default: false
},
disabled: {
type: Boolean,
default: false
},
// 如果列表有图标,显示的图标颜色主题名称
iconColor: {
type: String,
default: 'primary'
},
// 弹出方向,top | bottom
direction: {
type: String,
default: 'bottom'
},
// 弹出在父组件上的对齐方式,默认居中。可选left,right,center
mentDirection: {
type: String,
default: 'center'
},
list: {
type: Array,
default: () => {
return [];
}
},
// 如果list提供的是对象数组,需要提供对象的key。如果是string对象,无需提供。默认title
rangKey: {
type: String,
default: 'title'
}
},
computed: {
listType: function() {
return typeof this.list[0] === 'string';
},
tipFangx: function() {
if (this.tipDirection === 'left') return 'flex-start';
if (this.tipDirection === 'center') return 'flex-center';
if (this.tipDirection === 'right') return 'flex-end';
},
black_tmeme: function() {
if (this.black !== null) return this.black;
return this.$tm.vx.state().tmVuetify.black;
}
},
data() {
return {
showMenu: false,
};
},
mounted() {
if(this.disabled) return;
this.showMenu = this.show;
},
methods: {
toogle() {
if (!this.list || this.list.length == 0) return;
if(this.disabled) return;
this.showMenu = !this.showMenu;
this.$emit("update:show",this.showMenu)
},
open() {
if (!this.list || this.list.length == 0) return;
if(this.disabled) return;
this.showMenu = true;
this.$emit("update:show",true)
},
off() {
if (!this.list || this.list.length == 0) return;
if(this.disabled) return;
this.showMenu = false;
this.$emit("update:show",false)
}
},
}
</script>
<style lang="scss" scoped>
.tm-menu {
position: relative;
text-align: center;
.tm-menu-block {
z-index: 501;
width: 320upx;
&.bottom {
top: 100%;
bottom: inherit;
animation: roteScale 0.3s ease-in-out;
}
&.top {
top: inherit;
bottom: 100%;
animation: roteScaleTop 0.3s ease-in-out;
}
&.center {
left: calc(50% - 160upx);
}
&.left {
left: 0upx;
}
&.right {
right: 0upx;
}
}
}
@keyframes roteScale{
0%{
transform: scale(0.9) translateY(-20rpx);
opacity: 0;
}
100%{
transform: scale(1) translateY(0rpx);
opacity: 1;
}
}
@keyframes roteScaleTop{
0%{
transform: scale(0.9) translateY(20rpx);
opacity: 0;
}
100%{
transform: scale(1) translateY(0rpx);
opacity: 1;
}
}
</style>
@@ -0,0 +1,259 @@
<template>
<view @click.stop="" class="tm-menubars fulled ">
<view v-if="!transparent" :style="{ height: tabHeight }"></view>
<view v-if="!transparent" :style="{ height: '45px' }"></view>
<view class="body fulled" :class="[
black_tmeme ? 'bk grey-darken-5 ' : '',
'shadow-' + color_tmeme + `-${shadow} `,
transparent ? (isTransparent ? 'transparent flat ' + `text-${fontColorTheme}` : color_tmeme) : color_tmeme,
flat ? 'flat' : ''
]">
<view :style="{ height: tabHeight }"></view>
<view class="body_wk flex-between">
<view class="left flex-start">
<block v-if="showback">
<view v-if="pageUrl && isHome == false" class="home-btn mr-20 text flex-center flex-shrink"
:class="[color_tmeme,black_tmeme ? 'outlined bk' : '']">
<navigator :url="pageUrl" open-type="reLaunch" class="flex-center">
<text class="iconfont icon-home" :style="{ fontSize: '32rpx' }"></text>
</navigator>
</view>
<navigator v-if="!pageUrl" open-type="navigateBack" class="flex-center px-24 flex-shrink fulled-height">
<text class="iconfont icon-angle-left" :class="[`text-${fontColorTheme}`]" :style="{ fontSize: '28rpx' }"></text>
</navigator>
</block>
<slot name="left" :data="{ style: widths, isTransparent: isTransparent, title: title }"></slot>
</view>
<view class="center flex-center text-size-g text-overflow text-align-center" :class="[`text-${fontColorTheme}`]">
<slot name="default" :data="{ style: widths, isTransparent: isTransparent, title: title }">
{{ title }}
</slot>
</view>
<view class="right flex-end" :style="{ width: widths.btns }">
<slot name="right" :data="{ style: widths, isTransparent: isTransparent, title: title }"></slot>
</view>
</view>
</view>
</view>
</template>
<script>
/**
* 菜单工具栏
* @property {Boolean} black = [true|false] 默认:false,暗黑模式。
* @property {String|Array} color = [primary] 默认:primary,主题颜色名称如果为array,则为渐变色格式第一个方向:top,bottom,right,left[top,color1,color2],如果为string时,可以为rgba,rgb,#fff或者颜色主题名称如:purple-darken-4
* @property {String} theme = [black|white] 默认:custom,只有在transparent:true时,black:表示黑色模式,文字变白。white文字变黑。
* @property {String} font-color = [] 默认:null,文字颜色,默认不需要提供根据主题色自动推断。一旦赋值,自带的theme失效。使用用户颜色类。
* @property {String} home-url = [] 默认:'/pages/index/index',应用的首页地址。当从其它页面以reLaunch进入非首页时会自动显示首页按钮。
* @property {Boolean} flat = [true|false] 默认:false,去除投影,边线
* @property {Boolean} transparent = [true|false] 默认:false,开启透明顶部。下拉时会变成自定义的背景色。
* @property {Number} scroll-tobg = [true|false] 默认:0,当值大于0即开启透明背景。下拉时达到设定的值,即显示自定义的背景和文字色。
* @property {Number | String} width = [] 默认:0,宽度,数字,或者百度比。数字的单位是upx
* @property {Boolean} showback = [true|false] 默认:true,是否显示左边的返回和首页按钮。
* @property {String} title = [] 默认:标题, 中间标题文字。
* @example <tm-menubars color="white" :showback="false"></tm-menubars>
*
*/
import tmIcons from '@/tm-vuetify/components/tm-icons/tm-icons.vue';
export default {
components: {
tmIcons
},
name: 'tm-menubars',
props: {
// 是否开启暗黑模式
black: {
type: String | Boolean,
default: null
},
// 主题颜色名称如果为array,则为渐变色格式第一个方向:top,bottom,right,left[top,color1,color2]
// 如果为string时,可以为rgba,rgb,#fff或者颜色主题名称如:purple-darken-4
color: {
type: String | Array,
default: 'primary'
},
fontColor:{
type:String,
default:''
},
// custom为自定导航样式 。black标题文字为白。white标题文字为黑。它表示的是所处背景的模式。
theme: {
type: String,
default: 'custom' //'black'|'white'|'custom'
},
// 几乎所有组件都有flat选项,去除投影,边线。
flat: {
type: String | Boolean,
default: false
},
// 是否开启顶部透明模式。
transparent: {
type: String | Boolean,
default: false
},
// 当值大于0即开启透明背景。下拉时达到设定的值,即显示自定义的背景和文字色。
scrollTobg: {
type: Number,
default: 70
},
// 是否显示左边的返回和首页按钮。
showback: {
type: Boolean,
default: true
},
// 中间标题文字。
title: {
type: String,
default: '标题'
},
// 首页页面地址。当前访问子页面时,将会显示首页按钮。
homeUrl: {
type: String,
default: '/pages/index/index'
},
shadow: {
type: String | Number,
default: 3
},
// 跟随主题色的改变而改变。
fllowTheme: {
type: Boolean | String,
default: true
}
},
data() {
return {
pageUrl: '',
nowScrollTop: 0,
isHome: false,
tabHeight: 0
};
},
computed: {
black_tmeme: function() {
if (this.black !== null) return this.black;
return this.$tm.vx.state().tmVuetify.black;
},
color_tmeme: function() {
if (this.$tm.vx.state().tmVuetify.color !== null && this.$tm.vx.state().tmVuetify.color && this
.fllowTheme) {
return this.$tm.vx.state().tmVuetify.color;
}
if(this.transparent){
if(this.isTransparent) return ''
}
return this.color;
},
fontColorTheme:function(){
if(this.theme == 'custom') return this.fontColor;
if(this.transparent){
if(this.isTransparent){
if(this.theme == 'black') return 'white';
if(this.theme == 'white') return 'black';
}
}
return this.fontColor;
},
// 当页面在滚动时返回当前是透明还是不透明背景。
isTransparent: function() {
return this.nowScrollTop < this.scrollTobg;
},
ColorThemeName:function(){
return this.color;
},
widths: function() {
let jnwd = 0; //小程序有的胶囊宽度.
// #ifdef MP
// 胶囊的大小。
let mw = uni.getMenuButtonBoundingClientRect();
jnwd = mw.width;
// #endif
return uni.$tm.objToString({
btns: jnwd + 'px',
});
}
},
created() {
let sysinfo = uni.getSystemInfoSync();
let sysbarheight = 0;
// #ifdef MP || APP-PLUS || APP-VUE
sysbarheight = sysinfo.statusBarHeight;
// #endif
this.tabHeight = sysbarheight + 'px';
},
mounted() {
uni.$on('onPageScroll', e => {
this.nowScrollTop = e.scrollTop;
});
// 检查页面栈
let pages = getCurrentPages();
let nopage = pages[pages.length - 1].route;
if (nopage[0] != '/') {
nopage = '/' + nopage;
}
if (nopage == this.homeUrl) {
this.isHome = true;
}
if (pages.length == 1 && typeof pages[0].route !== 'undefined') {
// #ifdef H5
this.pageUrl = '/';
// #endif
// #ifdef MP
this.pageUrl = this.homeUrl;
// #endif
}
},
methods: {}
};
//
</script>
<style lang="scss" scoped>
.tm-menubars {
.body {
position: fixed;
z-index: 450;
top: 0;
left: 0;
&.transparent {
background: none !important;
transition: all 0.6s;
}
.body_wk {
height: 45px;
// opacity: 0.9;
.left {
max-width: 74px;
min-width: 74px;
.home-btn {
border-radius: 50%;
height: 30px;
width: 30px;
line-height: 30px;
margin-left: 24upx;
}
}
.right {
max-width: 74px;
min-width: 74px;
}
.center {
width: calc(100% - 148px);
flex-shrink: 0;
}
}
}
}
</style>
@@ -0,0 +1,365 @@
<template>
<view>
<view v-if="show_dev" @click.stop.prevent="maskClick" :class="[mask?'mask':'']"
class="tm-message fixed t-0 l-0 fulled fulled-height flex-center">
<view :class="[black_dev?'black bk':'',clickOverlay?'clickOverlay':'']" class="tm-message-body round-6 pa-24 flex-center shadow-24 ">
<view class=" flex-center flex-col">
<view :class="[
model,
]"><text class="iconfont" style="font-size: 54rpx;"
:class="[ `text-${color_tmeme[model]}`,icon_dev||icon[model],black_dev?'text-white':'']"></text>
</view>
<view class="pt-12 text-align-center">
<text class="text-size-n text-align-center "
:class="[black_dev?`text-${color_tmeme[model]||color_tmeme}`+' text-white bk':`text-grey-darken-5`]">
{{label_dev||label[model]}}
</text>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
/**
* 提示框
* @property {Object} color = [] 默认对应的类型主题色
* @property {Object} icon = [] 默认对应的类型图标
* @property {Object} label = [] 默认对应的类型提示文字
* @property {Boolean} black = [] 默认false,是否使用暗黑主题。
*/
export default {
name: 'tm-message',
props: {
color: {
type: Object,
default: () => {
return {
load: 'primary',
error: 'red',
info: 'grey-darken-4',
warn: 'orange',
quest: 'primary',
success: 'green',
disabled: 'pink',
wait: 'primary',
}
}
},
icon: {
type: Object,
default: () => {
return {
load: 'icon-loading',
error: 'icon-times-circle',
info: 'icon-info-circle',
warn: 'icon-exclamation-circle',
quest: 'icon-question-circle',
success: 'icon-check-circle',
disabled: 'icon-ban',
wait: 'icon-clock',
}
}
},
label: {
type: Object,
default: () => {
return {
load: '加载中',
error: '出错啦',
info: '信息提示',
warn: '警告信息',
quest: '似乎有问题',
success: '操作成功',
disabled: '禁止操作',
wait: '请等待',
}
}
},
// 暗黑
black: {
type: Boolean | String,
default: null
},
// 跟随主题色的改变而改变。
fllowTheme: {
type: Boolean | String,
default: true
}
},
computed: {
black_tmeme: function() {
if (this.black !== null) return this.black;
return this.$tm.vx.state().tmVuetify.black;
},
color_tmeme: function() {
if (this.$tm.vx.state().tmVuetify.color !== null && this.$tm.vx.state().tmVuetify.color && this
.fllowTheme) {
let cos = this.$tm.vx.state().tmVuetify.color;
let co={...this.color,info:cos,quest:cos,load:cos,wait:cos};
return co;
}
return this.color;
}
},
data() {
return {
model: 'wait', //load,error,infowarnquestsuccess,disabledwait
icon_dev: '',
label_dev: '',
timeId: 8964566588,
show_dev: false,
mask: false,
black_dev: false,
clickOverlay:false,
};
},
destroyed(){
clearTimeout(this.timeId);
},
methods: {
async anifeed(){
this.clickOverlay = true;
await uni.$tm.sleep(50)
this.clickOverlay = false;
},
//{label,model,icon,mask,wait,black}
show() {
let t = this;
let def = {
label: '',
model: 'info',
icon: '',
mask: false,
wait: 2000,
black: this.black_tmeme
};
let arg = arguments[0] ? {
...def,
...arguments[0]
} : def;
const {
label,
model,
icon,
mask,
wait,
black
} = arg;
this.label_dev = label;
this.model = model;
this.icon_dev = icon;
this.black_dev = black;
this.mask = mask;
clearTimeout(this.timeId);
if (this.model == 'load') {
this.show_dev = true;
} else {
this.show_dev = true;
this.timeId = setTimeout(function() {
t.hide();
}, wait);
}
},
async maskClick(){
await this.anifeed();
},
hide() {
this.show_dev = false;
clearTimeout(this.timeId);
this.mask = false;
this.label_dev = '';
this.model = 'info';
this.model = 'info';
this.icon_dev = '';
this.black_dev = this.black_tmeme;
},
},
};
</script>
<style lang="scss" scoped>
.tm-message {
z-index: 601;
pointer-events: none;
background-color: transparent;
&.mask {
backdrop-filter: blur(3px);
background-color: rgba(0, 0, 0, 0.3);
pointer-events: auto;
}
.tm-message-body {
min-width: 110rpx;
min-height: 120rpx;
max-width: 64%;
backdrop-filter: blur(10px);
background-color: rgba(255, 255, 255, 0.75);
&.black {
background-color: rgba(0, 0, 0, 0.90) !important;
}
animation: outin 0.3s ease-in-out;
&.clickOverlay{
animation: none !important;
}
.load {
animation: load 0.5s infinite linear;
}
.error {
animation: error 1.5s infinite linear;
}
.info {
animation: info 0.5s linear;
}
.warn {
animation: warn 0.5s infinite linear;
}
.quest {
animation: quest 1s infinite linear;
}
.success {
animation: success 1s linear;
}
.disabled {
animation: warn 0.5s infinite linear;
}
.wait {
animation: wait 3.5s infinite linear;
}
}
}
@keyframes outin {
0% {
transform: scale(0.64)
}
25% {
transform: scale(1.1)
}
50% {
transform: scale(0.9)
}
100% {
transform: scale(1)
}
}
// wait:'primary',
@keyframes wait {
0% {
transform: rotate(0deg)
}
100% {
transform: rotate(360deg)
}
}
@keyframes success {
0% {
transform: scale(1.9)
}
25% {
transform: scale(0.7)
}
50% {
transform: scale(1)
}
75% {
transform: scale(0.9)
}
100% {
transform: scale(1)
}
}
@keyframes quest {
0% {
transform: rotate(-45deg)
}
50% {
transform: rotate(45deg)
}
100% {
transform: rotate(-45deg)
}
}
@keyframes warn {
0% {
transform: translateX(-10rpx)
}
50% {
transform: translateX(10rpx)
}
100% {
transform: translateX(-10rpx)
}
}
@keyframes info {
0% {
transform: scale(0.5)
}
100% {
transform: scale(1)
}
}
@keyframes error {
0% {
transform: scale(0.8)
}
50% {
transform: scale(1.2)
}
100% {
transform: scale(0.8)
}
}
@keyframes load {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>
@@ -0,0 +1,302 @@
<template>
<view class="tm-monthCalendar " :class="[inline?'d-inline-block':'d-block']">
<view @click.stop.prevent="showpop=!showpop"><slot></slot></view>
<tm-poup :black="black_tmeme" @change="toogle" ref="pop" v-model="showpop" height="900" >
<view class="tm-monthCalendar-wk">
<view class="shadow-10">
<view class="tm-monthCalendar-title ">
<view class="text-size-g text-align-left pl-32 py-32" :class="[color_tmeme,black_tmeme?'bk':'']">
<view class="text-size-g text-white" style="font-size: 42upx;">{{ selectedDay?selectedDay.year:'' }}</view>
<view class="text-size-lg text-white" style="font-size: 72upx;">
{{ selectedDay?selectedDay.month:'' }}
<text class="text-size-g pl-10"></text>
</view>
</view>
<view class="tm-monthCalendar-close"><tm-icons @click="$refs.pop.close()" name="icon-times" :color="'white'"></tm-icons></view>
</view>
<view class="flex-between pa-24 " style="width: 50%;margin: auto;">
<view><tm-icons @click="preYear" name="icon-angle-left" color="grey-lighten-1"></tm-icons></view>
<view class="text-size-g text-weight-b">{{ titleVale }}</view>
<view><tm-icons @click="nextYear" name="icon-angle-right" color="grey-lighten-1"></tm-icons></view>
</view>
</view>
</view>
<view class="tm-monthCalendar-body">
<view class="tm-monthCalendar-bg flex-center">
<text class="text" :class="[black_tmeme ? ' opacity-5 ' : '']">{{ selectedDay?selectedDay.year:'' }}</text>
</view>
<view class="tm-monthCalendar-content">
<view style="height: 32upx;"></view>
<tm-row>
<tm-col
@click="day_danxuanclick(item, index)"
align="center"
grid="3"
v-for="(item, index) in nowData"
:key="index"
:round="6"
>
<view class="tm-monthCalendar-col flex-center flex-col round-6" :class="[item.checked === true ? color_tmeme : '']">
<text class="text-size-g" :class="[item.checked ? 'text-white' : '']">{{ item.text }}</text>
</view>
</tm-col>
</tm-row>
</view>
<view class="pa-32">
<tm-button @click="confirm" block itemeClass="round-24" :theme="btnColor ? btnColor : color_tmeme" fontSize="32">{{ btnText }}</tm-button>
</view>
</view>
</tm-poup>
</view>
</template>
<script>
/**
* 月份日历
* @description 日历组件,提供节气、农历公历显示,时间范围选择等功能。
* @property {Function} confirm = [] 当选择日期确认后触发,如果未选择确认后不会触发事件。
* @property {String} btn-text = [] 底部按钮确认的文字
* @property {Boolean} inline = [] 默认true,是否内联或者块状block,内联有助于单行内想快速显示操作多个日历。非内联,适合独占一行。
* @property {String} btn-color = [primary|green|orange|red|blue|bg-gradient-blue-lighten] 默认:bg-gradient-blue-lighten底部按钮确认的背景颜色仅支持主题色名称
* @property {String} color = [primary|green|orange|red|blue] 主题默认:primary,提供是请写主题色名称
* @property {String} default-value = [] 默认时间默认:当前时间,格式:'2021-7-21'
* @property {Boolean|String} disabled = [true|false] 是否禁用,只读,默认false
* @property {Boolean|String} black = [true|false] 是否开启暗黑模式
*/
import tmIcons from "@/tm-vuetify/components/tm-icons/tm-icons.vue"
import tmCol from "@/tm-vuetify/components/tm-col/tm-col.vue"
import tmRow from "@/tm-vuetify/components/tm-row/tm-row.vue"
import tmButton from "@/tm-vuetify/components/tm-button/tm-button.vue"
import tmPoup from "@/tm-vuetify/components/tm-poup/tm-poup.vue"
export default {
components:{tmIcons,tmCol,tmRow,tmButton,tmPoup},
name: 'tm-monthCalendar',
props: {
black: {
type: Boolean | String,
default: null
},
disabled: {
type: Boolean | String,
default: false
},
// 默认年月2020-7
defaultValue: {
type: String,
default: ''
},
// 底部按钮文件
btnText: {
type: String,
default: '确认'
},
// 底部按钮背景主题色名称
btnColor: {
type: String,
default: ''
},
// 主题色。
color: {
type: String,
default: 'primary'
},
value: {
type: Boolean,
default: false
},
// 跟随主题色的改变而改变。
fllowTheme:{
type:Boolean|String,
default:true
},
inline:{
type:Boolean|String,
default:true
}
},
model: {
prop: 'value',
event: 'input'
},
computed:{
black_tmeme: function() {
if (this.black !== null) return this.black;
return this.$tm.vx.state().tmVuetify.black;
},
color_tmeme:function(){
if(this.$tm.vx.state().tmVuetify.color!==null&&this.$tm.vx.state().tmVuetify.color && this.fllowTheme){
return this.$tm.vx.state().tmVuetify.color;
}
return this.color;
},
},
watch: {
value:function(val){
this.showpop = val;
},
defaultValue: function(val) {
let d = new Date().toLocaleDateString();
if (this.defaultValue) {
d = this.defaultValue.replace(/-/g,'/');
}
this.selectedDay = null;
this.cal = new Date(d);
this.titleVale = this.cal.getFullYear() + '年';
this.getData();
}
},
data() {
return {
showpop:false,
nowData: [], //当前月份数据。
cal: null, //日历对象数据。
selectedDay: null,
titleVale: '',
dataValue:null,
};
},
mounted() {
let d = new Date().toLocaleDateString();
if (this.defaultValue) {
d = this.defaultValue.replace(/-/g,'/');
this.dataValue = this.defaultValue;
}
this.cal = new Date(d);
this.titleVale = this.cal.getFullYear() + '年';
this.getData();
this.showpop = this.value;
},
methods: {
// 取当前年份的月数据。
getData() {
let text = ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'];
let year = this.cal.getFullYear();
let month = this.cal.getMonth();
this.nowData = [];
for (let i = 0; i < 12; i++) {
let checked = false;
if (this.selectedDay) {
checked = this.selectedDay.year == year && this.selectedDay.index == i ? true : false;
} else {
checked = month == i ? true : false;
}
let obj = {
month: i + 1,
text: text[i],
index: i,
year: year,
checked: checked
};
this.nowData.push(obj);
if (checked) {
this.selectedDay = obj;
}
}
},
confirm() {
this.$emit('confirm', this.selectedDay);
this.$refs.pop.close();
},
close(){
this.$refs.pop.close();
},
toogle(e){
let t = this;
if(e){
this.$nextTick(function(){
if(this.dataValue != this.defaultValue){
this.dataValue = this.defaultValue.replace(/-/g,'/');
}
})
}
this.$emit('input',e);
this.$emit('update:value',e);
},
preYear() {
if (!this.cal) return;
this.cal.setFullYear(this.cal.getFullYear() - 1);
this.titleVale = this.cal.getFullYear() + '年';
this.getData();
},
nextYear() {
if (!this.cal) return;
this.cal.setFullYear(this.cal.getFullYear() + 1);
this.titleVale = this.cal.getFullYear() + '年';
this.getData();
},
day_danxuanclick(item, index) {
if (this.disabled) {
this.$tm.toast('不可选择');
return;
}
let d = { ...item };
this.selectedDay = item;
this.getData();
}
}
};
</script>
<style lang="scss" scoped>
.tm-monthCalendar-col {
width: 100%;
height: 120upx;
// text-align: center;
// line-height: 80upx;
line-height: 0;
position: relative;
.text {
position: absolute;
bottom: 14upx;
}
}
.textOn {
color: #1976d2 !important;
}
.tm-monthCalendar-wk {
width: 100%;
.tm-monthCalendar-title {
position: relative;
.tm-monthCalendar-close {
position: absolute;
top: 32upx;
right: 32upx;
}
}
}
.tm-monthCalendar-body {
position: relative;
.tm-monthCalendar-bg {
height: 400upx;
.text {
font-size: 200upx;
color: rgba(225, 225, 225, 0.4);
}
}
.tm-monthCalendar-content {
width: 100%;
position: absolute;
top: 0;
left: 0;
}
}
</style>
+177
View File
@@ -0,0 +1,177 @@
<template>
<view
class="tm-more relative"
:style="{
height: downOpen ? 'auto !important' : `${hs + 40}px`,
paddingBottom: (downOpen && isRemovBar == false) || (show && !isRemovBar) ? '30px' : 0
}"
>
<view class="tm-more-content"><slot></slot></view>
<view v-if="!downOpen&&show&&showMask" :class="[black_tmeme?'bl':'']" class="tm-more-maskbody absolute"></view>
<view
@click="openMore"
v-if="(!downOpen || isRemovBar == false) && show"
:class="[bgColor,'text-'+iconColor,black_tmeme?'bk':'']"
class="tm-more-mask absolute text-align-center text-size-s text-grey flex-center"
>
<slot name="more" :data="downOpen">
<view class="fulled-height flex-center">
<text>{{downOpen?openLabel:label}}</text>
<view class="tm-more-btn px-24" :class="[downOpen?'on':'']">
<tm-icons :size="24" :color="iconColor" name="icon-angle-down"></tm-icons>
</view>
</view>
</slot>
</view>
</view>
</template>
<script>
/**
* 展开更多
* @property {Number} maxHeight = [] 默认:100,限定多高时展示更多按钮,单位upx
* @property {Boolean} disabled = [] 默认:false,是否禁用,禁用后无法展开更多
* @property {Boolean} showMask = [] 默认:false,是否显示内容遮罩。
* @property {Boolean} open = [] 默认:false,需要open.sync,是否展开更多,可不必填此。此值主要是用来手动控制展开和关闭。
* @property {Boolean} isRemovBar = [] 默认:false,打开后,是否隐藏展开更多
* @property {String} icon-color = [] 默认:grey,展开更多的图标颜色。
* @property {String} bg-color = [] 默认:white,展开更多的背景颜色。
* @property {String} label = [] 默认:展开阅读更多,更多的提示文字。
* @property {String} open-label = [] 默认:收缩阅读更多,更多的提示文字。
* @property {Function} click 点击展开更多时触发。
* @property {Function} change 展开更多变换时触发。
*/
import tmIcons from "@/tm-vuetify/components/tm-icons/tm-icons.vue"
export default {
components:{tmIcons},
name: 'tm-more',
props: {
// 限定多高时展示更多按钮。
maxHeight: {
type: Number,
default: 100
},
disabled: {
type: Boolean,
default: false
},
black: {
type: Boolean|String,
default: null
},
// 是否默认打开.可使用open.sync双向绑定。
open: {
type: Boolean,
default: false
},
// 打开后,是否隐藏展开更多
isRemovBar: {
type: Boolean,
default: false
},
iconColor: {
type: String,
default: 'grey'
},
// 展开更多 的背景色彩。
bgColor: {
type: String,
default: 'white'
},
label:{
type: String,
default: '展开阅读更多'
},
openLabel:{
type: String,
default: '收缩阅读更多'
},
showMask:{
type: Boolean|String,
default: false
}
},
watch: {
open: function() {
this.downOpen = this.open;
}
},
data() {
return {
downOpen: false,
show: true
};
},
mounted() {
this.$nextTick(function(){
this.downOpen = this.open;
let t = this;
t.$Querey('.tm-more-content',t).then(syninfo=>{
if (syninfo[0].height - 40 <= t.hs) {
t.show = false;
}
}).catch(e => {});
})
},
computed: {
hs: function() {
return uni.upx2px(this.maxHeight) || 100;
},
black_tmeme: function() {
if (this.black !== null) return this.black;
return this.$tm.vx.state().tmVuetify.black;
}
},
methods: {
openMore() {
this.$emit('click');
if (this.disabled) return;
this.downOpen = !this.downOpen;
this.$emit('change', this.downOpen);
this.$emit('update:open', this.downOpen);
}
}
};
</script>
<style lang="less" scoped>
.tm-more {
overflow: hidden;
.tm-more-content {
overflow: hidden;
}
.tm-more-maskbody{
height: calc(100% - 40px);
bottom: 40px;
z-index: 10;
width: 100%;
background-image: linear-gradient(rgba(255,255,255,0) ,rgba(255,255,255,1));
&.bl{
background-image: linear-gradient(rgba(0,0,0,0) 30%,rgba(0,0,0,1)) !important;
}
}
.tm-more-mask {
bottom: 0;
width: 100%;
height: 40px;
z-index: 10;
.tm-more-btn{
&.on{
transition: all 0.35s;
transform: rotate(180deg);
}
}
}
}
</style>
@@ -0,0 +1,230 @@
<template>
<view class="tm-pagination flex-center">
<view v-if="tal" class="flex-start flex-wrap flex-between fulled">
<tm-button :fllowTheme="false" :shadow="4" :icon-size="24"
:font-color="nowIndex==1||disabled?'grey':color_tmeme" :black="black_tmeme" @click="tv_pre"
:disabled="nowIndex==1||disabled" theme="white" :item-class="round?'rounded':''" block height="64" width="64"
icon="icon-angle-left"></tm-button>
<block v-for="(item,index) in items " :key="index">
<tm-button :fllowTheme="false" :shadow="4" :disabled="disabled" :black="black_tmeme"
:theme="item==nowIndex?color_tmeme:'white'" @click="clicPages(item)" block
:item-class="round?'rounded':''" height="64" width="64">
{{item}}
</tm-button>
</block>
<tm-button :fllowTheme="false" :shadow="4" :icon-size="24"
:font-color="nowIndex==tal||disabled?'grey':color_tmeme" :black="black_tmeme" theme="white"
@click="tv_next" :disabled="nowIndex==tal||disabled?true:false" :item-class="round?'rounded':''" block
height="64" width="64" icon="icon-angle-right">
</tm-button>
</view>
</view>
</template>
<script>
/**
* 分页
* @property {Function} change 页面改变时触发。
* @property {Number} page = [] 默认:1当前页码,必须使用page.sync
* @property {Number} total-visible = [] 默认:5最大可见页数
* @property {Number} total = [] 默认:0总条数。
* @property {Number} size = [] 默认:8每页的数量。
* @property {Boolean} round = [] 默认:false,是否圆形按钮。
* @property {Boolean} black = [] 默认:false,是否暗黑模式
* @property {Boolean} disabled = [] 默认:false,禁用
* @property {String} color = [] 默认:primary,选中的主题色
* @example <tm-pagination :page.sync="page" :total="100"></tm-pagination>
*/
import tmButton from "@/tm-vuetify/components/tm-button/tm-button.vue"
export default {
components: {
tmButton
},
name: "tm-pagination",
props: {
total: {
type: Number,
default: 0
},
// 最大可见按钮数量。
totalVisible: {
type: Number,
default: 5
},
// 当前页码。需要page.sync同步。
page: {
type: Number,
default: 1
},
// 每页的数量 。
size: {
type: Number,
default: 10
},
// 是否圆形按钮。
round: {
type: Boolean,
default: false
},
// 是否暗黑模式
black: {
type: Boolean | String,
default: null
},
// 主题色
color: {
type: String,
default: 'primary'
},
disabled: {
type: Boolean,
default: false
},
// 跟随主题色的改变而改变。
fllowTheme: {
type: Boolean | String,
default: true
}
},
watch: {
page: function(val) {
this.init()
}
},
computed: {
black_tmeme: function() {
if (this.black !== null) return this.black;
return this.$tm.vx.state().tmVuetify.black;
},
color_tmeme: function() {
if (this.$tm.vx.state().tmVuetify.color !== null && this.$tm.vx.state().tmVuetify.color && this
.fllowTheme) {
return this.$tm.vx.state().tmVuetify.color;
}
return this.color;
},
//总共的页码。
tal: function() {
if (this.total <= this.size) return 1;
let isInt = this.total % this.size;
let ds = this.total / this.size;
return isInt == 0 ? Math.floor(ds) : Math.floor(ds) + 1;
},
items: function() {
const totalVisible = parseInt(this.totalVisible, 10)<=5?5:parseInt(this.totalVisible, 10);
if(this.tal<=totalVisible){
return this.range(1, this.tal)
}
if (totalVisible === 0) {
return []
}
const maxLength = Math.min(
Math.max(0, totalVisible) || this.tal,
Math.max(0, this.maxButtons) || this.tal,
this.tal
)
if (this.tal <= maxLength) {
return this.range(1, this.tal)
}
const even = maxLength % 2 === 0 ? 1 : 0
const left = Math.floor(maxLength / 2)
const right = this.tal - left + 1 + even
if (this.page > left && this.page < right) {
const firstItem = 1
const lastItem = this.tal
const start = this.page - left + 2
const end = this.page + left - 2 - even
const secondItem = start - 1 === firstItem + 1 ? 2 : '...'
const beforeLastItem = end + 1 === lastItem - 1 ? end + 1 : '...'
return [1, secondItem, ...this.range(start, end), beforeLastItem, this.tal]
} else if (this.page === left) {
const end = this.page + left - 1 - even
return [...this.range(1, end), '...', this.tal]
} else if (this.page === right) {
const start = this.page - left + 1
return [1, '...', ...this.range(start, this.tal)]
} else {
return [
...this.range(1, left),
'...',
...this.range(right, this.tal),
]
}
},
},
data() {
return {
nowIndex: 1,
maxButtons: 0,
};
},
mounted() {
let t = this;
this.$Querey('.tm-pagination').then(res => {
let w = res[0].width;
t.maxButtons = Math.floor((w - uni.upx2px(20)) / uni.upx2px(64))
})
},
methods: {
clicPages(index) {
if (index == '...') return;
this.$emit('input', index)
this.$emit('update:page', index)
this.$emit('prev')
this.$emit('change',index)
},
tv_pre() {
this.$emit('input', this.page - 1)
this.$emit('update:page', this.page - 1)
this.$emit('prev')
this.$emit('change',this.page - 1)
},
tv_next() {
this.$emit('input', this.page + 1)
this.$emit('update:page', this.page + 1)
this.$emit('next')
this.$emit('change',this.page + 1)
},
range(from = 1, to) {
const range = []
from = from > 0 ? from : 1
for (let i = from; i <= to; i++) {
range.push(i)
}
return range
},
init() {
this.nowIndex = null;
uni.$tm.sleep(100).then(() => (this.nowIndex = this.page))
},
},
}
</script>
<style lang="scss">
</style>
@@ -0,0 +1,240 @@
<template>
<view class="tm-password ">
<view @click="show=!show" class="flex-center flex-between">
<view class="tm-password-item flex-center "
:class="[
width>0?'':'mx-12',
`round-${round}`,
`shadow-${color_tmeme}-${shadow}`,
model==='line'?black_tmeme?(strVal.length==index?' grey-darken-3 ':'border-grey-a-1 grey-darken-3 '):`border-${color_tmeme}-b-3`:'',
model==='box'?(black_tmeme?(strVal.length==index?'grey-darken-3 ':'border-grey-a-1 grey-darken-3 '):(strVal.length==index?'':`border-${color_tmeme}-a-1 `)):'',
model==='fill'?black_tmeme?'grey-darken-3 ':bgColor:'',
]"
v-for="(item,index) in strLength"
:key="index"
:style="{
width:width>0?'auto':(100/strLength)+'%',
height:height+'rpx'
}"
>
<view
:style="{width:width>0?width+'rpx':'atuo'}"
:class="[
`round-${round}`,
strVal.length==index?'aniOn '+(model!=='line'&&model!=='box'?`border-${color_tmeme}-a-2 shadow-${color_tmeme}-6`:''):'',
strVal.length==index?(model=='box'?`border-${color_tmeme}-a-2 shadow-${color_tmeme}-6`:''):'',
strVal.length==index?(model=='line'&&black_tmeme?`border-${color_tmeme}-a-2 shadow-${color_tmeme}-6`:''):'',
]" class=" fulled fulled-height flex-center">
<text v-if="strVal[index]&&showVal"
class="text-weight-b text-size-lg"
:class="[
black_tmeme?'text-white':`text-${textColor_tmeme}`
]"
>
{{strVal[index]?strVal[index]:""}}
</text>
<view
v-if="(strVal[index]&&!showVal)||(!strVal[index]&&showVal)"
class="tm-password-item-middle "
:class="[
noval==='mline'?'mline round-5 ':'',
noval==='round'?'mround rounded':'',
'tm-password-item-middle-w',
textColor_tmeme
]"
></view>
</view>
</view>
</view>
<tm-keyborad @confirm="$emit('confirm',strVal)" :ok-color="color_tmeme" :model="keyboradModel" :black="black_tmeme" v-model="strVal" :decimal="false" :show.sync="show"></tm-keyborad>
</view>
</template>
<script>
/**
* 密码、验证码输入框
* @property {String|Number} value = [] 默认:'',待输入内容,推荐使用v-model,或者value.sync
* @property {String|Number} round = [] 默认:4,圆角
* @property {String|Number} width = [] 默认:0,项目的宽,默认使用百分比的宽度。
* @property {String|Number} height = [] 默认:100,项目的宽
* @property {String|Number} shadow = [] 默认:0,投影
* @property {Number} code-length = [] 默认:4,输入框数
* @property {String} model = [line|box|fill] 默认:fill,外框的样式。line|box|fill
* @property {String} noval = [mline|round] 默认:round,中间未填充数据的样式。。mline|round
* @property {Boolean} show-val = [] 默认:false,是否展现/隐藏输入内容
* @property {Boolean} black = [] 默认:false,暗黑模式
* @property {String} color = [] 默认:primary,主题色
* @property {String} bg-color = [] 默认:grey-lighten-4,输入背景主题色
* @property {String} text-color = [] 默认:primary,文字颜色。
* @property {String} keyborad-model = [number|password] 默认:number,键盘类型:number|password
* @property {Function} input 输入内容时触发。
* @property {Function} confirm 输入完内容,点击确认时触发
* @example <tm-password v-model="num"></tm-password>
*/
import tmKeyborad from "@/tm-vuetify/components/tm-keyborad/tm-keyborad.vue"
export default {
components:{tmKeyborad},
name:"tm-password",
model:{
prop:'value',
event:'input'
},
props:{
value:{
type:String|Number,
default:''
},
codeLength:{
type:Number,
default:4
},
round:{
type:Number,
default:4
},
shadow:{
type:Number,
default:0
},
bgColor:{
type:String,
default:'grey-lighten-4'
},
height:{
type:Number,
default:100
},
width:{
type:Number,
default:0
},
// 外框的样式。
model:{
type:String,
default:'fill' //外形形状 line|box|fill
},
// 中间未填充数据的样式。
noval:{
type:String,
default:'round' //mline|round
},
// 是否展现内容
showVal:{
type:Boolean,
default:false
},
black:{
type:Boolean|String,
default:null
},
color:{
type:String,
default:'primary'
},
textColor:{
type:String,
default:'grey-darken-3'
},
keyboradModel:{
type:String,
default:'number' //number|password
},
// 跟随主题色的改变而改变。
fllowTheme:{
type:Boolean|String,
default:false
}
},
computed:{
black_tmeme: function() {
if (this.black !== null) return this.black;
return this.$tm.vx.state().tmVuetify.black;
},
color_tmeme:function(){
if(this.$tm.vx.state().tmVuetify.color!==null&&this.$tm.vx.state().tmVuetify.color && this.fllowTheme){
return this.$tm.vx.state().tmVuetify.color;
}
return this.color;
},
textColor_tmeme:function(){
if(this.$tm.vx.state().tmVuetify.color!==null&&this.$tm.vx.state().tmVuetify.color && this.fllowTheme){
return this.$tm.vx.state().tmVuetify.color;
}
return this.textColor;
},
strLength:function(){
if(this.codeLength==0||!this.codeLength) return 1;
return this.codeLength;
},
strVal:{
get:function(){
return this.value;
},
set:function(val){
let p = val;
if(val.length > this.strLength){
p = val.substr(0,this.strLength);;
}
this.$emit("input",p )
this.$emit("update:value",p )
}
},
// strCode:function(){
// let val = this.strVal;
// let strlen = this.strLength - this.strVal.length;
// for(let i=0;i<strlen;i++){
// }
// }
},
data() {
return {
show:false
};
}
}
</script>
<style lang="scss">
.tm-password{
.tm-password-item{
height: 100rpx;
.aniOn{
animation: easinOut 1.5s infinite linear;
}
.tm-password-item-middle{
&.mline{
width: 100%;
height: 10upx;
}
&.mround{
width: 20upx;
height: 20upx;
}
&.tm-password-item-middle-w{
width: 20upx;
}
}
}
}
@keyframes easinOut {
0% {
opacity: 1;
}
50% {
opacity: 0.4;
}
100% {
opacity: 1;
}
}
</style>
@@ -0,0 +1,216 @@
<template>
<view class="tm-pickers d-inline-block fulled">
<view @click.stop.prevent="openPoup"><slot></slot></view>
<tm-poup @change="toogle" ref="pop" v-model="showpop" :height="750" :bg-color="black_tmeme?'grey-darken-5':bgColor">
<view class="tm-pickers-title pa-32 pb-16">
<view class="text-size-n text-align-center" style="min-height: 48rpx;">{{title}}</view>
<view class="tm-pickers-close rounded flex-center " :class="black_tmeme?'grey-darken-3':'grey-lighten-3'">
<tm-icons @click="close" name="icon-times" size="24" :color="black_tmeme?'white':'grey'"></tm-icons>
</view>
</view>
<tm-pickersView v-if="showpop" @change="$emit('change',$event)" @aniStart="aniisTrue=false" @aniEnd="aniisTrue=true" ref="tmPicKersTest" :defaultValue="dataValue"
:itemHeight="itemHeight" :list="list" :rangKey="rangKey"
:childrenKey="childrenKey" :black="black_tmeme" :disabled="disabled"
:bgColor="bgColor"
></tm-pickersView>
<view class="pa-32">
<tm-button :black="black_tmeme" @click="confirm" block itemeClass="round-24" :fllowTheme="fllowTheme" :theme="colorBtn_tmeme" fontSize="32">{{btnText}}</tm-button>
</view>
</tm-poup>
</view>
</template>
<script>
/**
* 普通级联拉选择器(弹层式)
* @description 多级关联,单级关联选择
* @property {String} title = [] 弹层层标题
* @property {String} btn-text = [] 底部按钮确认的文字
* @property {String} btn-color = [primary|green|orange|red|blue|bg-gradient-blue-lighten] 默认:bg-gradient-blue-lighten底部按钮确认的背景颜色仅支持主题色名称
* @property {Array} default-value = [] 默认:[],默认赋值项。可选三种赋值方式,名称赋值,对象赋值,数字序列赋值
* @property {String|Number} item-height = [34|42|50|58|62] 项目的高度单位px
* @property {Array} list = [] 选择器的数据,可选格式:Array<string>,Array<object>.如果为object格式需要提供rangKey.如果为多级需要提供children.key值
* @property {String} rang-key = [text|title] 默认:text,如果List格式为对象数组,需要提供此值
* @property {String} children-key = [children] 默认:children,如果List格式为对象数组且为多级联选择,需要提供此值,理论上无限级联数据
* @property {String|Boolean} black = [true|false] 是否开启暗黑模式。
* @property {String|Boolean} disabled = [true|false] 是否禁用
* @property {String} bg-color = [white|blue] 默认:white,白色背景;请填写背景的主题色名称。
* @property {Function} change 列数被选中改变时触发。
* @property {Function} confirm = [] 返回当前选中的数据
*
*/
import tmButton from "@/tm-vuetify/components/tm-button/tm-button.vue"
import tmPoup from "@/tm-vuetify/components/tm-poup/tm-poup.vue"
import tmIcons from "@/tm-vuetify/components/tm-icons/tm-icons.vue"
import tmPickersView from "@/tm-vuetify/components/tm-pickersView/tm-pickersView.vue"
export default {
components:{tmButton,tmPoup,tmIcons,tmPickersView},
name:"tm-pickers",
model:{
prop:'value',
event:'input'
},
props: {
// 等同v-model,或者value.sync
value: {
type: String | Number,
default: false
},
// 默认选中的项
// 格式有三种分别是[string,string...]
// [数字序列,数字序列....]
// 和list同等对象结构[{},{},...],此格式需要提供rangKey字段否则报错。
defaultValue:{
type:Array,
default:()=>{return []}
},
// 行高。
itemHeight: {
type: String | Number,
default: 40
},
list: {
type: Array,
default: () => {
return []
}
},
// 如果数据是对象,则需要提供key值。
rangKey: {
type: String,
default: "text"
},
// 如果是联级,则需要提供子集key值。
childrenKey: {
type: String,
default: "children"
},
black:{
type:String|Boolean,
default:null
},
// 是否禁用
disabled:{
type:String|Boolean,
default:false
},
// 背景颜色,主题色名称。
bgColor:{
type:String,
default:'white'
},
// 顶部标题。
title:{
type:String,
default:'请选择选项'
},
// 底部按钮文件
btnText:{
type:String,
default:'确认'
},
// 底部按钮背景主题色名称
btnColor:{
type:String,
default:'primary'
},
// 跟随主题色的改变而改变。
fllowTheme:{
type:Boolean|String,
default:true
}
},
data() {
return {
showpop:false,
dataValue:[],
aniisTrue:true,
};
},
computed: {
black_tmeme: function() {
if (this.black !== null) return this.black;
return this.$tm.vx.state().tmVuetify.black;
},
colorBtn_tmeme:function(){
if(this.$tm.vx.state().tmVuetify.color!==null&&this.$tm.vx.state().tmVuetify.color && this.fllowTheme){
return this.$tm.vx.state().tmVuetify.color;
}
return this.btnColor;
},
},
mounted() {
this.showpop = this.value;
this.$nextTick(function(){
uni.hideKeyboard();
})
},
watch:{
value:function(val){
this.showpop = val;
}
},
methods: {
confirm() {
if(!this.aniisTrue){
console.log('no');
return ;
}
let sdata = this.$refs.tmPicKersTest.getSelectedValue();
let saray = [];
sdata.forEach(item=>{
saray.push(item.data)
})
this.$emit('confirm',sdata)
this.$emit('update:defaultValue',saray)
this.showpop=false;
},
close(){
this.showpop=false;
},
openPoup(){
if(this.disabled==true) return;
this.showpop=true;
},
toogle(e){
console.log(this.showpop);
if(e){
this.$nextTick(function(){
if(this.dataValue != this.defaultValue){
this.dataValue = this.defaultValue;
}
this.$refs.tmPicKersTest.setDefaultValue(this.dataValue)
})
}
this.$emit('input',e);
this.$emit('update:value',e);
}
},
}
</script>
<style lang="scss" scoped>
.tm-pickers-title {
position: relative;
.tm-pickers-close {
position: absolute;
top: 32upx;
right: 32upx;
width: 50upx;
height: 50upx;
}
}
.tm-pickers{
}
</style>
@@ -0,0 +1,285 @@
<template>
<view class="tm-pickersCity d-inline-block fulled">
<view @click.stop.prevent="openPoup"><slot></slot></view>
<tm-poup @change="toogle" ref="pop" v-model="showpop" :height="750" :bg-color="black_tmeme?'grey-darken-5':bgColor">
<view class="tm-pickersCity-title pa-32 pb-16">
<view class="text-size-g text-align-center" style="min-height: 48rpx;">{{title}}</view>
<view class="tm-pickersCity-close rounded flex-center " :class="black_tmeme?'grey-darken-3':'grey-lighten-3'">
<tm-icons @click="close" name="icon-times" size="24" :color="black_tmeme?'white':'grey'"></tm-icons>
</view>
</view>
<tm-pickersView v-if="showpop" @aniStart="aniisTrue=false" @aniEnd="aniisTrue=true" :default-value="dataValue" :bg-color="bgColor" :black="black_tmeme" :disabled="disabled" ref="tmPicKersTest" :list="list" ></tm-pickersView>
<view class="pa-32">
<tm-button @click="confirm" block itemeClass="round-24" :black="black_tmeme" :theme="btnColor" fontSize="32">{{btnText}}</tm-button>
</view>
</tm-poup>
</view>
</template>
<script>
import provinceData from '@/tm-vuetify/tool/util/province.js';
import cityData from '@/tm-vuetify/tool/util/city.js';
import areaData from '@/tm-vuetify/tool/util/area.js';
/**
* 地区选择器(弹层试)
* @description 地区选择器(弹层试)
* @property {String} title = [] 弹层层标题
* @property {String} btn-text = [] 底部按钮确认的文字
* @property {String} btn-color = [primary|green|orange|red|blue|bg-gradient-blue-lighten] 默认:bg-gradient-blue-lighten底部按钮确认的背景颜色仅支持主题色名称
* @property {String} bg-color = [white|blue] 默认:white,白色背景;请填写背景的主题色名称。
* @property {Function} confirm = [] 返回当前选中的数据
* @property {String} level = [province|city|area] ,默认area,显示的级别province:仅显示省,city仅显示省市,area:显示省市区。
* @property {Array} default-value = [] 同tm-pckerView格式,可以是数组内:序列,对象,字符串赋值。
* @property {String|Boolean} black = [true|false] 是否开启暗黑模式。
* @property {String|Boolean} disabled = [true|false] 是否禁用
* @example <tm-pickersCityView ref="city" :defaultValue='["上海市", "市辖区", "徐汇区"]'></tm-pickersCityView>
*
*
*/
import tmButton from "@/tm-vuetify/components/tm-button/tm-button.vue"
import tmPoup from "@/tm-vuetify/components/tm-poup/tm-poup.vue"
import tmIcons from "@/tm-vuetify/components/tm-icons/tm-icons.vue"
import tmPickersView from "@/tm-vuetify/components/tm-pickersView/tm-pickersView.vue"
export default {
components:{tmButton,tmPoup,tmIcons,tmPickersView},
name:"tm-pickersCity",
model:{
prop:'value',
event:'input'
},
props: {
defaultValue:{
type:Array,
default:()=>{return []}
},
// 显示的级别。provincecityarea。
level:{
type:String,
default:'area'
},
black:{
type:String|Boolean,
default:null
},
// 是否禁用
disabled:{
type:String|Boolean,
default:false
},
// 等同v-model,或者value.sync
value: {
type: String | Number,
default: false
},
// 背景颜色,主题色名称。
bgColor:{
type:String,
default:'white'
},
show:{
type:Boolean,
default:true
},
// 顶部标题。
title:{
type:String,
default:'请选择地址'
},
// 底部按钮文件
btnText:{
type:String,
default:'确认'
},
// 底部按钮背景主题色名称
btnColor:{
type:String,
default:'primary'
},
},
data() {
return {
showpop:false,
dataValue:[],
list:[],
aniisTrue:true,
};
},
computed: {
black_tmeme: function() {
if (this.black !== null) return this.black;
return this.$tm.vx.state().tmVuetify.black;
}
},
mounted() {
this.showpop = this.value;
this.$nextTick(function(){
this.chili_level();
uni.hideKeyboard();
})
},
watch:{
value:function(val){
this.showpop = val;
}
},
methods: {
// 获取选中的资料。
getSelectedValue(){
let d = this.$refs.tmPicKersTest.getSelectedValue();
let p = [];
if(this.level=='province'){
p = [d[0].data.text]
}else if(this.level=='city'){
p = [d[0].data.text,d[1].data.text]
}else{
p = [d[0].data.text,d[1].data.text,d[2].data.text]
}
//返回对象数组
if(typeof this.defaultValue[0] === 'object'){
this.$emit("update:defaultValue",[d[0].data,d[1].data,d[2].data])
//返回索引数组
}else if(typeof this.defaultValue[0] === 'number'){
this.$emit("update:defaultValue",[d[0].index,d[1].index,d[2].index])
//返回字符串数组
}else{
this.$emit("update:defaultValue",p)
}
return p;
},
chili_level(){
if(this.level=='province'){
this.chiliFormatCity_pro();
}else if(this.level=='city'){
this.chiliFormatCity_city();
}else{
this.chiliFormatCity_area();
}
},
chiliFormatCity_area() {
let list = [];
provinceData.forEach((item,index)=>{
list.push({
id:item.value,
text:item.label,
children:[]
})
})
cityData.forEach((item,index)=>{
item.forEach((citem,cindex)=>{
list[index].children.push({
id:citem.value,
text:citem.label,
children:[]
})
})
})
list.forEach((item,index)=>{
item.children.forEach((citem,cindex)=>{
areaData[index][cindex].forEach(jitem=>{
list[index].children[cindex].children.push({
id:jitem.value,
text:jitem.label
})
})
})
})
this.list = list;
},
chiliFormatCity_pro() {
let list = [];
provinceData.forEach((item,index)=>{
list.push({
id:item.value,
text:item.label
})
})
this.list = list;
},
chiliFormatCity_city() {
let list = [];
provinceData.forEach((item,index)=>{
list.push({
id:item.value,
text:item.label,
children:[]
})
})
cityData.forEach((item,index)=>{
item.forEach((citem,cindex)=>{
list[index].children.push({
id:citem.value,
text:citem.label
})
})
})
this.list = list;
},
confirm() {
if(!this.aniisTrue){
console.log('no');
return ;
}
this.$emit('confirm',this.getSelectedValue())
this.$refs.pop.close();
},
close(){
this.$refs.pop.close();
},
openPoup(){
if(this.disabled==true) return;
this.showpop=!this.showpop
},
toogle(e){
if(e){
this.$nextTick( function(){
if(this.dataValue != this.defaultValue){
this.dataValue = this.defaultValue;
this.$refs.tmPicKersTest.setDefaultValue(this.dataValue)
}else{
if(!this.dataValue || this.dataValue?.length==0){
this.$refs.tmPicKersTest.setDefaultValue([0,0,0])
}else{
this.$refs.tmPicKersTest.setDefaultValue(this.dataValue)
}
}
this.$emit('input',e);
this.$emit('update:value',e);
})
}
}
},
}
</script>
<style lang="scss" scoped>
.tm-pickersCity-title {
position: relative;
.tm-pickersCity-close {
position: absolute;
top: 32upx;
right: 32upx;
width: 50upx;
height: 50upx;
}
}
</style>
@@ -0,0 +1,160 @@
<template>
<view class="tm-pickersCityView">
<tm-pickersView :bg-color="bgColor" :black="black_tmeme" :disabled="disabled" ref="cityApp" :default-value="defaultValue" :list="list" ></tm-pickersView>
</view>
</template>
<script>
import provinceData from '@/tm-vuetify/tool/util/province.js';
import cityData from '@/tm-vuetify/tool/util/city.js';
import areaData from '@/tm-vuetify/tool/util/area.js';
/**
* 地区选择器(内嵌式)
* @description 地区选择器(内嵌式),使用$refs 方式调用getSelectedValue取得选中的数据。
* @property {String} level = [province|city|area] ,默认area,显示的级别province:仅显示省,city仅显示省市,area:显示省市区。
* @property {Array} default-value = [] 同tm-pckerView格式,可以是数组内:序列,对象,字符串赋值。
* @property {String|Boolean} black = [true|false] 是否开启暗黑模式。
* @property {String|Boolean} disabled = [true|false] 是否禁用
* @property {String} bg-color = [white|blue] 默认:white,白色背景;请填写背景的主题色名称。
* @example <tm-pickersCityView ref="city" :defaultValue='["上海市", "市辖区", "徐汇区"]'></tm-pickersCityView>
*
*/
import tmPickersView from "@/tm-vuetify/components/tm-pickersView/tm-pickersView.vue"
export default {
components:{tmPickersView},
name:'tm-pickersCityView',
props:{
defaultValue:{
type:Array,
default:()=>{return []}
},
// 显示的级别。provincecityarea。
level:{
type:String,
default:'area'
},
black:{
type:String|Boolean,
default:null
},
// 是否禁用
disabled:{
type:String|Boolean,
default:false
},
// 背景颜色,主题色名称。
bgColor:{
type:String,
default:'white'
},
},
data() {
return {
list:[],
};
},
computed: {
black_tmeme: function() {
if (this.black !== null) return this.black;
return this.$tm.vx.state().tmVuetify.black;
}
},
mounted() {
this.$nextTick(function(){
this.chili_level();
})
},
methods: {
// 获取选中的资料。
getSelectedValue(){
let d = this.$refs.cityApp.getSelectedValue();
let p = [];
if(this.level=='province'){
p = [d[0].data.text]
}else if(this.level=='city'){
p = [d[0].data.text,d[1].data.text]
}else{
p = [d[0].data.text,d[1].data.text,d[2].data.text]
}
return p;
},
chili_level(){
if(this.level=='province'){
this.chiliFormatCity_pro();
}else if(this.level=='city'){
this.chiliFormatCity_city();
}else{
this.chiliFormatCity_area();
}
},
chiliFormatCity_area() {
let list = [];
provinceData.forEach((item,index)=>{
list.push({
id:item.value,
text:item.label,
children:[]
})
})
cityData.forEach((item,index)=>{
item.forEach((citem,cindex)=>{
list[index].children.push({
id:citem.value,
text:citem.label,
children:[]
})
})
})
list.forEach((item,index)=>{
item.children.forEach((citem,cindex)=>{
areaData[index][cindex].forEach(jitem=>{
list[index].children[cindex].children.push({
id:jitem.value,
text:jitem.label
})
})
})
})
this.list = list;
},
chiliFormatCity_pro() {
let list = [];
provinceData.forEach((item,index)=>{
list.push({
id:item.value,
text:item.label
})
})
this.list = list;
},
chiliFormatCity_city() {
let list = [];
provinceData.forEach((item,index)=>{
list.push({
id:item.value,
text:item.label,
children:[]
})
})
cityData.forEach((item,index)=>{
item.forEach((citem,cindex)=>{
list[index].children.push({
id:citem.value,
text:citem.label
})
})
})
this.list = list;
}
},
}
</script>
<style lang="scss">
</style>
@@ -0,0 +1,244 @@
<template>
<view class="tm-pickersDate d-inline-block fulled">
<view @click.stop.prevent="openPoup"><slot></slot></view>
<tm-poup @change="toogle" ref="pop" v-model="showpop" :height="750" :bg-color="black_tmeme?'grey-darken-5':bgColor">
<view class="tm-pickersDate-title pa-32 pb-16">
<view class="text-size-n text-align-center" style="min-height: 48rpx;">{{title}}</view>
<view class="tm-pickersDate-close rounded flex-center " :class="black_tmeme?'grey-darken-3':'grey-lighten-3'">
<tm-icons @click="close" name="icon-times" size="24" :color="black_tmeme?'white':'grey'"></tm-icons>
</view>
</view>
<tm-pickersDateView ref="gg"
@aniStart="aniisTrue=false" @aniEnd="aniisTrue=true"
:modeValue="modeValue"
:black="black_tmeme"
:start="start"
:end="end"
:defaultValue="dataValue"
:itemHeight="itemHeight"
:disabled="disabled"
:bgColor="bgColor"
:showDetail="showDetail"
:mode="mode"
:fullNumber="fullNumber"
></tm-pickersDateView>
<view class="pa-32">
<tm-button :black="black_tmeme" @click="confirm" block itemeClass="round-24" :theme="btnColor" fontSize="32">{{btnText}}</tm-button>
</view>
</tm-poup>
</view>
</template>
<script>
/**
* 日期下拉选择器(弹层)
* @description 日期下拉选择器(弹层)
* @property {Array} default-value = [] 默认:当前的时间,初始显示的时间
* @property {String|Number} item-height = [34|42|50|58|62] 项目的高度单位px
* @property {String|Boolean} black = [true|false] 是否开启暗黑模式。
* @property {String|Boolean} disabled = [true|false] 是否禁用
* @property {String} bg-color = [white|blue] 默认:white,白色背景;请填写背景的主题色名称。
* @property {Object} show-detail = [{year:true,month:true,day:true,hour:false,min:false,sec:false}] 默认:{year:true,month:true,day:true,hour:false,min:false,sec:false}
* @property {String} start = [1900-1-1 00:00:00] 默认:1900-1-1 00:00:00,开始的时间
* @property {String} end = [] 默认:当前,结束的时间
* @property {String|Boolean} mode = [true|false] 默认:true,是否显示中文年,月后缀
* @property {String|Boolean} full-number = [true|false] 默认:true,是否把个位数补齐双位数
* @property {String} title = [] 弹层层标题
* @property {String} btn-text = [] 底部按钮确认的文字
* @property {String} btn-color = [primary|green|orange|red|blue|bg-gradient-blue-lighten] 默认:bg-gradient-blue-lighten底部按钮确认的背景颜色仅支持主题色名称
* @property {Function} confirm 返回当前选中的数据
*/
import tmButton from "@/tm-vuetify/components/tm-button/tm-button.vue"
import tmPoup from "@/tm-vuetify/components/tm-poup/tm-poup.vue"
import tmIcons from "@/tm-vuetify/components/tm-icons/tm-icons.vue"
import tmPickersDateView from "@/tm-vuetify/components/tm-pickersDateView/tm-pickersDateView.vue"
export default {
components:{tmButton,tmPoup,tmIcons,tmPickersDateView},
name:"tm-pickersDate",
model:{
prop:'value',
event:'input'
},
mounted() {
this.showpop = this.value;
},
props: {
// 行高。
itemHeight: {
type: String | Number,
default: 40
},
// 等同v-model,或者value.sync
value: {
type: String | Number,
default: false
},
black:{
type:String|Boolean,
default:null
},
// 是否禁用
disabled:{
type:String|Boolean,
default:false
},
// 背景颜色,主题色名称。
bgColor:{
type:String,
default:'white'
},
//要展示的时间。
showDetail:{
type:Object,
default:()=>{
return {
year:true,//年
month:true,//月
day:true,//天
hour:false,//小时
min:false,//分
sec:false//秒
}
}
},
start:{
type:String,
default:'1949-1-1 00:00:00'
},
end:{
type:String,
default:''
},
defaultValue:'',
// 是否显示中文年,月后缀
mode:{
type:String|Boolean,
default:true
},
// 是否把个位数补齐双位数
fullNumber:{
type:String|Boolean,
default:true
},
// 顶部标题。
title:{
type:String,
default:'请选择时间'
},
// 底部按钮文件
btnText:{
type:String,
default:'确认'
},
// 底部按钮背景主题色名称
btnColor:{
type:String,
default:'primary'
},
//要展示的时间。
modeValue:{
type:Object,
default:()=>{
return {
year:'年',//年
month:'月',//月
day:'日',//天
hour:'时',//小时
min:'分',//分
sec:'秒'//秒
}
}
},
},
data() {
return {
showpop:false,
dataValue:'1949',
aniisTrue:true,
};
},
computed: {
black_tmeme: function() {
if (this.black !== null) return this.black;
return this.$tm.vx.state().tmVuetify.black;
}
},
watch:{
value:function(val){
this.showpop = val;
if(val){
this.$nextTick(function(){
setTimeout(function() {
uni.hideKeyboard();
}, 20);
})
}
}
},
mounted() {
this.$nextTick(function(){
this.$refs.gg.resetVal(this.dataValue)
})
},
methods: {
confirm() {
if(!this.aniisTrue){
console.log('no');
return ;
}
let value = this.$refs.gg.getSelectedValue();
this.$emit('confirm',value)
// this.$emit('update:defaultValue',this.$refs.gg.getSelectedValue())
this.$refs.pop.close();
},
close(){
this.$refs.pop.close();
},
openPoup(){
if(this.disabled==true) return;
this.showpop=!this.showpop
},
toogle(e){
let t = this;
if(e){
if(this.dataValue != this.defaultValue){
t.dataValue = t.defaultValue;
}
// #ifdef APP-VUE
this.$nextTick(function(){
setTimeout(function(){
t.$refs.gg.resetVal(t.dataValue)
},500)
})
// #endif
// #ifndef APP-VUE
t.$refs.gg.resetVal(t.dataValue)
// #endif
}
this.$emit('input',e);
this.$emit('update:value',e);
}
},
}
</script>
<style lang="scss" scoped>
.tm-pickersDate-title {
position: relative;
.tm-pickersDate-close {
position: absolute;
top: 32upx;
right: 32upx;
width: 50upx;
height: 50upx;
}
}
</style>
@@ -0,0 +1,621 @@
<!-- 日期组件 -->
<template>
<view class="tm-pickersDateView flex-start px-24" :class="[black_tmeme ? 'grey-darken-5' : bgColor]">
<!-- :value="value_default" @change="change" -->
<picker-view
@pickstart="$emit('aniStart')"
@pickend="$emit('aniEnd')"
@change="change"
v-if="list_cD != null"
:value="value_default"
:mask-style="black_tmeme ? 'opacity:0;' : ''"
indicator-style="height:50px;"
indicator-class="tm-pickersCView-item-h"
class="tm-pickersCView-wk"
>
<picker-view-column v-for="(item, key) in list_cD" :key="key">
<view
class="tm-pickersCView-item fulled-height flex-center "
style="margin: 0 5px;"
:class="[value_default[reIndex(key)] == index_pub ? ' text-weight-b active' : '', black_tmeme ? 'bk' : '', 'text-size-n']"
v-for="(item_data, index_pub) in item"
:key="index_pub"
>
<text>{{ buqi(item_data) }}</text>
<text v-if="mode">{{ modhz[key] }}</text>
</view>
</picker-view-column>
</picker-view>
</view>
</template>
<script>
/**
* 日期下拉选择器(嵌入式)
* @description 多级关联,单级关联选择
* @property {Array} default-value = [] 默认:当前的时间,初始显示的时间
* @property {String|Number} item-height = [34|42|50|58|62] 项目的高度单位px
* @property {String|Boolean} black = [true|false] 是否开启暗黑模式。
* @property {String|Boolean} disabled = [true|false] 是否禁用
* @property {String} bg-color = [white|blue] 默认:white,白色背景;请填写背景的主题色名称。
* @property {Object} show-detail = [{year:true,month:true,day:true,hour:false,min:false,sec:false}] 默认:{year:true,month:true,day:true,hour:false,min:false,sec:false}
* @property {String} start = [1900-1-1 00:00:00] 默认:1900-1-1 00:00:00,开始的时间
* @property {String} end = [] 默认:当前,结束的时间
* @property {String|Boolean} mode = [true|false] 默认:true,是否显示中文年,月后缀
* @property {String|Boolean} full-number = [true|false] 默认:true,是否把个位数补齐双位数
*/
export default {
name: 'tm-pickersDateView',
props: {
// 行高。
itemHeight: {
type: String | Number,
default: 40
},
black: {
type: String | Boolean,
default: null
},
// 是否禁用
disabled: {
type: String | Boolean,
default: false
},
// 背景颜色,主题色名称。
bgColor: {
type: String,
default: 'white'
},
//要展示的时间。
showDetail: {
type: Object,
default: () => {
return {
year: true, //年
month: true, //月
day: true, //天
hour: false, //小时
min: false, //分
sec: false //秒
};
}
},
start: {
type: String,
default: '1949-1-1 00:00:00'
},
end: {
type: String,
default: ''
},
defaultValue: '',
// 是否显示中文年,月后缀
mode: {
type: String | Boolean,
default: true
},
//要展示的时间。
modeValue: {
type: Object,
default: () => {
return {
year: '年', //年
month: '月', //月
day: '日', //天
hour: '时', //小时
min: '分', //分
sec: '秒' //秒
};
}
},
// 是否把个位数补齐双位数
fullNumber: {
type: String | Boolean,
default: true
}
},
data() {
return {
scrollEvent: 0,
childrenIndex: 0,
scrollChildrenIndex: 0,
listIndex: [],
listData: [[], [], [], [], [], []],
dataCauser: {
year: false, //年
month: false, //月
day: false, //天
hour: false, //小时
min: false, //分
sec: false //秒
},
hoz: {
year: '年', //年
month: '月', //月
day: '日', //天
hour: '时', //小时
min: '分', //分
sec: '秒' //秒
},
startTime: null,
endTime: null,
value_default: [],
pre_value: [],
syheng_key: [],
list_cD: null
};
},
created() {
this.dataCauser = { ...this.dataCauser, ...this.showDetail };
let ls = Object.keys(this.dataCauser);
for (let i = 0; i < this.listData.length; i++) {
let p = {
itemIndex: 0,
childrenIndex: 0,
wz: 0
};
p[ls[i]] = this.dataCauser[ls[i]];
this.listIndex.push(p);
}
},
async mounted() {
this.chulisdata();
await uni.$tm.sleep(60);
this.chulishijian();
this.setDefaultValue();
},
watch: {
defaultValue: async function() {
if (this.list_cD == null) return;
await this.setDefaultValue();
},
start: async function() {
if (this.list_cD == null) return;
this.chulisdata();
this.chulishijian();
await this.setDefaultValue();
},
end: async function() {
if (this.list_cD == null) return;
await this.setDefaultValue();
}
},
computed: {
black_tmeme: function() {
if (this.black !== null) return this.black;
return this.$tm.vx.state().tmVuetify.black;
},
modhz: function() {
return { ...this.hoz, ...this.modeValue };
}
},
methods: {
reIndex(key) {
let id = 0;
for (let i = 0; i < this.syheng_key.length; i++) {
if (this.syheng_key[i] == key) {
id = i;
break;
}
}
return id;
},
chulishijian() {
let kl = Object.keys(this.dataCauser);
let d = {};
let pk = [];
for (let i = 0; i < kl.length; i++) {
if (this.dataCauser[kl[i]] == true) {
let sj = null;
let key = '';
if (kl[i] == 'year') {
sj = this.listData[0];
key = 'year';
} else if (kl[i] == 'month') {
sj = this.listData[1];
key = 'month';
} else if (kl[i] == 'day') {
sj = this.listData[2];
key = 'day';
} else if (kl[i] == 'hour') {
sj = this.listData[3];
key = 'hour';
} else if (kl[i] == 'min') {
sj = this.listData[4];
key = 'min';
} else if (kl[i] == 'sec') {
sj = this.listData[5];
key = 'sec';
}
d[kl[i]] = {...sj};
pk.push(key);
}
}
this.list_cD = {...d};
this.syheng_key = [...pk];
},
buqi(val) {
return val > 9 ? '' + val : '0' + val;
},
SeletecdeIndexdefault() {
let kl = Object.keys(this.dataCauser);
let d = [];
for (let i = 0; i < this.listIndex.length; i++) {
if (this.listIndex[i][kl[i]] == true) {
d.push(this.listIndex[i].itemIndex);
}
}
this.value_default = d;
},
// 获取当前选中的数据。
getSelectedValue() {
let t = this;
let ap = [];
this.listIndex.forEach((item, index) => {
ap.push(t.listData[index][item.itemIndex]);
});
let jg = {
year: ap[0], //年
month: ap[1], //月
day: ap[2], //天
hour: ap[3], //小时
min: ap[4], //分
sec: ap[5] //秒
};
let ar = Object.keys(this.dataCauser);
ar.forEach(item => {
if (t.dataCauser[item] === false) {
delete jg[item];
}
});
return jg;
},
chulisdata() {
if (typeof this.showDetail === 'object') {
this.dataCauser = { ...this.dataCauser, ...this.showDetail };
}
this.startTime = new Date(this.start.replace(/-/g, '/'));
if (isNaN(this.startTime.getFullYear())) {
this.startTime = new Date('1949/1/1 00:00:00');
}
this.endTime = new Date(this.end.replace(/-/g, '/'));
if (isNaN(this.endTime.getFullYear())) {
this.endTime = new Date();
}
let s_y = this.startTime.getFullYear();
let s_m = this.startTime.getMonth() + 1;
let s_d = this.startTime.getDate();
let s_h = this.startTime.getHours();
let s_mm = this.startTime.getMinutes();
let s_s = this.startTime.getSeconds();
let e_y = this.endTime.getFullYear();
let e_m = this.endTime.getMonth() + 1;
let e_d = this.endTime.getDate();
let e_h = this.endTime.getHours();
let e_mm = this.endTime.getMinutes();
let e_s = this.endTime.getSeconds();
let n_y = this.listData[0][this.listIndex[0].itemIndex];
n_y = n_y == undefined ? 1949 : n_y;
let n_m = this.listData[1][this.listIndex[1].itemIndex];
n_m = n_m == undefined ? 1 : n_m;
let n_d = this.listData[2][this.listIndex[2].itemIndex];
n_d = n_d == undefined ? 1 : n_d;
let n_h = this.listData[3][this.listIndex[3].itemIndex];
n_h = n_h == undefined ? 0 : n_h;
let n_mm = this.listData[4][this.listIndex[4].itemIndex];
n_mm = n_mm == undefined ? 0 : n_mm;
let n_s = this.listData[5][this.listIndex[5].itemIndex];
n_s = n_s == undefined ? 0 : n_s;
let nowDate = new Date(n_y + '/' + n_m + '/' + n_d + ' ' + n_h + ':' + n_mm + ':' + n_s);
function monthDay(year, month) {
let date = new Date(year, month, 1, 0, 0, 0);
let yesterDay = new Date(date - 1000);
return yesterDay.getDate();
}
//年,开始,结束。
let tsb = [[s_y, e_y]];
for (let i = 1; i < 6; i++) {
let st = 0;
let et = 0;
if (i == 1) {
st = 1;
et = 12;
if (n_y === s_y) {
st = s_m;
et = 12;
}
if (n_y === e_y) {
st = 1;
et = e_m;
}
if(s_y===e_y&&n_y===s_y){
st = s_m;
et = e_m;
}
}
if (i == 2) {
let months = [31, monthDay(n_y, 2), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
st = 1;
et = months[n_m - 1];
if (n_y === s_y && n_m === s_m) {
st = s_d;
}
if (n_y === e_y && n_m === e_m) {
et = e_d;
}
}
if (i == 3) {
st = 0;
et = 23;
if (n_y === s_y && n_m === s_m && n_d === s_d) {
st = s_h;
}
if (n_y === e_y && n_m === e_m && n_d === e_d) {
et = e_h;
}
}
if (i == 4) {
st = 0;
et = 59;
if (n_y === s_y && n_m === s_m && n_d === s_d && n_h === s_h) {
st = s_mm;
}
if (n_y === e_y && n_m === e_m && n_d === e_d && n_h === e_h) {
et = e_mm;
}
}
if (i == 5) {
st = 0;
et = 59;
if (n_y === s_y && n_m === s_m && n_d === s_d && n_h === s_h && n_mm === s_mm) {
st = s_s;
}
if (n_y === e_y && n_m === e_m && n_d === e_d && n_h === e_h && n_mm === e_mm) {
et = e_s;
}
}
tsb.push([st, et]);
}
for (let ik = 0; ik < tsb.length; ik++) {
let idate = tsb[ik];
let py_data = [];
for (let i = idate[0]; i <= idate[1]; i++) {
py_data.push(i);
}
this.listData.splice(ik, 1, py_data);
}
return this.listData;
},
setDefaultValue(date) {
this.chulisdata();
this.chulishijian();
uni.$tm.sleep(50)
.then(()=>this.inits(date))
.then(()=>{
this.chulisdata();
this.chulishijian();
return uni.$tm.sleep(50);
}).then(()=>{
this.SeletecdeIndexdefault();
this.chulisdata();
})
},
async inits(date) {
let t = this;
let mobj;
if (date) {
mobj = new Date(date.replace(/-/g, '/'));
} else {
try {
mobj = new Date(this.defaultValue.replace(/-/g, '/'));
} catch (e) {
mobj = new Date();
}
if (!this.defaultValue || isNaN(mobj.getFullYear())) {
mobj = new Date();
}
}
let s_y = this.startTime.getFullYear();
let s_m = this.startTime.getMonth() + 1;
let s_d = this.startTime.getDate();
let s_h = this.startTime.getHours();
let s_mm = this.startTime.getMinutes();
let s_s = this.startTime.getSeconds();
let e_y = this.endTime.getFullYear();
let e_m = this.endTime.getMonth() + 1;
let e_d = this.endTime.getDate();
let e_h = this.endTime.getHours();
let e_mm = this.endTime.getMinutes();
let e_s = this.endTime.getSeconds();
let n_y = mobj.getFullYear();
let n_m = mobj.getMonth() + 1;
let n_d = mobj.getDate();
let n_h = mobj.getHours();
let n_mm = mobj.getMinutes();
let n_s = mobj.getSeconds();
if (mobj.getTime() >= this.endTime.getTime()) {
n_y = e_y;
n_m = e_m;
n_d = e_d;
n_h = e_h;
n_mm = e_mm;
n_s = e_s;
}
if (mobj.getTime() <= this.startTime.getTime()) {
n_y = s_y;
n_m = s_m;
n_d = s_d;
n_h = s_h;
n_mm = s_mm;
n_s = s_s;
}
let tsb = [n_y, n_m, n_d, n_h, n_mm, n_s];
for (let ik = 0; ik < tsb.length; ik++) {
let itemIndex_y = this.listData[ik].findIndex(item => item == tsb[ik]);
if (itemIndex_y > -1) {
this.$set(this.listIndex[ik], 'itemIndex', itemIndex_y);
} else {
this.$set(this.listIndex[ik], 'itemIndex', 0);
}
}
},
change(e, index) {
console.log(this.list_cD);
let pl = e.detail.value;
this.pre_value = [...this.value_default];
if (this.disabled) {
this.value_default = this.pre_value;
return;
}
for (let i = 0; i < this.syheng_key.length; i++) {
for (let j = 0; j < this.listIndex.length; j++) {
if (this.listIndex[j][this.syheng_key[i]] == true) {
this.$set(this.listIndex[j], 'itemIndex', pl[i]);
}
}
}
this.chulisdata();
this.chulishijian();
this.value_default = pl;
},
jswid() {
let wd = this.gridNum - 1 - 2;
if (wd <= 0) wd = 1;
return 100 / wd;
},
scrllEnd(e) {
function monthDay(year, month) {
var date = new Date(year, month, 1, 0, 0, 0);
var yesterDay = new Date(date - 1000);
return yesterDay.getDate();
}
if (!this.scrollEvent) return;
let dNum = this.gridNum;
let d;
let t = this;
let idb = 88;
let idc = 884;
let srcllTop = this.scrollEvent.detail.scrollTop;
if (srcllTop <= 0) {
srcllTop = 0;
} else if (srcllTop >= this.scrollEvent.detail.srcollHeigh) {
srcllTop = this.scrollEvent.detail.srcollHeigh;
}
d = Math.ceil((srcllTop - this.itemHeight / 2) / this.itemHeight);
if (d >= t.listData[t.childrenIndex].length - 1) {
d = t.listData[t.childrenIndex].length - 1;
}
t.$set(t.listIndex[t.childrenIndex], 'wz', srcllTop);
let old_index = this.listIndex[this.childrenIndex].itemIndex || 0;
if (t.disabled) {
clearTimeout(idb);
idb = setTimeout(function() {
t.$nextTick(function() {
t.$set(t.listIndex[t.childrenIndex], 'wz', old_index * t.itemHeight);
});
}, 5);
return;
}
this.$set(this.listIndex[t.childrenIndex], 'itemIndex', d);
t.chulisdata();
clearTimeout(idb);
idb = setTimeout(function() {
t.$nextTick(function() {
t.$set(t.listIndex[t.childrenIndex], 'wz', d * t.itemHeight);
for (let lsindex = t.childrenIndex + 1; lsindex < 6; lsindex++) {
if (t.listData[lsindex][t.listIndex[lsindex].itemIndex] == undefined) {
let pda = t.listData[lsindex].length - 1;
if (d >= pda) {
this.$set(this.listIndex[lsindex], 'itemIndex', pda);
t.$set(t.listIndex[lsindex], 'wz', pda * t.itemHeight);
} else {
this.$set(this.listIndex[lsindex], 'itemIndex', 0);
t.$set(t.listIndex[lsindex], 'wz', 0);
}
} else {
let srcllTop_s = t.listIndex[lsindex].wz;
const tsdd = t.listIndex[lsindex].itemIndex;
t.$set(t.listIndex[lsindex], 'wz', tsdd * t.itemHeight - 1);
this.$nextTick(function() {
t.$set(t.listIndex[lsindex], 'wz', tsdd * t.itemHeight);
});
}
}
});
}, 10);
}
}
};
</script>
<style>
.tm-pickersDateView .tm-pickersCView-item-h {
height: 50px;
background-color: rgba(0, 0, 0, 0.03);
width: calc(100% - 10px);
margin-left: 5px;
border-radius: 20rpx;
border: none;
}
.tm-pickersDateView .tm-pickersCView-item-h::after,
.tm-pickersDateView .tm-pickersCView-item-h::before {
border: none;
}
.tm-pickersDateView .tm-pickersCView-wk {
position: relative;
width: 750rpx;
height: 500rpx;
}
.tm-pickersDateView .tm-pickersCView-wk .tm-pickersCView-item.bk {
opacity: 0.4;
}
.tm-pickersDateView .tm-pickersCView-wk .tm-pickersCView-item.active {
opacity: 1;
border-radius: 20rpx;
border: none;
background-color: rgba(0, 0, 0, 0.06);
}
.tm-pickersDateView .tm-pickersCView-wk .tm-pickersCView-item.active.bk {
background-color: rgba(255, 255, 255, 0.06);
}
</style>
@@ -0,0 +1,836 @@
<!-- 日期组件 -->
<template>
<view class="tm-pickersDateView flex-start px-24" :class="[black_tmeme?'grey-darken-5':bgColor]">
<block v-for="(item_data,index_pub) in listData" :key="index_pub">
<view v-if="(index_pub==0&&dataCauser.year)
||(index_pub==1&&dataCauser.month)
||(index_pub==2&&dataCauser.day)
||(index_pub==3&&dataCauser.hour)
||(index_pub==4&&dataCauser.min)
||(index_pub==5&&dataCauser.sec)
" class="tm-pickersDateView-wk " :style="{
height:itemHeight*5+'px',
width:jswid() + '%',
marginLeft:index_pub==0?0:1 + '%',
}">
<scroll-view
@touchstart="setChildreIndex(index_pub)"
:show-scrollbar='false'
v-if="dataType!==null&&item_data"
:scroll-top="listIndex[index_pub]?listIndex[index_pub]['wz']:''"
scroll-y
@touchend="scrllEnd"
@scroll="scroll($event,index_pub)"
scroll-with-animation class="tm-pickersDateView-showbg"
:style="{
height:itemHeight*5+'px'
}">
<!-- #ifdef H5 -->
<view :id='"_bar_"+(index4)+"_bar_"' v-for="(item4,index4) in 2" :key="index4+'_a'" class="tm-pickersDateView-item "
:style="{height:itemHeight+'px'}">
<text class="opacity-1"></text>
</view>
<!-- #endif -->
<!-- #ifndef H5 -->
<view :id='"_bar_"+(index4)+"_bar_"' v-for="(item4,index4) in 2" :key="index4" class="tm-pickersDateView-item "
:style="{height:itemHeight+'px'}">
<text class="opacity-1"></text>
</view>
<!-- #endif -->
<view :id='"_bar_"+(index+2)+"_bar_"' v-for="(item,index) in item_data" :key="index"
class="tm-pickersDateView-item flex-center" :style="{
height:itemHeight+'px'
}">
<view class="text-size-g tm-pickersDateView-item-text" :class="[
listIndex[index_pub].itemIndex==index? (black_tmeme?'text-white':'text-black'):(black_tmeme?'':'opacity-4'),
listIndex[index_pub].itemIndex+1==index||listIndex[index_pub].itemIndex-1==index?'textLevel_1':'',
listIndex[index_pub].itemIndex+2==index||listIndex[index_pub].itemIndex-2==index?'textLevel_2':'',
]">
<text >{{fullNumber?buqi(item):item}}{{index_pub==0&&mode?'年':''}}{{index_pub==1&&mode?'月':''}}{{index_pub==2&&mode?'日':''}}{{index_pub==3&&mode?'时':''}}{{index_pub==4&&mode?'分':''}}{{index_pub==5&&mode?'秒':''}}</text>
</view>
</view>
<!-- #ifdef H5 -->
<view v-for="(item4,index4) in 2" :key="index4+'_bb'" class="tm-pickersDateView-item"
:style="{height:itemHeight+'px'}">
<text></text>
</view>
<!-- #endif -->
<!-- #ifndef H5 -->
<view v-for="(item4,index4) in 2" :key="index4" class="tm-pickersDateView-item"
:style="{height:itemHeight+'px'}">
<text></text>
</view>
<!-- #endif -->
</scroll-view>
<view class="tm-pickersDateView-fg overflow round-5 shadow-5 flex-center"
:class="[
black_tmeme?'white opacity-1':'grey-darken-1 opacity-1'
]" :style="{
height:itemHeight+'px',
top:itemHeight*2+'px'
}">
</view>
</view>
</block>
</view>
</template>
<script>
/**
* 日期下拉选择器(嵌入式)
* @description 多级关联,单级关联选择
* @property {Array} default-value = [] 默认:当前的时间,初始显示的时间
* @property {String|Number} item-height = [34|42|50|58|62] 项目的高度单位px
* @property {String|Boolean} black = [true|false] 是否开启暗黑模式。
* @property {String|Boolean} disabled = [true|false] 是否禁用
* @property {String} bg-color = [white|blue] 默认:white,白色背景;请填写背景的主题色名称。
* @property {Object} show-detail = [{year:true,month:true,day:true,hour:false,min:false,sec:false}] 默认:{year:true,month:true,day:true,hour:false,min:false,sec:false}
* @property {String} start = [1900-1-1 00:00:00] 默认:1900-1-1 00:00:00,开始的时间
* @property {String} end = [] 默认:当前,结束的时间
* @property {String|Boolean} mode = [true|false] 默认:true,是否显示中文年,月后缀
* @property {String|Boolean} full-number = [true|false] 默认:true,是否把个位数补齐双位数
*/
export default {
name: "tm-pickersDateView",
props: {
// 行高。
itemHeight: {
type: String | Number,
default: 40
},
black:{
type:String|Boolean,
default:null
},
// 是否禁用
disabled:{
type:String|Boolean,
default:false
},
// 背景颜色,主题色名称。
bgColor:{
type:String,
default:'white'
},
//要展示的时间。
showDetail:{
type:Object,
default:()=>{
return {
year:true,//年
month:true,//月
day:true,//天
hour:false,//小时
min:false,//分
sec:false//秒
}
}
},
start:{
type:String,
default:'1900-1-1 00:00:00'
},
end:{
type:String,
default:''
},
defaultValue:'',
// 是否显示中文年,月后缀
mode:{
type:String|Boolean,
default:true
},
// 是否把个位数补齐双位数
fullNumber:{
type:String|Boolean,
default:true
}
},
data() {
return {
scrollEvent: 0,
childrenIndex: 0,
scrollChildrenIndex:0,
listIndex: [],
listData: [[],[],[],[],[],[]],
dataCauser:{
year:true,//年
month:true,//月
day:true,//天
hour:true,//小时
min:true,//分
sec:true//秒
},
startTime:null,
endTime:null,
};
},
mounted() {
for(let i =0 ; i <this.listData.length;i++){
this.listIndex.push({
itemIndex: 0,
childrenIndex: 0,
wz: 0
})
}
this.chulisdata()
this.$nextTick(function(){
this.setDefaultValue();
})
},
watch:{
defaultValue:async function(){
await this.setDefaultValue();
},
start:async function(){
await this.setDefaultValue();
},
end:async function(){
await this.setDefaultValue();
},
},
computed: {
dataType: function() {
return 'string';
},
gridNum: function() {
let t = this;
let d = 0;
for(let key in this.showDetail){
if(this.showDetail[key]==true){
d++
}
}
return d;
},
black_tmeme: function() {
if (this.black !== null) return this.black;
return this.$tm.vx.state().tmVuetify.black;
}
},
methods: {
buqi(val){
return (val > 9) ? ("" + val) : ("0" + val);
},
// 获取当前选中的数据。
getSelectedValue(){
let t = this;
let ap = [];
this.listIndex.forEach((item,index)=>{
ap.push(t.listData[index][item.itemIndex])
})
let jg = {
year:ap[0],//年
month:ap[1],//月
day:ap[2],//天
hour:ap[3],//小时
min:ap[4],//分
sec:ap[5]//秒
}
let ar = Object.keys(this.dataCauser);
ar.forEach(item=>{
if(t.dataCauser[item]===false){
delete jg[item]
}
})
return jg;
},
chulisdata() {
if(typeof this.showDetail === 'object'){
this.dataCauser = {...this.dataCauser,...this.showDetail};
}
this.startTime = new Date(this.start.replace(/-/g,'/'))
if(isNaN(this.startTime.getFullYear())){
this.startTime = new Date('1900/1/1 00:00:00')
}
this.endTime = new Date(this.end.replace(/-/g,'/'))
if(isNaN(this.endTime.getFullYear()) ){
this.endTime = new Date()
}
// this.dataCauser.year===
if(true){
let pyear = [];
for(let i=this.startTime.getFullYear();i<=this.endTime.getFullYear(); i++){
pyear.push(i)
}
this.listData.splice(0,1,pyear)
}
// this.dataCauser.month===
if(true){
let pmonth = [];
let x_year = this.startTime.getFullYear();
let xz_year= this.listData[0][this.listIndex[0].itemIndex];
if(xz_year === x_year){
for(let i=this.startTime.getMonth()+1;i<=12; i++){
pmonth.push(i)
}
}else{
// 不能大于endtime.
if(xz_year >= this.endTime.getFullYear()){
for(let i=1;i<=this.endTime.getMonth()+1; i++){
pmonth.push(i)
}
}else{
for(let i=1;i<=12; i++){
pmonth.push(i)
}
}
}
this.listData.splice(1,1,pmonth)
}
// this.dataCauser.day===
if(true){
function monthDay(year, month) {
var date = new Date(year, month, 1, 0, 0, 0)
var yesterDay = new Date(date - 1000)
return yesterDay.getDate()
}
let pday = [];
let year = this.startTime.getFullYear();
let xzyear = this.listData[0][this.listIndex[0].itemIndex];
if(xzyear){
year = xzyear;
}
let month = this.startTime.getMonth();
let xzmonth = this.listData[1][this.listIndex[1].itemIndex]-1;
if(xzmonth){
month = xzmonth;
}
let months = [31,monthDay(year,month+1),31,30,31,30,31,31,30,31,30,31];
if(year === this.startTime.getFullYear() && month === this.startTime.getMonth()){
if(year == this.endTime.getFullYear()&&
month == this.endTime.getMonth()+1
){
for(let i=this.startTime.getDate();i<=this.endTime.getDate(); i++){
pday.push(i)
}
}else{
for(let i=this.startTime.getDate();i<=months[month]; i++){
pday.push(i)
}
}
}else{
if(year >= this.endTime.getFullYear()&& month >= this.endTime.getMonth()){
for(let i=1;i<=this.endTime.getDate(); i++){
pday.push(i)
}
}else{
for(let i=1;i<=months[month]; i++){
pday.push(i)
}
}
}
this.listData.splice(2,1,pday)
}
// this.dataCauser.hour===
if(true){
let phour = [];
let s_year = this.startTime.getFullYear();
let o_year = this.listData[0][this.listIndex[0].itemIndex];
let s_month = this.startTime.getMonth()+1;
let o_month = this.listData[1][this.listIndex[1].itemIndex];
let s_day = this.startTime.getDate();
let o_day = this.listData[2][this.listIndex[2].itemIndex];
// let s_hour = this.startTime.getMinutes();
// let o_hour = this.listData[3][this.listIndex[3].itemIndex];
if(s_year===o_year&&s_month===o_month&&s_day===o_day){
if(s_year == this.endTime.getFullYear()&&
s_month == this.endTime.getMonth()+1 &&
s_day == this.endTime.getDate()
){
for(let i=this.startTime.getHours();i<=this.endTime.getHours(); i++){
phour.push(i)
}
}else{
for(let i=this.startTime.getHours();i<24; i++){
phour.push(i)
}
}
}else{
if(o_year >= this.endTime.getFullYear() && o_month >= this.endTime.getMonth()&& o_day >= this.endTime.getDate()){
for(let i=0;i<=this.endTime.getHours(); i++){
phour.push(i)
}
}else{
for(let i=0;i<24; i++){
phour.push(i)
}
}
}
this.listData.splice(3,1,phour)
}
// this.dataCauser.min===true
if(true){
let pmin = [];
let s_year = this.startTime.getFullYear();
let o_year = this.listData[0][this.listIndex[0].itemIndex];
let s_month = this.startTime.getMonth()+1;
let o_month = this.listData[1][this.listIndex[1].itemIndex];
let s_day = this.startTime.getDate();
let o_day = this.listData[2][this.listIndex[2].itemIndex];
let s_hour = this.startTime.getHours();
let o_hour = this.listData[3][this.listIndex[3].itemIndex];
if(s_year===o_year&&s_month===o_month&&s_day===o_day&&s_hour===o_hour){
if(s_year == this.endTime.getFullYear()&&
s_month == this.endTime.getMonth()+1 &&
s_day == this.endTime.getDate()&&
s_hour == this.endTime.getHours()
){
for(let i=this.startTime.getMinutes();i<=this.endTime.getMinutes(); i++){
pmin.push(i)
}
}else{
for(let i=this.startTime.getMinutes();i<60; i++){
pmin.push(i)
}
}
}else{
if(o_year >= this.endTime.getFullYear() && o_month >= this.endTime.getMonth()&& o_day >= this.endTime.getDate() && o_hour >= this.endTime.getHours()){
for(let i=1;i<=this.endTime.getMinutes(); i++){
pmin.push(i)
}
}else{
for(let i=0;i<60; i++){
pmin.push(i)
}
}
}
this.listData.splice(4,1,pmin)
}
// this.dataCauser.sec===
if(true){
let psec = [];
let s_year = this.startTime.getFullYear();
let o_year = this.listData[0][this.listIndex[0].itemIndex];
let s_month = this.startTime.getMonth()+1;
let o_month = this.listData[1][this.listIndex[1].itemIndex];
let s_day = this.startTime.getDate();
let o_day = this.listData[2][this.listIndex[2].itemIndex];
let s_hour = this.startTime.getHours();
let o_hour = this.listData[3][this.listIndex[3].itemIndex];
let s_min = this.startTime.getMinutes();
let o_min = this.listData[4][this.listIndex[4].itemIndex];
if(s_year===o_year&&s_month===o_month&&s_day===o_day&&s_hour===o_hour&&s_min===o_min){
if(s_year == this.endTime.getFullYear()&&
s_month == this.endTime.getMonth()+1 &&
s_day == this.endTime.getDate()&&
s_hour == this.endTime.getHours()&&
s_min == this.endTime.getMinutes()
){
for(let i=this.startTime.getSeconds();i<=this.endTime.getSeconds(); i++){
psec.push(i)
}
}else{
for(let i=this.startTime.getSeconds();i<60; i++){
psec.push(i)
}
}
}else{
if(o_year >= this.endTime.getFullYear() && o_month >= this.endTime.getMonth()&& o_day >= this.endTime.getDate() && o_hour >= this.endTime.getHours()&& o_min >= this.endTime.getMinutes()){
for(let i=1;i<=this.endTime.getSeconds(); i++){
psec.push(i)
}
}else{
for(let i=0;i<60; i++){
psec.push(i)
}
}
}
this.listData.splice(5,1,psec)
}
return this.listData;
},
async setDefaultValue(date){
let t = this;
let mobj;
if(date){
mobj = new Date(date.replace(/-/g,'/'));
}else{
try{
mobj = new Date(this.defaultValue.replace(/-/g,'/'));
}catch(e){
mobj = new Date();
}
if(!this.defaultValue || isNaN(mobj.getFullYear())){
mobj = new Date();
}
}
await uni.$tm.sleep(20)
// this.dataCauser.year===
if(true){
let pyear = mobj.getFullYear();
if(pyear <= this.startTime.getFullYear()){
pyear = this.startTime.getFullYear()
}else if(pyear >= this.endTime.getFullYear()){
pyear = this.endTime.getFullYear()
}
let itemIndex = this.listData[0].indexOf(pyear)
if(itemIndex>-1){
this.$set(this.listIndex[0], 'itemIndex', itemIndex);
this.$set(t.listIndex[0], 'wz', itemIndex * t.itemHeight);
this.chulisdata();
}
}
await uni.$tm.sleep(20)
// this.dataCauser.month===
if(true){
let pmonth = mobj.getMonth()+1;
if(mobj.getFullYear() <= this.startTime.getFullYear()&&pmonth <= this.startTime.getMonth()+1){
pmonth = this.startTime.getMonth()+1
}else if(mobj.getFullYear() >= this.endTime.getFullYear()&&pmonth >= this.endTime.getMonth()+1){
pmonth = this.endTime.getMonth()+1
}
let itemIndex = this.listData[1].indexOf(pmonth)
if(itemIndex>-1){
this.chulisdata();
this.$set(this.listIndex[1], 'itemIndex', itemIndex);
this.$set(t.listIndex[1], 'wz', itemIndex * t.itemHeight-0.1);
setTimeout(function() {
t.$set(t.listIndex[1], 'wz', itemIndex * t.itemHeight);
}, 10);
}
}
await uni.$tm.sleep(20)
// this.dataCauser.day===
if(true){
let pday = mobj.getDate();
let pmonth = mobj.getMonth()+1;
if(mobj.getFullYear() <= this.startTime.getFullYear()&&
pmonth <= this.startTime.getMonth()+1&&
pday <= this.startTime.getDate()
){
pday = this.startTime.getDate()
}else if(mobj.getFullYear() >= this.endTime.getFullYear()&&
pmonth >= this.endTime.getMonth()+1&&
pday >= this.endTime.getDate()
){
pday = this.endTime.getDate()
}
let itemIndex = this.listData[2].indexOf(pday)
if(itemIndex>-1){
this.chulisdata();
this.$set(this.listIndex[2], 'itemIndex', itemIndex);
this.$set(t.listIndex[2], 'wz', itemIndex * t.itemHeight);
}
}
await uni.$tm.sleep(20)
// this.dataCauser.hour===
if(true){
let phour = mobj.getHours();
let pday = mobj.getDate();
let pmonth = mobj.getMonth()+1;
if(mobj.getFullYear() <= this.startTime.getFullYear()&&
pmonth <= this.startTime.getMonth()+1&&
pday <= this.startTime.getDate()&&
phour <= this.startTime.getHours()
){
phour = this.startTime.getHours()
}else if(mobj.getFullYear() >= this.endTime.getFullYear()&&
pmonth >= this.endTime.getMonth()+1&&
pday >= this.endTime.getDate()&&
phour >= this.endTime.getHours()
){
phour = this.endTime.getHours()
}
let itemIndex = this.listData[3].indexOf(phour)
if(itemIndex>-1){
this.chulisdata();
this.$set(this.listIndex[3], 'itemIndex', itemIndex);
this.$set(t.listIndex[3], 'wz', itemIndex * t.itemHeight);
}
}
await uni.$tm.sleep(20)
// this.dataCauser.min===
if(true){
let pmin = mobj.getMinutes();
let phour = mobj.getHours();
let pday = mobj.getDate();
let pmonth = mobj.getMonth()+1;
if(mobj.getFullYear() <= this.startTime.getFullYear()&&
pmonth <= this.startTime.getMonth()+1&&
pday <= this.startTime.getDate()&&
phour <= this.startTime.getHours()&&
pmin <= this.startTime.getMinutes()
){
phour = this.startTime.getMinutes()
}else if(mobj.getFullYear() >= this.endTime.getFullYear()&&
pmonth >= this.endTime.getMonth()+1&&
pday >= this.endTime.getDate()&&
phour >= this.endTime.getHours()&&
pmin >= this.endTime.getMinutes()
){
phour = this.endTime.getMinutes()
}
let itemIndex = this.listData[4].indexOf(pmin)
if(itemIndex>-1){
this.$set(this.listIndex[4], 'itemIndex', itemIndex);
this.$set(t.listIndex[4], 'wz', itemIndex * t.itemHeight);
}
}
await uni.$tm.sleep(20)
// this.dataCauser.sec===
if(true){
let psec = mobj.getSeconds();
let pmin = mobj.getMinutes();
let phour = mobj.getHours();
let pday = mobj.getDate();
let pmonth = mobj.getMonth()+1;
if(mobj.getFullYear() <= this.startTime.getFullYear()&&
pmonth <= this.startTime.getMonth()+1&&
pday <= this.startTime.getDate()&&
phour <= this.startTime.getHours()&&
pmin <= this.startTime.getMinutes()&&
psec <= this.startTime.getSeconds()
){
psec = this.startTime.getMinutes()
}else if(mobj.getFullYear() >= this.endTime.getFullYear()&&
pmonth >= this.endTime.getMonth()+1&&
pday >= this.endTime.getDate()&&
phour >= this.endTime.getHours()&&
pmin >= this.endTime.getMinutes()&&
psec >= this.endTime.getSeconds()
){
psec = this.endTime.getSeconds()
}
let itemIndex = this.listData[5].indexOf(psec)
if(itemIndex>-1){
this.$set(this.listIndex[5], 'itemIndex', itemIndex);
this.$set(t.listIndex[5], 'wz', itemIndex * t.itemHeight);
}
}
},
setChildreIndex(index){
this.childrenIndex = index;
},
scroll(e, index) {
console.log(e);
this.scrollEvent = e;
this.scrollChildrenIndex = index;
},
jswid(){
let wd =(this.gridNum-1)-2
if(wd<=0) wd = 1;
return 100/wd;
},
scrllEnd(e) {
function monthDay(year, month) {
var date = new Date(year, month, 1, 0, 0, 0)
var yesterDay = new Date(date - 1000)
return yesterDay.getDate()
}
if (!this.scrollEvent) return;
let dNum = this.gridNum;
let d;
let t = this;
let idb = 88;
let idc = 884;
let srcllTop = this.scrollEvent.detail.scrollTop ;
if(srcllTop<=0){
srcllTop = 0;
}else if(srcllTop >= this.scrollEvent.detail.srcollHeigh){
srcllTop = this.scrollEvent.detail.srcollHeigh;
}
d = Math.ceil((srcllTop - (this.itemHeight / 2)) / this.itemHeight);
if(d>= t.listData[t.childrenIndex].length-1){
d = t.listData[t.childrenIndex].length-1
}
t.$set(t.listIndex[t.childrenIndex], 'wz', srcllTop)
let old_index = this.listIndex[this.childrenIndex].itemIndex || 0;
if(t.disabled){
clearTimeout(idb)
idb = setTimeout(function() {
t.$nextTick(function(){
t.$set(t.listIndex[t.childrenIndex], 'wz', old_index * t.itemHeight)
})
}, 5);
return;
}
this.$set(this.listIndex[t.childrenIndex], 'itemIndex', d)
t.chulisdata();
clearTimeout(idb);
idb = setTimeout(function() {
t.$nextTick(function(){
t.$set(t.listIndex[t.childrenIndex], 'wz', d * t.itemHeight)
for(let lsindex = t.childrenIndex+1;lsindex<6;lsindex++){
if( t.listData[lsindex][t.listIndex[lsindex].itemIndex] == undefined){
let pda = t.listData[lsindex].length-1;
if(d>=pda){
this.$set(this.listIndex[lsindex], 'itemIndex',pda)
t.$set(t.listIndex[lsindex], 'wz', pda * t.itemHeight)
}else{
this.$set(this.listIndex[lsindex], 'itemIndex',0)
t.$set(t.listIndex[lsindex], 'wz', 0)
}
}else{
let srcllTop_s = t.listIndex[lsindex].wz;
const tsdd = t.listIndex[lsindex].itemIndex;
t.$set(t.listIndex[lsindex], 'wz', (tsdd) * t.itemHeight-1)
this.$nextTick(function(){
t.$set(t.listIndex[lsindex], 'wz', (tsdd) * t.itemHeight)
})
}
}
})
}, 10);
}
},
}
</script>
<style lang="scss" scoped>
.tm-pickersDateView {
.tm-pickersDateView-wk {
position: relative;
.tm-pickersDateView-showbg {
position: absolute;
left: 0;
height: 0;
z-index: 5;
.tm-pickersDateView-item {
.tm-pickersDateView-item-text {
text {
transition: all 0.5s;
}
&.textLevel_1 {
text {
font-size: 28upx !important;
}
}
&.textLevel_2 {
text {
font-size: 26upx !important;
}
}
}
}
}
.tm-pickersDateView-fg {
position: relative;
z-index: 3;
}
}
}
</style>
@@ -0,0 +1,650 @@
<!-- 日期组件 -->
<template>
<view class="tm-pickersDateView flex-start px-24" :class="[black_tmeme ? 'grey-darken-5' : bgColor]">
<!-- :value="value_default" @change="change" -->
<picker-view
@pickstart="$emit('aniStart')"
@pickend="$emit('aniEnd')"
@change="change"
v-if="list_cD != null"
:value="value_default"
:mask-style="black_tmeme ? 'opacity:0;' : ''"
indicator-style="height:50px;"
indicator-class="tm-pickersCView-item-h"
class="tm-pickersCView-wk"
>
<picker-view-column v-show="syheng_key[key]" v-for="(item, key) in list_cD" :key="key">
<view
class="tm-pickersCView-item fulled-height flex-center "
style="margin: 0 5px;"
:class="[value_default[key] == index_pub ? ' text-weight-b active' : '', black_tmeme ? 'bk' : '', 'text-size-n']"
v-for="(item_data, index_pub) in item"
:key="index_pub"
>
<text>{{ buqi(item_data) }}</text>
<text v-if="mode">{{ modhz[key] }}</text>
</view>
</picker-view-column>
</picker-view>
</view>
</template>
<script>
/**
* 日期下拉选择器(嵌入式)
* @description 多级关联,单级关联选择
* @property {Array} default-value = [] 默认:当前的时间,初始显示的时间
* @property {String|Number} item-height = [34|42|50|58|62] 项目的高度单位px
* @property {String|Boolean} black = [true|false] 是否开启暗黑模式。
* @property {String|Boolean} disabled = [true|false] 是否禁用
* @property {String} bg-color = [white|blue] 默认:white,白色背景;请填写背景的主题色名称。
* @property {Object} show-detail = [{year:true,month:true,day:true,hour:false,min:false,sec:false}] 默认:{year:true,month:true,day:true,hour:false,min:false,sec:false}
* @property {String} start = [1900-1-1 00:00:00] 默认:1900-1-1 00:00:00,开始的时间
* @property {String} end = [] 默认:当前,结束的时间
* @property {String|Boolean} mode = [true|false] 默认:true,是否显示中文年,月后缀
* @property {String|Boolean} full-number = [true|false] 默认:true,是否把个位数补齐双位数
*/
export default {
name: 'tm-pickersDateView',
props: {
// 行高。
itemHeight: {
type: String | Number,
default: 40
},
black: {
type: String | Boolean,
default: null
},
// 是否禁用
disabled: {
type: String | Boolean,
default: false
},
// 背景颜色,主题色名称。
bgColor: {
type: String,
default: 'white'
},
//要展示的时间。
showDetail: {
type: Object,
default: () => {
return {
year: true, //年
month: true, //月
day: true, //天
hour: false, //小时
min: false, //分
sec: false //秒
};
}
},
start: {
type: String,
default: '1949-1-1 00:00:00'
},
end: {
type: String,
default: ''
},
defaultValue: '',
// 是否显示中文年,月后缀
mode: {
type: String | Boolean,
default: true
},
//要展示的时间。
modeValue: {
type: Object,
default: () => {
return {
year: '年', //年
month: '月', //月
day: '日', //天
hour: '时', //小时
min: '分', //分
sec: '秒' //秒
};
}
},
// 是否把个位数补齐双位数
fullNumber: {
type: String | Boolean,
default: true
}
},
data() {
return {
dataCauser: {
year: false, //年
month: false, //月
day: false, //天
hour: false, //小时
min: false, //分
sec: false //秒
},
hoz: {
year: '年', //年
month: '月', //月
day: '日', //天
hour: '时', //小时
min: '分', //分
sec: '秒' //秒
},
totalRow:0,
syheng_key: {},
//当前生成的所有数据年~秒
r_list:[],
list_cD: null,
value_default: [],
nowObj:null,
};
},
created() {
this.dataCauser = {...this.dataCauser,...(this.showDetail||{})}
this.setdataCauserArray();
this._reInits();
},
mounted() {
},
watch: {
showDetail:{
deep:true,
handler(){
this.dataCauser = {...this.dataCauser,...this.showDetail};
this.setdataCauserArray();
}
},
defaultValue: function(val) {
let nowdateVal;
if (val) {
nowdateVal = new Date(val.replace(/-/g, '/'));
} else {
nowdateVal = new Date();
}
this.nowObj = nowdateVal;
if(this.list_cD==null) return;
this._reInits();
},
start: async function() {
if(this.list_cD==null) return;
this._reInits();
},
end: async function() {
if(this.list_cD==null) return;
this._reInits();
}
},
computed: {
black_tmeme: function() {
if (this.black !== null) return this.black;
return this.$tm.vx.state().tmVuetify.black;
},
modhz: function() {
let hz = [];
let moz = { ...this.hoz, ...this.modeValue };
hz.push(moz.year)
hz.push(moz.month)
hz.push(moz.day)
hz.push(moz.hour)
hz.push(moz.min)
hz.push(moz.sec)
return hz;
},
detavlue:function () {
let d = this.defaultValue;
if(!d){
let ys = new Date();
d = ys.getFullYear()+'-'+(ys.getMonth()+1)+'-'+ys.getDate()+' '+ys.getHours()+':'+ys.getMinutes()+':'+ys.getSeconds()
}
return d.replace(/-/g, '/');
},
//结束的日期,默认为当前
end_str:function () {
let d = this.end;
if(!d){
let ys = new Date();
d = ys.getFullYear()+'-'+(ys.getMonth()+1)+'-'+ys.getDate()+' '+ys.getHours()+':'+ys.getMinutes()+':'+ys.getSeconds()
}
return d.replace(/-/g, '/');
},
//开始默认为1960年
start_str:function () {
let d = this.start;
if(!d){
d='1960-1-1 00:00:00'
}
return d.replace(/-/g, '/');
},
},
methods: {
//设置显示的行当。
setdataCauserArray(){
let t = this;
let f = {
'0':this.dataCauser['year'],
'1':this.dataCauser['month'],
'2':this.dataCauser['day'],
'3':this.dataCauser['hour'],
'4':this.dataCauser['min'],
'5':this.dataCauser['sec'],
}
//显示的列表数。
let totalHoz = 0;
let p = Object.keys(this.dataCauser);
p = p.filter(el=>t.dataCauser[el]==true)
this.totalRow = p.length;
this.syheng_key = f;
},
//初始生成对应的开始和结束日期数据。
_reInits(date){
let t = this;
let nowdateVal;
if (date) {
nowdateVal = new Date(date.replace(/-/g, '/'));
} else {
nowdateVal = new Date(this.detavlue);
}
this.nowObj = nowdateVal;
/**
* 接下来,需要比对,年月,日。
* 分开比较的原因是:如果年不变的话,只是改变月,那么只需重新
* 更改日的数据(如果每月的日期一样,也不需要改变。)
*/
//根据提供的值nodwdateVal来划定开始和结束的日期数据。为了保证流畅,采用一次性生成的方法。
//先生成开始的数据。
//开始
const start = new Date(this.start_str);
//结束
const end = new Date(this.end_str);
//当前
const now = nowdateVal;
let list = [];
let year = this.range(start.getFullYear(),end.getFullYear())
list.push(year)
// 月。是需要根据nowdateVal提供的值来生成。因为月是不固定的。
//默认先生成start到12的
let month_s = this.range(start.getMonth()+1,12)
let month_e = this.range(1,end.getMonth()+1)
//同年同月
if(start.getFullYear()==end.getFullYear()&&start.getMonth()==end.getMonth()){
let tn = this.range(start.getMonth()+1,end.getMonth()+1);
list.push([tn,tn])
}else{
list.push([month_s,month_e])
}
let day_s = this.range(start.getDate(),this.monthDay(start.getFullYear(),start.getMonth()))
let day_e = this.range(1,end.getDate())
//同年同月同日
if(start.getFullYear()==end.getFullYear()
&&start.getMonth()==end.getMonth()
&&start.getDate()==end.getDate()
){
let tn = this.range(start.getDate(),end.getDate());
list.push([tn,tn])
}else{
list.push([day_s,day_e])
}
let hours_s = this.range(start.getHours(),23)
let hours_e = this.range(0,end.getHours())
//同年同月同日同时
if(start.getFullYear()==end.getFullYear()
&&start.getMonth()==end.getMonth()
&&start.getDate()==end.getDate()
&&start.getHours()==end.getHours()
){
let tn = this.range(start.getHours(),end.getHours());
list.push([tn,tn])
}else{
list.push([hours_s,hours_e])
}
let minutes_s = this.range(start.getMinutes(),59)
let minutes_e = this.range(0,end.getMinutes())
//同年同月同日同时同分
if(start.getFullYear()==end.getFullYear()
&&start.getMonth()==end.getMonth()
&&start.getDate()==end.getDate()
&&start.getHours()==end.getHours()
){
let tn = this.range(start.getMinutes(),end.getMinutes());
list.push([tn,tn])
}else{
list.push([minutes_s,minutes_e])
}
let seconds_s = this.range(start.getSeconds(),59)
let seconds_e = this.range(0,end.getSeconds())
//同年同月同日同时同分同秒
if(start.getFullYear()==end.getFullYear()
&&start.getMonth()==end.getMonth()
&&start.getDate()==end.getDate()
&&start.getHours()==end.getHours()
&&start.getSeconds()==end.getSeconds()
){
let tn = this.range(start.getSeconds(),end.getSeconds());
list.push([tn,tn])
}else{
list.push([seconds_s,seconds_e])
}
this.r_list = list;
this.$nextTick(function () {
this._getListCd(start,end,now)
})
},
//生成对应的列表数据,以供选择。不需要生成所有,只要生成默认当前时间的。
_getListCd(start,end,now,issetd){
let list_cD = [];
//年
list_cD.push(this.r_list[0])
//月。
let year_s = new Date(String(start.getFullYear())+'/1/1').getTime()
let year_e = new Date(String(end.getFullYear())+'/1/1').getTime()
let year_n = new Date(String(now.getFullYear())+'/1/1').getTime()
if(
year_s===year_e //开始和结束相同
||(year_s!=year_e&&year_n==year_s) //现在=开始。
||(year_s!=year_e&&year_n<year_s) //现在小于开始
){
list_cD.push(this.r_list[1][0])
}else if(
(year_s!=year_e&&year_n==year_e) //现在=结束
||(year_s!=year_e&&year_n>year_e) //现在大于结束
){
list_cD.push(this.r_list[1][1])
}else{ //在开始和结束之间。
list_cD.push(this.range(1,12))
}
//日。
let day_s = new Date(start.getFullYear()+'/'+(start.getMonth()+1)+'/1').getTime()
let day_e = new Date(end.getFullYear()+'/'+(end.getMonth()+1)+'/1').getTime()
let day_n = new Date(now.getFullYear()+'/'+(now.getMonth()+1)+'/1').getTime()
if(
day_s===day_e //开始和结束相同
||(day_s!=day_e&&day_n==day_s) //现在=开始。
||(day_s!=day_e&&day_n<day_s) //现在小于开始
){
list_cD.push(this.r_list[2][0])
}else if(
(day_s!=day_e&&day_n==day_e) //现在=结束
||(day_s!=day_e&&day_n>day_e) //现在大于结束
){
list_cD.push(this.r_list[2][1])
}else{ //在开始和结束之间。
list_cD.push(this.range(1,this.monthDay(now.getFullYear(),now.getMonth())))
}
//时。
let hours_s = new Date(String(start.getFullYear())+'/'+(start.getMonth()+1)+'/'+start.getDate()).getTime()
let hours_e = new Date(String(end.getFullYear())+'/'+(end.getMonth()+1)+'/'+end.getDate()).getTime()
let hours_n = new Date(String(now.getFullYear())+'/'+(now.getMonth()+1)+'/'+now.getDate()).getTime()
if(
hours_s===hours_e //开始和结束相同
||(hours_s!=hours_e&&hours_n==hours_s) //现在=开始。
||(hours_s!=hours_e&&hours_n<hours_s) //现在小于开始
){
list_cD.push(this.r_list[3][0])
}else if(
(hours_s!=hours_e&&hours_n==hours_e) //现在=结束
||(hours_s!=hours_e&&hours_n>hours_e) //现在大于结束
){
list_cD.push(this.r_list[3][1])
}else{ //在开始和结束之间。
list_cD.push(this.range(0,23))
}
//分。
let min_s = new Date(String(start.getFullYear())+'/'+(start.getMonth()+1)+'/'+start.getDate()+' '+start.getHours()+':00:00').getTime()
let min_e = new Date(String(end.getFullYear())+'/'+(end.getMonth()+1)+'/'+end.getDate()+' '+end.getHours()+':00:00').getTime()
let min_n = new Date(String(now.getFullYear())+'/'+(now.getMonth()+1)+'/'+now.getDate()+' '+now.getHours()+':00:00').getTime()
if(
min_s===min_e //开始和结束相同
||(min_s!=min_e&&min_n==min_s) //现在=开始。
||(min_s!=min_e&&min_n<min_s) //现在小于开始
){
list_cD.push(this.r_list[4][0])
}else if(
(min_s!=min_e&&min_n==min_e) //现在=结束
||(min_s!=min_e&&min_n>min_e) //现在大于结束
){
list_cD.push(this.r_list[4][1])
}else{ //在开始和结束之间。
list_cD.push(this.range(0,59))
}
//秒。
let seccode_s = new Date(String(start.getFullYear())+'/'+(start.getMonth()+1)+'/'+start.getDate()+' '+start.getHours()+':'+start.getMinutes()+':00').getTime()
let seccode_e = new Date(String(end.getFullYear())+'/'+(end.getMonth()+1)+'/'+end.getDate()+' '+end.getHours()+':'+start.getMinutes()+':00').getTime()
let seccode_n = new Date(String(now.getFullYear())+'/'+(now.getMonth()+1)+'/'+now.getDate()+' '+now.getHours()+':'+start.getMinutes()+':00').getTime()
if(
seccode_s===seccode_e //开始和结束相同
||(seccode_s!=seccode_e&&seccode_n==seccode_s) //现在=开始。
||(seccode_s!=seccode_e&&seccode_n<seccode_s) //现在小于开始
){
list_cD.push(this.r_list[4][0])
}else if(
(seccode_s!=seccode_e&&seccode_n==seccode_e) //现在=结束
||(seccode_s!=seccode_e&&seccode_n>seccode_e) //现在大于结束
){
list_cD.push(this.r_list[4][1])
}else{ //在开始和结束之间。
list_cD.push(this.range(0,59))
}
this.$nextTick(function () {
this.list_cD = list_cD;
if(!issetd){
this.$nextTick(function () {
this.setDefaultIndex();
})
}
})
},
monthDay(year, month) {
let date = new Date(year, month, 1, 0, 0, 0);
date.setMonth(date.getMonth()+1)
let yesterDay = new Date(date - 1000);
return yesterDay.getDate();
},
//生成一个数据数组。
range(from=0,to){
const range = [];
if(from===to) return [from];
for (let i = from; i <= to; i++) {
range.push(i)
}
return range
},
//设置当前选中的索引。
setDefaultIndex(){
if(!this.list_cD) return;
let value_default = [];
let t = this;
// 年。
let year = this.list_cD[0].findIndex(el=>el==t.nowObj.getFullYear());
year=year<=0?0:year;
let month = this.list_cD[1].findIndex(el=>el==t.nowObj.getMonth()+1);
month=month<=0?0:month;
let day = this.list_cD[2].findIndex(el=>el==t.nowObj.getDate());
day=day<=0?0:day;
let hours = this.list_cD[3].findIndex(el=>el==t.nowObj.getHours());
hours=hours<=0?0:hours;
let minutes = this.list_cD[4].findIndex(el=>el==t.nowObj.getMinutes());
minutes=minutes<=0?0:minutes;
let seconds = this.list_cD[5].findIndex(el=>el==t.nowObj.getSeconds());
seconds=seconds<=0?0:seconds;
// 开始设置,如果当前默认的日期不在范围内。默认选中的索引日期。
value_default = [year,month,day,hours,minutes,seconds]
this.$nextTick(function () {
this.value_default = value_default;
})
},
//回显到初始化值。
resetVal(setd){
let val = this.defaultValue;
if(setd) val = setd;
let nowdateVal;
if (val) {
nowdateVal = new Date(val.replace(/-/g, '/'));
} else {
nowdateVal = new Date();
}
this.nowObj = nowdateVal;
if(this.list_cD==null) return;
this._reInits();
this.$nextTick(function () {
this.setDefaultIndex();
})
},
buqi(val) {
return val > 9 ? '' + val : '0' + val;
},
//通过索引获取当前数据
SeletecdeIndexdefault(value_default) {
if(!value_default) value_default = this.value_default;
let t = this;
let ap = [];
this.value_default.forEach((item,index) => {
let f = t.list_cD[index][parseInt(item)];
f = typeof(f)=="undefined"? t.list_cD[index][ t.list_cD[index].length-1]:f;
ap.push(f);
});
return ap;
},
// 获取当前选中的数据。
getSelectedValue() {
let t = this;
let ap = this.SeletecdeIndexdefault();
let jg = {
year: ap[0], //年
month: ap[1], //月
day: ap[2], //天
hour: ap[3], //小时
min: ap[4], //分
sec: ap[5] //秒
};
let ar = Object.keys(this.dataCauser);
ar.forEach(item => {
if (t.dataCauser[item] === false) {
delete jg[item];
}
});
return jg;
},
getSelsectDate() {
let t = this;
let ap = this.SeletecdeIndexdefault();
let jg = {
year: ap[0], //年
month: ap[1], //月
day: ap[2], //天
hour: ap[3], //小时
min: ap[4], //分
sec: ap[5] //秒
};
return new Date(ap[0]+'/'+ap[1]+'/'+ap[2]+' '+ap[3]+':'+ap[4]+':'+ap[5]);
},
change(e) {
//滑动后,要动态修改数据。
let val = e.detail.value;
let index =0;
// 找出修改的index项。
let nowD = [this.nowObj.getFullYear(),1,1,0,0,0];
let nowObj = [this.nowObj.getFullYear(),this.nowObj.getMonth()+1,this.nowObj.getDate(),this.nowObj.getHours(),this.nowObj.getMinutes(),this.nowObj.getSeconds()];
for (var i = 0; i < 6; i++) {
if(this.value_default[i]!==val[i]){
nowD[i] = this.list_cD[i][val[i]]
}else{
let idx = this.list_cD[i].findIndex(el=>el==nowObj[i])
if(idx==-1){
nowD[i] = this.list_cD[i][0]
}else{
nowD[i] = nowObj[i]
}
}
}
const now = nowD[0]+'/'+(nowD[1])+'/'+nowD[2]+' '+nowD[3]+':'+nowD[4]+':'+nowD[5];
this._reInits(now)
let nowVal = val.map(el=>{
let dsdd = el<=0?0:el;
return dsdd
})
this.$nextTick(function () {
this.value_default = nowVal;
// 发送滚动选中的时间数据。
this.$emit('change',this.getSelectedValue());
})
},
jswid() {
let wd = this.gridNum - 1 - 2;
if (wd <= 0) wd = 1;
return 100 / wd;
},
}
};
</script>
<style>
.tm-pickersDateView .tm-pickersCView-item-h {
height: 50px;
background-color: rgba(0, 0, 0, 0.03);
width: calc(100% - 10px);
margin-left: 5px;
border-radius: 20rpx;
border: none;
}
.tm-pickersDateView .tm-pickersCView-item-h::after,
.tm-pickersDateView .tm-pickersCView-item-h::before {
border: none;
}
.tm-pickersDateView .tm-pickersCView-wk {
position: relative;
width: 750rpx;
height: 500rpx;
}
.tm-pickersDateView .tm-pickersCView-wk .tm-pickersCView-item.bk {
opacity: 0.4;
}
.tm-pickersDateView .tm-pickersCView-wk .tm-pickersCView-item.active {
opacity: 1;
border-radius: 20rpx;
border: none;
background-color: rgba(0, 0, 0, 0.06);
}
.tm-pickersDateView .tm-pickersCView-wk .tm-pickersCView-item.active.bk {
background-color: rgba(255, 255, 255, 0.06);
}
</style>
@@ -0,0 +1,538 @@
<template>
<view class="tm-pickersView flex-start px-24" :class="[black_tmeme?'grey-darken-5':bgColor]">
<!-- @change="bindChange" -->
<picker-view @change="change" @pickstart='$emit("aniStart")' @pickend='$emit("aniEnd")' v-if="listData.length>0" :value="value_default"
:mask-style='black_tmeme?"opacity:0;":""'
indicator-style='height:50px;'
indicator-class="tm-pickersBView-item-h"
class="tm-pickersBView-wk">
<picker-view-column v-for="(item,index) in listData" :key="index">
<view class="tm-pickersBView-item fulled-height flex-center " style="margin: 0 5px;" :class="[value_default[index]==index_pub?'text-size-n text-weight-b active':'',black_tmeme?'bk':'']" v-for="(item_data,index_pub) in listData[index]" :key="index_pub">
<text v-if="dataType == 'string'" >{{item_data}}</text>
<text v-if="dataType == 'object'">{{item_data[rangKey]}}</text>
</view>
</picker-view-column>
</picker-view>
</view>
</template>
<script>
/**
* 普通级联拉选择器(嵌入式)
* @description 多级关联,单级关联选择
* @property {Array} default-value = [] 默认:[],默认赋值项。可选三种赋值方式,名称赋值,对象赋值,数字序列赋值
* @property {String|Number} item-height = [34|42|50|58|62] 项目的高度单位px
* @property {Array} list = [] 选择器的数据,可选格式:Array<string>,Array<object>.如果为object格式需要提供rangKey.如果为多级需要提供children.key值
* @property {String} rang-key = [text|title] 默认:text,如果List格式为对象数组,需要提供此值
* @property {String} children-key = [children] 默认:children,如果List格式为对象数组且为多级联选择,需要提供此值,理论上无限级联数据
* @property {String|Boolean} black = [true|false] 是否开启暗黑模式。
* @property {String|Boolean} disabled = [true|false] 是否禁用
* @property {String} bg-color = [white|blue] 默认:white,白色背景;请填写背景的主题色名称。
* @property {Function} change 列数被选中改变时触发。
*
*/
export default {
name: "tm-pickersView",
props: {
// 默认选中的项
// 格式有三种分别是[string,string...]
// [数字序列,数字序列....]
// 和list同等对象结构[{},{},...],此格式需要提供rangKey字段否则报错。
defaultValue:{
type:Array,
default:()=>{return []}
},
// 行高。
itemHeight: {
type: String | Number,
default: 40
},
list: {
type: Array,
default: () => {
return []
}
},
// 如果数据是对象,则需要提供key值。
rangKey: {
type: String,
default: "text"
},
rangKeyId: {
type: String,
default: "id"
},
// 如果是联级,则需要提供子集key值。
childrenKey: {
type: String,
default: "children"
},
black:{
type:String|Boolean,
default:null
},
// 是否禁用
disabled:{
type:String|Boolean,
default:false
},
// 背景颜色,主题色名称。
bgColor:{
type:String,
default:'white'
}
},
data() {
return {
value_default:[],
pre_value:[],
scrollEvent: 0,
childrenIndex: 0,
listIndex: [],
listData: [],
idx:9123
};
},
mounted() {
this.$nextTick(function(){
this.chulisdata()
this.setDefaultValue();
})
},
watch:{
defaultValue:{
deep:true,
handler: function(newV,oldV){
this.chulisdata()
this.$nextTick(function(){
this.inits();
})
}
},
list:{
deep:true,
handler:async function(newV,oldV){
this.chulisdata()
this.$nextTick(async function(){
await this.inits();
})
}
},
},
computed: {
black_tmeme: function() {
if (this.black !== null) return this.black;
return this.$tm.vx.state().tmVuetify.black;
},
dataType: function() {
// 数据有误
if (typeof this.list !== 'object' && !Array.isArray(this.list) && !this.list.length) return null;
if (typeof this.list[0] === 'string') return 'string';
if (typeof this.list[0] === 'object') return 'object';
},
gridNum: function() {
let t = this;
if (
(typeof this.list !== 'object' && !Array.isArray(this.list) && this.list.length==0)||
typeof this.list[0] === 'undefined'
) {
this.listIndex = [{
itemIndex: 0,
childrenIndex: 0,
wz: 0
}]
return 0
};
if (typeof this.list[0] === 'string') {
this.listIndex = [{
itemIndex: 0,
childrenIndex: 0,
wz: 0
}]
return 1
}
if (typeof this.list[0] === 'object') {
let index = 0;
let cindex = 1;
let pds = []
function tests(obj) {
if(!obj||obj?.length==0){
return;
}
cindex = cindex+1;
index +=1;
pds.push({
itemIndex: 0,
childrenIndex: index,
wz: 0
})
if (obj && typeof obj === 'object' && Array.isArray(obj)) {
if (obj[0][t.childrenKey]) {
tests(obj[0][t.childrenKey]);
}
}
}
pds.push({
itemIndex: 0,
childrenIndex: index,
wz: 0
})
tests(this.list[0][this.childrenKey])
t.listIndex = pds;
return cindex;
}
},
},
methods: {
SeletecdeIndexdefault(){
let d = []
for(let i=0;i<this.gridNum;i++){
d.push(this.listIndex[i].itemIndex)
}
this.value_default = d;
},
// 获取当前选中的数据
getSelectedValue(){
let t = this;
// 总的级联数
let dNum = this.gridNum;
let pd = this.listIndex;
if(this.dataType === 'string'){
return [{
index:this.listIndex[0].itemIndex,
data:this.listData[0][this.listIndex[0].itemIndex]
}]
}else if(this.dataType === 'object'){
if(dNum===1){
let ps = {...this.listData[0][this.listIndex[0].itemIndex]};
delete ps.children;
return [{
index:this.listIndex[0].itemIndex,
data:ps
}]
}else if(dNum>1){
let p = [];
this.listIndex.forEach((item,index)=>{
if(t.listData[index]){
let ps = {...t.listData[index][item.itemIndex]};
delete ps.children;
p.push({
index:item.itemIndex,
data:ps
})
}
})
return p;
}
}
return [];
},
chulisdata() {
// 总的级联数。
let dNum = this.gridNum;
let t = this;
if (dNum === 0) {
this.listData = [];
this.$forceUpdate()
return this.listData;
}
if (dNum === 1) {
this.listData = [this.list];
// this.listData.push([...this.list]);
this.$forceUpdate()
return this.listData;
}
if (dNum > 1) {
let index = 1;
let list = [];
let p = [];
function tests(obj) {
if(index > dNum) return;
list.push([...obj])
if(obj[t.listIndex[index]?.itemIndex]){
let cl = obj[t.listIndex[index].itemIndex][t.childrenKey];
if (cl && typeof cl === 'object' && Array.isArray(cl)) {
index++;
tests(cl);
}
}
}
p.push([...this.list])
if(this.list[t.listIndex[0].itemIndex][this.childrenKey]){
tests(this.list[t.listIndex[0].itemIndex][this.childrenKey])
}
p.push(...list);
this.$forceUpdate()
this.listData = p;
}
return this.listData;
},
setDefaultValue(objSelected){
let t = this;
uni.$tm.sleep(50).then(()=>t.inits(objSelected))
.then(()=>uni.$tm.sleep(50))
.then(()=>t.SeletecdeIndexdefault())
},
async inits(objSelected){
// 总的级联数。
let dNum = this.gridNum;
let t = this;
var sjd = null;
if(typeof objSelected ==='object' && Array.isArray(objSelected)){
sjd = objSelected;
}else{
if(!this.defaultValue||this.defaultValue.length==0) return;
sjd = this.defaultValue;
}
let typeindex = typeof sjd[0];
if(dNum===0) return;
if(typeindex === 'number'){
if (dNum === 1) {
let itemIndex = sjd[0];
if(typeof itemIndex === 'number' && !isNaN(itemIndex) ){
this.$set(this.listIndex[0], 'itemIndex', itemIndex);
}
return
}else if(dNum > 1){
let index = 1;
async function tests() {
if(index > dNum) return;
let itemIndex = t.defaultValue[index];
if(typeof itemIndex === 'number' && !isNaN(itemIndex) &&typeof t.listIndex[index] === 'object' && typeof t.listIndex[index] !=='undefined'){
await uni.$tm.sleep(30)
t.$set(t.listIndex[index], 'itemIndex', itemIndex);
t.chulisdata();
index++;
await tests();
}
}
let itemIndex = sjd[0];
this.$set(this.listIndex[0], 'itemIndex', itemIndex);
this.chulisdata();
await tests()
}
}else if(typeindex === 'string'){
if(this.dataType==='string'){
if (dNum === 1) {
let valueStr = sjd[0];
if(typeof valueStr !=='string' || typeof valueStr ==='undefined' || valueStr ==null){
return;
}
let itemIndex = this.listData[0].indexOf(valueStr)
if(itemIndex>-1){
this.$set(this.listIndex[0], 'itemIndex', itemIndex);
}
return
}
}else if(this.dataType === 'object'){
if (dNum === 1) {
let valueStr = sjd[0];
if(typeof valueStr !=='string' || typeof valueStr ==='undefined' || valueStr ==null){
return;
}
let itemIndex = this.listData[0].findIndex(item=>{
return item[t.rangKey] == valueStr;
})
if(itemIndex>-1){
this.$set(this.listIndex[0], 'itemIndex', itemIndex);
}
return
}else if(dNum>1){
let index = 0;
async function tests() {
if(index > dNum) return;
if(typeof t.listIndex[index] === 'object' && typeof t.listIndex[index] !=='undefined'){
let valueStr = t.defaultValue[index];
if(typeof valueStr !=='string' || typeof valueStr ==='undefined' || valueStr ==null){
return;
}
let itemIndex = t.listData[index].findIndex(item=>{
return item[t.rangKey] == valueStr;
})
if(itemIndex>-1){
await uni.$tm.sleep(30)
t.$set(t.listIndex[index], 'itemIndex', itemIndex);
t.chulisdata();
}
index++;
await tests();
}
}
await tests()
}
}
}else if(typeindex === 'object'){
if (dNum === 1) {
let valueStr = sjd[0];
if(typeof valueStr[t.rangKey] ==='undefined' || typeof valueStr !=='object' || typeof valueStr ==='undefined' || valueStr ==null){
return;
}
let itemIndex = this.listData[0].findIndex(item=>{
return (item[t.rangKey] == valueStr[t.rangKey])||(parseInt(item[t.rangKeyId]) == parseInt(valueStr[t.rangKeyId]));;
})
if(itemIndex>-1){
this.$set(this.listIndex[0], 'itemIndex', itemIndex);
}
return
}else if(dNum>1){
let index = 0;
async function tests() {
if(index > dNum) return;
if(typeof t.listIndex[index] === 'object' && typeof t.listIndex[index] !=='undefined'){
let valueStr = t.defaultValue[index];
if(typeof valueStr[t.rangKey] ==='undefined' || typeof valueStr !=='object' || typeof valueStr ==='undefined' || valueStr ==null){
return;
}
let itemIndex = t.listData[index].findIndex(item=>{
return (item[t.rangKey] == valueStr[t.rangKey])||(parseInt(item[t.rangKeyId]) == parseInt(valueStr[t.rangKeyId]));
})
if(itemIndex>-1){
await uni.$tm.sleep(30)
t.$set(t.listIndex[index], 'itemIndex', itemIndex);
t.$set(t.listIndex[index], 'wz', itemIndex * t.itemHeight);
t.chulisdata();
}
index++;
await tests();
}
}
await tests()
}
}
},
change(e) {
let pl = [...e.detail.value];
this.pre_value =[...this.value_default];
if(this.disabled){
this.value_default = this.pre_value;
return;
}
let childrenIndex = 0;
for(let i=0;i<pl.length;i++){
if(this.listIndex[i].itemIndex !== pl[i]){
childrenIndex = this.listIndex[i].childrenIndex;
break;
}
}
this.childrenIndex = childrenIndex;
for(let i=childrenIndex;i<pl.length;i++){
if(this.listIndex[i]?.itemIndex !== pl[i]){
this.$set(this.listIndex[i],'itemIndex',pl[i])
}else{
this.$set(this.listIndex[i],'itemIndex',0)
pl[i] = 0;
}
}
this.chulisdata()
this.$nextTick(function(){
this.value_default = pl;
this.$emit("change",pl)
})
},
},
}
</script>
<style >
.tm-pickersView .tm-pickersBView-item-h{
height: 50px;
background-color: rgba(0,0,0,0.03);
width: calc(100% - 10px);
margin-left: 5px;
border-radius: 20rpx;
border: none;
}
.tm-pickersView .tm-pickersBView-item-h::after,.tm-pickersView .tm-pickersBView-item-h::before{
border: none;
}
.tm-pickersView .tm-pickersBView-wk{
position: relative;
width: 750rpx;
height: 500rpx;
}
.tm-pickersView .tm-pickersBView-wk .tm-pickersBView-item.bk{
opacity: 0.4;
}
.tm-pickersView .tm-pickersBView-wk .tm-pickersBView-item.active{
opacity: 1;
border-radius: 20rpx;
border: none;
background-color: rgba(0,0,0,0.06);
}
.tm-pickersView .tm-pickersBView-wk .tm-pickersBView-item.active.bk{
background-color: rgba(255,255,255,0.06);
}
</style>
@@ -0,0 +1,587 @@
<template>
<view class="tm-pickersView flex-start px-24" :class="[black_tmeme?'grey-darken-5':bgColor]">
<view v-for="(item_data,index_pub) in listData" :key="index_pub" class="tm-pickersView-wk " :style="{
height:itemHeight*5+'px',
width:(100/gridNum-1) + '%',
marginLeft:index_pub==0?0:1.5 + '%',
}">
<scroll-view :show-scrollbar='false'
v-if="dataType!==null&&item_data"
:scroll-top="listIndex[index_pub].wz" scroll-y
@touchend="scrllEnd"
@scroll="scroll($event,index_pub)"
scroll-with-animation
class="tm-pickersView-showbg" :style="{
height:itemHeight*5+'px'
}">
<!-- #ifdef H5 -->
<view :id='"_bar_"+(index4)+"_bar_"' v-for="(item4,index4) in 2" :key="index4+'_a'" class="tm-pickersView-item "
:style="{height:itemHeight+'px'}">
<text class="opacity-1"></text>
</view>
<!-- #endif -->
<!-- #ifndef H5 -->
<view :id='"_bar_"+(index4)+"_bar_"' v-for="(item4,index4) in 2" :key="index4" class="tm-pickersView-item "
:style="{height:itemHeight+'px'}">
<text class="opacity-1"></text>
</view>
<!-- #endif -->
<view :id='"_bar_"+(index+2)+"_bar_"' v-for="(item,index) in item_data" :key="index"
class="tm-pickersView-item flex-center " :style="{
height:itemHeight+'px'
}">
<view class="text-size-n tm-pickersView-item-text " :class="[
listIndex[index_pub].itemIndex==index? (black_tmeme?'text-white':'text-black'):(black_tmeme?'':'opacity-4'),
listIndex[index_pub].itemIndex+1==index||listIndex[index_pub].itemIndex-1==index?'textLevel_1':'',
listIndex[index_pub].itemIndex+2==index||listIndex[index_pub].itemIndex-2==index?'textLevel_2':'',
]">
<text v-if="dataType === 'string'">{{item}}</text>
<text v-if="dataType === 'object'">{{item[rangKey]}}</text>
</view>
</view>
<!-- #ifdef H5 -->
<view v-for="(item4,index4) in 2" :key="index4+'_b'" class="tm-pickersView-item"
:style="{height:itemHeight+'px'}">
<text></text>
</view>
<!-- #endif -->
<!-- #ifndef H5 -->
<view v-for="(item4,index4) in 2" :key="index4" class="tm-pickersView-item"
:style="{height:itemHeight+'px'}">
<text></text>
</view>
<!-- #endif -->
</scroll-view>
<view class="tm-pickersView-fg overflow round-5 shadow-10 flex-center" :class="[
black_tmeme?'white opacity-1':'grey-darken-1 opacity-1'
]" :style="{
height:itemHeight+'px',
top:itemHeight*2+'px'
}">
</view>
</view>
</view>
</template>
<script>
/**
* 普通级联拉选择器(嵌入式)
* @description 多级关联,单级关联选择
* @property {Array} default-value = [] 默认:[],默认赋值项。可选三种赋值方式,名称赋值,对象赋值,数字序列赋值
* @property {String|Number} item-height = [34|42|50|58|62] 项目的高度单位px
* @property {Array} list = [] 选择器的数据,可选格式:Array<string>,Array<object>.如果为object格式需要提供rangKey.如果为多级需要提供children.key值
* @property {String} rang-key = [text|title] 默认:text,如果List格式为对象数组,需要提供此值
* @property {String} children-key = [children] 默认:children,如果List格式为对象数组且为多级联选择,需要提供此值,理论上无限级联数据
* @property {String|Boolean} black = [true|false] 是否开启暗黑模式。
* @property {String|Boolean} disabled = [true|false] 是否禁用
* @property {String} bg-color = [white|blue] 默认:white,白色背景;请填写背景的主题色名称。
*
*/
export default {
name: "tm-pickersView",
props: {
// 默认选中的项
// 格式有三种分别是[string,string...]
// [数字序列,数字序列....]
// 和list同等对象结构[{},{},...],此格式需要提供rangKey字段否则报错。
defaultValue:{
type:Array,
default:()=>{return []}
},
// 行高。
itemHeight: {
type: String | Number,
default: 40
},
list: {
type: Array,
default: () => {
return []
}
},
// 如果数据是对象,则需要提供key值。
rangKey: {
type: String,
default: "text"
},
rangKeyId: {
type: String,
default: "id"
},
// 如果是联级,则需要提供子集key值。
childrenKey: {
type: String,
default: "children"
},
black:{
type:String|Boolean,
default:null
},
// 是否禁用
disabled:{
type:String|Boolean,
default:false
},
// 背景颜色,主题色名称。
bgColor:{
type:String,
default:'white'
}
},
data() {
return {
scrollEvent: 0,
childrenIndex: 0,
listIndex: [],
listData: [],
isTounch:false,
idx:9123
};
},
mounted() {
this.$nextTick(function(){
this.chulisdata()
this.setDefaultValue();
})
},
watch:{
defaultValue:{
deep:true,
handler: function(newV,oldV){
this.setDefaultValue();
}
},
list:{
deep:true,
handler:function(newV,oldV){
this.chulisdata()
this.setDefaultValue();
}
},
},
computed: {
black_tmeme: function() {
if (this.black !== null) return this.black;
return this.$tm.vx.state().tmVuetify.black;
},
dataType: function() {
// 数据有误
if (typeof this.list !== 'object' && !Array.isArray(this.list) && !this.list.length) return null;
if (typeof this.list[0] === 'string') return 'string';
if (typeof this.list[0] === 'object') return 'object';
},
gridNum: function() {
let t = this;
if (typeof this.list !== 'object' && !Array.isArray(this.list) && !this.list.length) {
this.listIndex = [{
itemIndex: 0,
childrenIndex: 0,
wz: 0
}]
return 0
};
if (typeof this.list[0] === 'string') {
this.listIndex = [{
itemIndex: 0,
childrenIndex: 0,
wz: 0
}]
return 1
}
if (typeof this.list[0] === 'object') {
let index = 1;
function tests(obj) {
t.listIndex.push({
itemIndex: 0,
childrenIndex: index,
wz: 0
})
if (obj && typeof obj === 'object' && Array.isArray(obj)) {
index++
if (obj[0][t.childrenKey]) {
tests(obj[0][t.childrenKey]);
}
}
}
this.listIndex = [{
itemIndex: 0,
childrenIndex: 0,
wz: 0
}]
tests(this.list[0][this.childrenKey])
return index;
}
},
},
methods: {
// 获取当前选中的数据。
getSelectedValue(){
let t = this;
// 总的级联数。
let dNum = this.gridNum;
let pd = this.listIndex;
if(this.dataType === 'string'){
// let ps = {...this.listData[0][this.listIndex[0].itemIndex]};
return [{
index:this.listIndex[0].itemIndex,
data:this.listData[0][this.listIndex[0].itemIndex]
}]
}else if(this.dataType === 'object'){
if(dNum===1){
let ps = {...this.listData[0][this.listIndex[0].itemIndex]};
delete ps.children;
return [{
index:this.listIndex[0].itemIndex,
data:ps
}]
}else if(dNum>1){
let p = [];
this.listIndex.forEach((item,index)=>{
if(t.listData[index]){
let ps = {...t.listData[index][item.itemIndex]};
delete ps.children;
p.push({
index:item.itemIndex,
data:ps
})
}
})
return p;
}
}
return [];
},
chulisdata() {
// 总的级联数。
let dNum = this.gridNum;
let t = this;
if (dNum === 0) {
this.listData = [];
return this.listData;
}
if (dNum === 1) {
this.listData = [];
this.listData.push([...this.list]);
return this.listData;
}
if (dNum > 1) {
let index = 1;
let list = [];
let p = [];
function tests(obj) {
if(index > dNum) return;
list.push([...obj])
if(obj[t.listIndex[index]?.itemIndex]){
let cl = obj[t.listIndex[index].itemIndex][t.childrenKey];
if (cl && typeof cl === 'object' && Array.isArray(cl)) {
index++;
tests(cl);
}
}
}
p.push([...this.list])
if(this.list[t.listIndex[0].itemIndex][this.childrenKey]){
tests(this.list[t.listIndex[0].itemIndex][this.childrenKey])
}
p.push(...list);
this.listData = p;
}
return this.listData;
},
async setDefaultValue(objSelected){
// 总的级联数。
let dNum = this.gridNum;
let t = this;
var sjd = null;
if(typeof objSelected ==='object' && Array.isArray(objSelected)){
sjd = objSelected;
}else{
if(!this.defaultValue||this.defaultValue.length==0) return;
sjd = this.defaultValue;
}
let typeindex = typeof sjd[0];
if(dNum===0) return;
if(typeindex === 'number'){
if (dNum === 1) {
let itemIndex = sjd[0];
if(typeof itemIndex === 'number' && !isNaN(itemIndex) ){
this.$set(this.listIndex[0], 'itemIndex', itemIndex);
this.$set(t.listIndex[0], 'wz', itemIndex * t.itemHeight);
}
return
}else if(dNum > 1){
let index = 1;
async function tests() {
if(index > dNum) return;
let itemIndex = t.defaultValue[index];
if(typeof itemIndex === 'number' && !isNaN(itemIndex) &&typeof t.listIndex[index] === 'object' && typeof t.listIndex[index] !=='undefined'){
await uni.$tm.sleep(30)
t.$set(t.listIndex[index], 'itemIndex', itemIndex);
t.$set(t.listIndex[index], 'wz', itemIndex * t.itemHeight);
t.chulisdata();
index++;
await tests();
}
}
let itemIndex = sjd[0];
this.$set(this.listIndex[0], 'itemIndex', itemIndex);
this.$set(t.listIndex[0], 'wz', itemIndex * t.itemHeight);
this.chulisdata();
await tests()
}
}else if(typeindex === 'string'){
if(this.dataType==='string'){
if (dNum === 1) {
let valueStr = sjd[0];
if(typeof valueStr !=='string' || typeof valueStr ==='undefined' || valueStr ==null){
return;
}
let itemIndex = this.listData[0].indexOf(valueStr)
if(itemIndex>-1){
this.$set(this.listIndex[0], 'itemIndex', itemIndex);
this.$set(t.listIndex[0], 'wz', itemIndex * t.itemHeight);
}
return
}
}else if(this.dataType === 'object'){
if (dNum === 1) {
let valueStr = sjd[0];
if(typeof valueStr !=='string' || typeof valueStr ==='undefined' || valueStr ==null){
return;
}
let itemIndex = this.listData[0].findIndex(item=>{
return item[t.rangKey] == valueStr;
})
if(itemIndex>-1){
this.$set(this.listIndex[0], 'itemIndex', itemIndex);
this.$set(t.listIndex[0], 'wz', itemIndex * t.itemHeight);
}
return
}else if(dNum>1){
let index = 0;
async function tests() {
if(index > dNum) return;
if(typeof t.listIndex[index] === 'object' && typeof t.listIndex[index] !=='undefined'){
let valueStr = t.defaultValue[index];
if(typeof valueStr !=='string' || typeof valueStr ==='undefined' || valueStr ==null){
return;
}
let itemIndex = t.listData[index].findIndex(item=>{
return item[t.rangKey] == valueStr;
})
if(itemIndex>-1){
await uni.$tm.sleep(30)
t.$set(t.listIndex[index], 'itemIndex', itemIndex);
t.$set(t.listIndex[index], 'wz', itemIndex * t.itemHeight);
t.chulisdata();
}
index++;
await tests();
}
}
await tests()
}
}
}else if(typeindex === 'object'){
if (dNum === 1) {
let valueStr = sjd[0];
if(typeof valueStr[t.rangKey] ==='undefined' || typeof valueStr !=='object' || typeof valueStr ==='undefined' || valueStr ==null){
return;
}
let itemIndex = this.listData[0].findIndex(item=>{
return (item[t.rangKey] == valueStr[t.rangKey])||(parseInt(item[t.rangKeyId]) == parseInt(valueStr[t.rangKeyId]));;
})
if(itemIndex>-1){
this.$set(this.listIndex[0], 'itemIndex', itemIndex);
this.$set(t.listIndex[0], 'wz', itemIndex * t.itemHeight);
}
return
}else if(dNum>1){
let index = 0;
async function tests() {
if(index > dNum) return;
if(typeof t.listIndex[index] === 'object' && typeof t.listIndex[index] !=='undefined'){
let valueStr = t.defaultValue[index];
if(typeof valueStr[t.rangKey] ==='undefined' || typeof valueStr !=='object' || typeof valueStr ==='undefined' || valueStr ==null){
return;
}
let itemIndex = t.listData[index].findIndex(item=>{
return (item[t.rangKey] == valueStr[t.rangKey])||(parseInt(item[t.rangKeyId]) == parseInt(valueStr[t.rangKeyId]));
})
if(itemIndex>-1){
await uni.$tm.sleep(30)
t.$set(t.listIndex[index], 'itemIndex', itemIndex);
t.$set(t.listIndex[index], 'wz', itemIndex * t.itemHeight);
t.chulisdata();
}
index++;
await tests();
}
}
await tests()
}
}
},
scroll(e, index) {
this.scrollEvent = e;
this.childrenIndex = index;
},
scrllEnd(e) {
this.isTounch = false;
if (!this.scrollEvent) return;
let dNum = this.gridNum;
let d;
let t = this;
let idb = 88;
let srcllTop = this.scrollEvent.detail.scrollTop;
if(srcllTop<=0){
srcllTop = 0;
}else if(srcllTop >= this.scrollEvent.detail.srcollHeigh){
srcllTop = this.scrollEvent.detail.srcollHeigh;
}
d = Math.ceil((srcllTop - (this.itemHeight / 2)) / this.itemHeight);
if(d>= t.listData[t.childrenIndex].length-1){
d = t.listData[t.childrenIndex].length-1
}
this.$set(this.listIndex[this.childrenIndex], 'wz', srcllTop)
let isNo = false;//是否相同(和上一次对比。)
if(this.listIndex[this.childrenIndex].itemIndex == d){
isNo = true;
}
if(!this.disabled){
this.$set(this.listIndex[this.childrenIndex], 'itemIndex', d)
}
clearTimeout(idb)
idb = setTimeout(function() {
if(t.disabled){
t.$tm.toast("已禁用")
t.$set(t.listIndex[t.childrenIndex], 'wz', t.listIndex[t.childrenIndex].itemIndex * t.itemHeight)
return;
}else{
t.$set(t.listIndex[t.childrenIndex], 'wz', d * t.itemHeight)
}
if(isNo) return;
for(let i=0;i<t.listIndex.length;i++){
if(i>t.childrenIndex && i < dNum){
t.$set(t.listIndex[i],'itemIndex',0)
t.$set(t.listIndex[i],'wz',0)
}
}
t.chulisdata();
}, 10);
}
},
}
</script>
<style lang="scss" scoped>
.tm-pickersView {
.tm-pickersView-wk {
position: relative;
.tm-pickersView-showbg {
position: absolute;
left: 0;
height: 0;
z-index: 5;
.tm-pickersView-item {
.tm-pickersView-item-text {
text {
transition: all 0.5s;
}
&.textLevel_1 {
text {
font-size: 28upx !important;
}
}
&.textLevel_2 {
text {
font-size: 26upx !important;
}
}
}
}
}
.tm-pickersView-fg {
position: relative;
z-index: 3;
}
}
}
</style>
@@ -0,0 +1,78 @@
<template>
<view class="tm-position relative ">
<view class="tm-position-content">
<slot name="default"></slot>
</view>
<view :style="{
top:position_s.top==true?0:'null'+'px',
bottom:position_s.bottom==true?0:'null'+'px',
transform:`translateY(${offset[1]}px) translateX(${offset[0]}px)`,
}" class="tm-position-body absolute" :class="[
position_s.left?'flex-start':'',
position_s.right?'flex-end':'',
]" >
<slot name="position"></slot>
</view>
</view>
</template>
<script>
/**
* 定位组件
* @property {Object} position = [] 位置{top:false,left:false,right:false,bottom:false}
* @property {Array} offset = [] 默认:[0,0],偏移x,y
*/
export default {
name:"tm-position",
props:{
position:{
type:Object,
default:()=>{
return {};
}
},
offset:{
type:Array,
default:()=>{
return [0,0];
}
},
},
computed:{
position_s:function(){
let p = {
top:false,
left:false,
right:false,
bottom:false
};
return {...p,...this.position}
},
},
data() {
return {
height:0
};
},
async mounted() {
// this.$nextTick(async function(){
// let syinfo = await this.$Querey('.tm-position-content',this).catch(e=>{})
// console.log(syinfo);
// this.height = syinfo[0].height;
// })
}
}
</script>
<style lang="less">
.tm-position{
.tm-position-body{
width: 100%;
}
.tm-position-content{
}
}
</style>
+519
View File
@@ -0,0 +1,519 @@
<template>
<view class="tm-poups " @click.stop="">
<block v-if="position_sv != 'center'">
<view v-show="value==true&&position_sv != 'center'" class="tm-poup " :class="[
isFilter?'blur':'',
position_sv == 'center' ? 'tm-poup-center' : '',
position_sv !='center'?position_sv:'',
isClickbled?'isClickbled':''
]" @click.stop.prevent="overClick" @touchmove.stop.prevent="stopMove" :style="{
backgroundColor: overColor,
width:'100%',height:'100%'
}">
<!-- 内容 -->
<!-- <view class="tm-poup-wk bottom">{{ show ? 'on' : 'off'}}</view> -->
<scroll-view :animation="aniData" @click.stop.prevent="" class="tm-poup-wk dhshiKa" scroll-y="true" :class="[
position_sv == 'top'?'round-b-' + round:'',
position_sv == 'bottom'?'round-t-' + round:'',
position_sv, aniOn ? 'on ' : 'off',
black_tmeme ? 'grey-darken-5 bk' : bgColor
]" :style="{
width: (position_sv == 'top' || position_sv == 'bottom') ? '100%' : width_w,
height: position_sv == 'right' || position_sv == 'left' ?'100%' : height_h,
}">
<view :class="[clssStyle]" >
<slot></slot>
</view>
</scroll-view>
</view>
<view class="bottomHeight"></view>
</block>
<view v-if="value===true&&position_sv == 'center'" class="tm-poup " :class="[
isFilter?'blur':'',
position_sv == 'center' ? 'tm-poup-center' : ''
]" @click="overClick" @touchmove.stop.prevent="stopMove" :style="{
backgroundColor: overColor,
width:sysInfo.screenWidth+'px',height:'100%'
}">
<!-- 内容 -->
<scroll-view :animation="aniData" @click.stop.prevent="" class="tm-poup-wk " scroll-y="true" :class="[
`round-${round}`,aniOn ? 'on' : 'off', position_sv,
black_tmeme ? 'grey-darken-5 bk' : bgColor
]" :style="{
width: width_w,
height: height_h
}">
<view :class="[clssStyle]">
<slot></slot>
</view>
</scroll-view>
</view>
</view>
</template>
<script>
/**
* poup弹出层
* @description poup弹出层,上下,左右方向。
* @property {Boolean} value = [true|false] 使用时value.sync可同步,也可不同步。等同于v-model
* @property {Boolea} v-model 显示和关闭。
* @property {String} position = [bottom|top|left|right|center] 方向可选bottom,left,right,top,center
* @property {Function} change 改变时会调用此函数,参数e等同于v-model和value
* @property {String|Number} width 宽,位置为left,right是起作用。可以是30%或者数字(单位upx)
* @property {String|Number} height 宽,位置为top,bottom是起作用。可以是30%或者数字(单位upx)
* @property {String|Number} round 圆角0-25
* @property {String|Boolean} black = [true|false] 暗黑模式
* @property {Boolean} over-close = [true|false] 是否点击遮罩关闭。
* @property {Boolean} is-filter = [true|false] 是否背景模糊
* @property {String} clss-style = [] 自定内容的类
* @property {String} bg-color = [white|blue] 默认:white,白色背景;请填写背景的主题色名称。
* @property {String} over-color = [] 默认:rgba(0,0,0,0.3), 遮罩层颜色值不是主题。
* @example <tm-poup height="85%" v-model="show"></tm-poup>
*/
export default {
name: 'tm-poup',
props: {
bgColor: {
type: String,
default: 'white'
},
// 遮罩层颜色。
overColor: {
type: String,
default: 'rgba(0,0,0,0.3)'
},
black: {
type: Boolean | String,
default: null
},
clssStyle: {
type: String,
default: ''
},
value: {
type: Boolean,
default: false
},
// bottom,left,right,top
position: {
type: String,
default: 'bottom'
},
round: {
type: String | Number,
default: '10'
},
width: {
type: String | Number,
default: '30%'
},
height: {
type: String | Number,
default: 220
},
overClose: {
type: Boolean,
default: true
},
isFilter: {
type: Boolean,
default: true,
},
//允许穿透背景遮罩。
isClickbled: {
type: Boolean,
default: false
}
},
model: {
prop: 'value',
event: 'input',
sysInfo: {},
},
watch: {
value:function(val){
this.$emit('change', val);
if(val){
this.open()
}else{this.close()}
},
position: function() {
this.position_sv = this.position
}
},
created() {
this.sysInfo = uni.getSystemInfoSync();
},
computed: {
black_tmeme: function() {
if (this.black !== null) return this.black;
return this.$tm.vx.state().tmVuetify.black;
},
width_w: function() {
let w = this.$TestUnit(this.width);
let i = w.value;
if (w.type == 'number') {
i = w.value + 'px';
}
return i;
},
height_h: function() {
let w = this.$TestUnit(this.height);
let i = w.value;
if (w.type == 'number') {
i = w.value + 'px';
}
return i;
},
},
data() {
return {
aniOn: false,
closeTimid: null,
position_sv: this.position,
dhshiKa:true,//是否结束动画
aniData:null,
timdiiid:6369784254,
};
},
deactivated() {
clearTimeout(this.closeTimid)
},
destroyed() {
clearTimeout(this.closeTimid)
},
mounted() {
if(this.value){
this.open()
}
},
methods: {
overClick() {
if (!this.overClose) return;
this.close();
},
close() {
let t = this;
clearTimeout(this.timdiiid)
this.dhshiKa=false;
t.aniOn=false;
this.createBtT(this.position_sv,'off').then(()=>{
t.$emit('input', false);
t.closeTimid = null;
t.dhshiKa = true;
// t.$emit('change', false);
// console.log('off');
})
},
open() {
let t = this;
clearTimeout(this.timdiiid)
this.dhshiKa=false
this.aniOn=true;
this.createBtT(this.position_sv,'on').then(()=>{
t.dhshiKa=true
t.isclick=false
// console.log('on');
})
},
//下至上。
createBtT(pos,type){
let t = this;
this.aniData = '';
let aniData = uni.createAnimation({
duration:240,
timingFunction: 'linear',
})
this.aniData = aniData;
if(pos=='bottom'){
if(type=='on'){
aniData.translateY('0%').step();
this.aniData = aniData.export()
}
if(type=='off'){
aniData.translateY('100%').step();
this.aniData = aniData.export()
}
}else if(pos=='top'){
if(type=='on'){
aniData.translateY('0%').step();
this.aniData = aniData.export()
}
if(type=='off'){
aniData.translateY('-100%').step();
this.aniData = aniData.export()
}
}else if(pos=='left'){
if(type=='on'){
aniData.translateX('0%').step();
this.aniData = aniData.export()
}
if(type=='off'){
aniData.translateX('-100%').step();
this.aniData = aniData.export()
}
}else if(pos=='right'){
if(type=='on'){
aniData.translateX('0%').step();
this.aniData = aniData.export()
}
if(type=='off'){
aniData.translateX('100%').step();
this.aniData = aniData.export()
}
}else if(pos=='center'){
if(type=='on'){
aniData.opacity(1).scale(1).step();
this.aniData = aniData.export()
}
if(type=='off'){
aniData.opacity(0).scale(0.6).step();
this.aniData = aniData.export()
}
}
return new Promise(res=>{
t.timdiiid = setTimeout(()=>{
t.aniData = null;
res();
},240)
})
},
stopMove(e) {}
}
};
</script>
<style lang="scss" scoped>
.bottomHeight{
height: var(--window-bottom);
}
.tm-poup {
position: fixed;
z-index: 452;
width: 100%;
height: 100%;
min-height: 100%;
min-width: 100%;
overflow: hidden;
top: 0;
left: 0;
&.isClickbled {
pointer-events: none;
}
&.okkk{
pointer-events: none;
}
&.blur {
backdrop-filter: blur(10px);
}
&.on {
animation: opta 1s linear;
}
&.off {
animation: opta_off 0.35s linear;
}
.tm-poup-wk {
position: absolute;
overflow: hidden;
pointer-events: auto;
// transition: all 0.3s;
&.bottom {
transform: translateY(100%);
width: 100%;
bottom: 0;
}
&.top {
top: 0;
left: 0;
width: 100%;
transform: translateY(-100%);
}
&.left {
top: 0;
transform: translateX(-100%);
border-radius: 0 !important;
left: 0;
}
&.right {
top: 0;
right: 0;
transform: translateX(100%);
border-radius: 0 !important;
}
&.center {
opacity:0;
transform: scale(0.6);
}
}
&.tm-poup-center {
display: flex;
justify-content: center;
align-items: center;
align-content: center;
.tm-poup-wk {
position: static;
}
}
}
@keyframes opta {
from {
opacity: 0.6;
}
to {
opacity: 1;
}
}
@keyframes opta_off {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
@keyframes bottomTtop {
from {
transform: translateY(100%);
}
to {
transform: translateY(0%);
}
}
@keyframes bottomTtop_off {
from {
transform: translateY(0%);
}
to {
transform: translateY(100%);
}
}
@keyframes topTbottom {
from {
transform: translateY(-100%);
}
to {
transform: translateY(0);
}
}
@keyframes topTbottom_off {
from {
transform: translateY(0);
}
to {
transform: translateY(-100%);
}
}
@keyframes leftTright {
from {
transform: translateX(-100%);
}
to {
transform: translateX(0);
}
}
@keyframes leftTright_off {
from {
transform: translateX(0);
}
to {
transform: translateX(-100%);
}
}
@keyframes rightTleft {
from {
transform: translateX(100%);
}
to {
transform: translateX(0%);
}
}
@keyframes rightTleft_off {
from {
transform: translateX(0%);
}
to {
transform: translateX(100%);
}
}
@keyframes Centerleft {
from {
transform: scale(0.65);
opacity: 0.65;
}
to {
transform: scale(1);
opacity: 1;
}
}
@keyframes Centerleft_off {
from {
transform: scale(1);
opacity: 1;
}
to {
transform: scale(0.65);
opacity: 0.65;
}
}
</style>
@@ -0,0 +1,251 @@
<template>
<view class="tm-propress" :style="{
width: width_c ,
}">
<view
class="tm-propress-wk "
:style="{
height: height_upx,
overflow:loading?'hidden':'inherit'
}"
>
<view :class="[shape=='round'?'round-24':'',bgColor,black_tmeme?'bk':'']" class="tm-propress-wk-statick "></view>
<view :animation="animationData" :class="[black_tmeme?'bk':'',shape=='round'?'round-24':'',colors.theme?colors.color:'',loading?'ani':'']"
class="tm-propress-wk-active flex-end"
:style="{
height: height_upx,
width:baifenbi,
background:colors.theme?'default':colors.color
}"
>
<block v-if="value>0&&showValue">
<slot name="default" :value="value">
<view class="tm-propress-wk-active-label flex-center px-10 round-24"
:class="[black_tmeme?'bk':'',colors.theme?colors.color:'']"
:style="{background:colors.theme?'default':colors.color}">
<text>{{value}}%</text>
</view>
</slot>
</block>
</view>
</view>
</view>
</template>
<script>
/**
* 进度条
* @property {Function} change value变化时触发.
* @property {Function} input value变化时触发.
* @property {Number} value = [] 通过value来赋值进度,如果想双向绑定需要value.sync,建议使用v-model.
* @property {Number} width = [] 宽度,默认自动宽度,单位rpx,也可输入百分比或者其它类型比如5px
* @property {Number} height = [] 高度,默认6,单位upx,也可输入百分比或者其它类型比如5px
* @property {String} shape = [round|square] 默认round,方形:square,半圆形:round.
* @property {String} color = [] 默认primary,可以是16进制,rgb,rgba,主题色名称.
* @property {String} bg-color = [] 默认 grey-lighten-4 ,背景颜色
* @property {Boolean} loading = [true|false] 默认false,自动加载中...
* @example <tm-propress v-model="checked" ></tm-propress>
*/
export default {
name: 'tm-propress',
model:{
prop:"value",
event:"input"
},
props: {
// 单位upx
width: {
type: Number|String,
default: 0
},
// 单位upx
height: {
type: Number|String,
default: 8
},
value:{
type:Number,
default:0
},
showValue:{
type:Boolean|String,
default:true,
},
// 方形:square,半圆形:round.
shape:{
type:String,
default:"round"
},
color:{
type:String,
default:"primary"
},
loading:{
type:Boolean,
default:false
},
bgColor:{
type:String,
default:'grey-lighten-4'
},
// 跟随主题色的改变而改变。
fllowTheme:{
type:Boolean|String,
default:true
},
black: {
type: Boolean,
default: null
},
},
computed: {
black_tmeme: function() {
if (this.black !== null) return this.black;
return this.$tm.vx.state().tmVuetify.black;
},
colors:function(){
return this.$TestColor(this.color_tmeme);
},
color_tmeme:function(){
if(this.$tm.vx.state().tmVuetify.color!==null&&this.$tm.vx.state().tmVuetify.color && this.fllowTheme){
return this.$tm.vx.state().tmVuetify.color;
}
return this.color;
},
width_upx: {
get:function(){
return this.width_c;
},
set:function(val){
if(val==''||val==0||typeof val === 'undefined'){
this.width_c = '100%';
return;
}
let reg = /(vw|vh|rem|em|\%|upx|rpx|auto|px)/g;
if (reg.test(val)) {
this.width_c = val;
return
}
this.width_c = val+'rpx';
}
},
height_upx: function() {
let reg = /(vw|vh|rem|em|\%|upx|rpx|auto|px)/g;
if (reg.test(this.height)) {
return this.height;
}
return this.height+'rpx';
},
baifenbi:function(){
if(this.loading) return "100rpx";
let bl = this.value ;
if(typeof bl !=='number') bl =0;
if(bl>=100) bl =100;
if(bl<=0) bl =0;
this.$emit("input",bl);
this.$emit("update:value",bl);
this.$emit("change",bl);
return bl + "%"
}
},
data() {
return {
width_c:0,
animationData:"",
animation:null,
tmiddd:55656
};
},
created() {
this.width_upx = this.width;
},
destroyed() {
clearInterval(this.tmiddd)
},
async mounted() {
this.$nextTick(async function(){
let p = await this.$Querey(".tm-propress",this).catch(e=>{})
this.width_upx = p[0].width+'px';
if(this.loading){
await this.startAni();
}
})
},
methods: {
async startAni(){
// clearInterval(this.tmiddd)
let t = this;
var animation = uni.createAnimation({
duration: 1000,
timingFunction: 'linear',
})
this.animation = animation
animation.opacity(0).translateX(t.width_c).step()
this.animationData = animation.export()
this.tmiddd = setInterval(async function() {
t.animationData = ""
animation.opacity(1).translateX(0).step({duration:0})
t.animationData = animation.export()
await uni.$tm.sleep(50)
animation.opacity(0).translateX(t.width_c).step()
t.animationData = animation.export()
}.bind(this), 1000)
}
},
};
</script>
<style lang="scss" scoped>
.tm-propress {
.tm-propress-wk {
position: relative;
.tm-propress-wk-statick,
.tm-propress-wk-active {
height: 100%;
}
.tm-propress-wk-active {
position: absolute;
top: 0;
left: 0;
transition:0.35s;
.tm-propress-wk-active-label{
height: 30rpx;
font-size:22rpx;
max-width: 100rpx;
}
// &.ani{
// transition:1s;
// animation-name: linef;
// animation-duration: 1s;
// animation-timing-function: linear;
// animation-iteration-count: infinite;
// }
}
}
}
@keyframes linef{
0%{
// left:0;
transform: translateX(0);
opacity: 0.4;
}
100%{
// left:100%;
transform: translateX(311px);
opacity: 1;
}
}
</style>
@@ -0,0 +1,345 @@
<template>
<view class="tm-propressRound d-inline-block">
<view class="tm-propressRound-text" :style="{
width:size+'px',
height:size+'px'
}">
<slot name="default" :value="value">
<text class="text-black" :class="[black?'text-white':'']">{{value}}%</text>
</slot>
</view>
<view class="tm-propressRound-rk">
<!-- #ifdef MP-WEIXIN || MP-ALIPAY -->
<canvas :canvas-id="cid" :id="cid"
:style="{
width:size+'px',
height:size+'px'
}"
type="2d"
></canvas>
<!-- #endif -->
<!-- #ifndef MP-WEIXIN || MP-ALIPAY -->
<canvas :canvas-id="cid" :id="cid"
:style="{
width:size+'px',
height:size+'px'
}"
></canvas>
<!-- #endif -->
</view>
</view>
</template>
<script>
/**
* 环形进度条
* @property {Number} value = [] 默认0,推荐使用v-model赋值。value.sync等同。
* @property {Number} size = [] 默认100,圆环大小。
* @property {Number} line-width = [] 默认6,圆环线条宽。
* @property {Array} color = [] 默认['#FF9800','#E91E63','#FF5722'],激活颜色
* @property {String} bgcolor = [] 默认'#EEEEEE',背景颜色
* @property {Boolea} semicircle = [] 默认 false,是否半圆
* @property {Function} input 变化时的值。
* @property {Function} change 变化时的值。
* @example <tm-propressRound v-model="checked"></tm-propressRound>
*/
export default {
name:"tm-propressRound",
model:{
prop:"value",
event:"input"
},
props:{
black:{
type:Boolean,
default:false
},
size:{
type:Number,
default:100
},
lineWidth:{
type:Number,
default:10
},
value:{
type:Number,
default:0
},
color:{
type:Array,
default:()=>{
return ['rgb(255,152,0)','rgb(233,30,99)','rgb(255,87,34)']
}
},
bgcolor:{
type:String,
default:'rgb(238,238,238)'
},
semicircle:{
type:Boolean|String,
default:false
}
},
data() {
return {
ctx:null,
timid:null,
jishu:0,
cid:'fdd_psd',
is2d:false
};
},
computed:{
},
created() {
// #ifdef H5 || APP-VUE || APP-PLUS
this.cid = uni.$tm.guid();
// #endif
// #ifdef MP-WEIXIN || MP-ALIPAY
this.is2d=true;
// #endif
},
mounted() {
this.$nextTick(function(){
this.inits();
})
},
destroyed() {
clearInterval(this.timid)
},
watch:{
value:function(newval,oldval){
if(newval < 0){
this.$emit("input",0)
this.$emit("update:value",0)
this.$emit("change",0)
return;
}else if(newval >100){
this.$emit("input",100)
this.$emit("update:value",100)
this.$emit("change",100)
return;
}
this.$emit("input",newval)
this.$emit("update:value",newval)
this.$emit("change",newval)
let blv = newval - oldval;
if(newval>=100){
blv = 100 - oldval;
}
if(blv >= 0 ){
this.startHuiZhi(blv,oldval,true)
}else{
if(newval<=0){
blv = oldval;
}
this.startHuiZhi(Math.abs(blv),oldval,false)
}
}
},
methods: {
inits(){
let t = this;
if(this.is2d){
const query = wx.createSelectorQuery().in(this)
query.select('#'+this.cid)
.node((res) => {
const canvas = res.node
const ctx = canvas.getContext('2d')
const dpr = uni.getSystemInfoSync().pixelRatio
canvas.width = res.node._width * dpr
canvas.height = res.node._height * dpr
ctx.scale(dpr, dpr)
t.ctx = ctx;
t.creatShape();
this.startHuiZhi(this.value,0,true)
})
.exec()
}else{
this.ctx = uni.createCanvasContext(this.cid,this)
this.creatShape();
this.startHuiZhi(this.value,0,true)
}
},
startHuiZhi(val,oldv,type){
clearInterval(this.timid);
let i = 0;
let t = this;
// #ifdef H5
this.timid = setInterval(()=>{
i += 1;
if(i>=val){
clearInterval(t.timid);
return;
}
if(type){
t.huizhired(oldv+i,type);
}else{
t.huizhired(oldv-i,type);
}
},5)
// #endif
// #ifndef H5
if(type){
t.huizhired(val+oldv,type);
}else{
t.huizhired(oldv-val,type);
}
// #endif
},
// 重新绘制。
creatShape() {
if(!this.ctx) return;
let ctx= this.ctx;
let x,y,r ;
x = y = this.size / 2;
r = (this.size - this.lineWidth*2) / 2
if(this.is2d){
ctx.beginPath()
ctx.lineCap='round';
ctx.lineWidth=this.lineWidth-1;
ctx.strokeStyle=this.bgcolor;
if(this.semicircle){
ctx.arc(x, y, r, Math.PI, 0)
}else{
ctx.arc(x, y, r, -90, 2 * Math.PI)
}
ctx.stroke()
}else{
ctx.beginPath()
ctx.setLineCap('round')
ctx.setLineWidth(this.lineWidth-1)
ctx.setStrokeStyle(this.bgcolor)
if(this.semicircle){
ctx.arc(x, y, r, Math.PI, 0)
}else{
ctx.arc(x, y, r, -90, 2 * Math.PI)
}
ctx.stroke()
ctx.draw(true)
}
},
huizhired(end=0,type){
if(!this.ctx) return;
let ctx = this.ctx;
if(end>=100) end = 100;
if(end<=0) end = 0;
let bl = end * 3.6;
bl = bl-90
let x,y,r ;
x = y = this.size / 2;
r = (this.size - this.lineWidth*2) / 2
if(this.is2d){
ctx.beginPath()
ctx.lineCap='round';
ctx.lineWidth=type?this.lineWidth-1:this.lineWidth+1;
ctx.strokeStyle=this.bgcolor
if(this.semicircle){
ctx.arc(x, y, r, Math.PI, 0)
}else{
ctx.arc(x, y, r, -90, 2 * Math.PI)
}
ctx.stroke()
ctx.beginPath()
ctx.lineCap='round';
ctx.lineWidth=this.lineWidth
var grd=ctx.createLinearGradient(x,0,0,2*x);
let colorl = this.color.length;
if(colorl==1){
grd.addColorStop(1,this.color[0]);
}else if(colorl>1){
for(let i=0;i<colorl;i++){
grd.addColorStop(i/(colorl-1),this.color[i]);
}
}
ctx.strokeStyle=grd
if(this.semicircle){
let val = this.value;
val = val <=0?0:val;
val = val>=100?100:val;
bl = -(Math.PI-this.value/100 * Math.PI)
ctx.arc(x, y, r, Math.PI, bl)
}else{
ctx.arc(x, y, r, -90 * (Math.PI / 180), bl * (Math.PI / 180))
}
ctx.stroke()
}else{
ctx.beginPath()
ctx.setLineCap('round')
ctx.setLineWidth(type?this.lineWidth-1:this.lineWidth+1)
ctx.setStrokeStyle(this.bgcolor)
if(this.semicircle){
ctx.arc(x, y, r, Math.PI, 0)
}else{
ctx.arc(x, y, r, -90, 2 * Math.PI)
}
ctx.stroke()
ctx.draw(true)
ctx.beginPath()
ctx.setLineCap('round')
ctx.setLineWidth(this.lineWidth)
var grd=ctx.createLinearGradient(x,0,0,2*x);
let colorl = this.color.length;
if(colorl==1){
grd.addColorStop(1,this.color[0]);
}else if(colorl>1){
for(let i=0;i<colorl;i++){
grd.addColorStop(i/(colorl-1),this.color[i]);
}
}
ctx.setStrokeStyle(grd)
if(this.semicircle){
let val = this.value;
val = val <=0?Math.PI:val;
val = val>=100?100:val;
bl = -(Math.PI-this.value/100 * Math.PI)
bl = Math.abs(bl)>=Math.PI?Math.PI:bl;
ctx.arc(x, y, r, Math.PI, bl)
}else{
ctx.arc(x, y, r, -90 * (Math.PI / 180), bl * (Math.PI / 180))
}
ctx.stroke()
ctx.draw(true)
}
},
},
}
</script>
<style lang="scss" scoped>
.tm-propressRound{
position: relative;
.tm-propressRound-text{
position: absolute;
display: flex;
justify-content: center;
align-items: center;
align-content: center;
}
}
</style>
@@ -0,0 +1,222 @@
<template>
<view class="tm-pullBottom">
<scroll-view
class="tm-pullBottom-sroll"
:refresher-enabled="disabled"
:refresher-threshold="pullY"
:refresher-triggered="isRefresh"
:scroll-y="true"
refresher-default-style="none"
:lower-threshold="bottomY"
@scrolltolower="pullBottom"
@refresherpulling="onPulling"
@refresherrefresh="onRefresh"
@refresherrestore="onRestore"
@refresherabort="onAbort"
:style="{
height:activeHeight+'px'
}">
<view v-if="bottomLoadding==true" class="tm-pullBottom-top flex-center flex-col">
<view v-if="loading">
<view>
<slot name="pull" v-if="isPullDown==true&&showScrollPic">
<view class="tm-pullBottom-top-icon flex-center pull"
>
<tm-icons :color="color" name="icon-long-arrow-up"></tm-icons>
</view>
<view class="flex-center tm-pullBottom-top-text text-size-n"
:class="[`text-${color}`]">继续下拉刷新</view>
</slot>
<slot name="pullReresh" v-if="isPullDown==false&&showScrollPic">
<view class="tm-pullBottom-top-icon flex-center "
>
<tm-icons :color="color" name="icon-long-arrow-up"></tm-icons>
</view>
<view class="flex-center tm-pullBottom-top-text text-size-n"
:class="[`text-${color}`]" >松开刷新</view>
</slot>
</view>
<tm-loadding v-if="showScrollPic==false" ></tm-loadding>
</view>
</view>
<slot name="default"></slot>
<tm-loadding v-if="bottomLoadding==false&&loading" ></tm-loadding>
</scroll-view>
</view>
</template>
<script>
/**
* 上拉触底刷新
* @property {String|Number} height = [] 默认0,默认0使用父高度。
* @property {Number} pullY = [] 默认80,下拉多长的距离执行刷新。
* @property {Number} bottomY = [] 默认0,离底部多高度多少执行加载
* @property {String} color = [] 默认primary,主题色。
* @property {Boolean} loading = [] 默认 false,,需要loading.sync执行双向绑定,加载状态,true,加载中。false加载完成。
* @property {Boolean} disabled = [] 默认 true,,是否启用下拉刷新,不影响触底刷新功能。
* @property {Boolean} finish = [] 默认 false,是否数据没有了。如果为true,触底后将不会触发刷新操作。
* @property {Function} refresh 当下拉或者触底时,触发此函数。参数e=pull为下拉刷新,bottom为触底刷新。
* @example <tm-pullBottom :loading.sync="loading" @refresh="getdata"></tm-pullBottom>
*/
import tmIcons from "@/tm-vuetify/components/tm-icons/tm-icons.vue"
import tmLoadding from "@/tm-vuetify/components/tm-loadding/tm-loadding.vue"
export default {
components:{tmIcons,tmLoadding},
name:"tm-pullBottom",
props:{
// 高度,默认为0时,自动使用父组件的高度.
height: {
type: String | Number,
default: 0
},
pullY:{
type: Number,
default: 80
},
bottomY:{
type: Number,
default: 0
},
color: {
type: String,
default: 'primary'
},
loading:{
type:Boolean,
default:false
},
// 是否没有更多数据了。
finish:{
type:Boolean,
default:false
},
disabled:{
type:Boolean,
default:true
}
},
watch:{
loading:function(newval){
if(newval==false){
// 结束操作。
this.susscess();
}
},
},
data() {
return {
activeHeight: 0,
isRefresh:false,//是否触发了下拉刷新区域。
freshing:false,//是否刷新 中。
showScrollPic:false,//是否拖动了下拉区域,显示图标。停止不显示。
isPullDown:false,//是否下正确的下拉刷新区域。
bottomLoadding:true,//当前是底部还是顶部刷新操作。false为底部。true为顶部。
};
},
computed:{
},
mounted() {
this.guid = uni.$tm.guid();
let t = this;
this.$nextTick(async function() {
this.activeHeight = uni.upx2px(this.height);
if (!this.activeHeight) {
let wsz = await this.$Querey(".tm-pullBottom",this).catch(e=>{})
this.activeHeight = wsz[0].height||150;
}
if(this.loading===true){
this.$emit('update:loading',false)
}
});
},
methods: {
onRefresh(e) {
let t = this;
this.isRefresh = true;
setTimeout(function() {
t.isRefresh = false;
t.showScrollPic = false;
if(t.freshing==true) return;
t.freshing = true;
t.pullChangeOk();
}, 200);
},
onPulling(e){
if(this.loading === false){
this.$emit('update:loading',true)
}
this.bottomLoadding = true;
this.showScrollPic = true;//显示刷新 图标。
let dy = e.target.dy || 0
// #ifdef H5
dy = e.target.deltaY;
// #endif
if(dy < this. pullY){
this.isPullDown = true;
}else{
this.isPullDown = false;
}
},
onAbort(e){
this.$emit('update:loading',false)
this.showScrollPic = false;//显示刷新 图标。
},
onRestore(e){
this.isRefresh = 'restore'; // 需要重置
},
// 结束操作。
susscess(){
this.freshing = false;
this.$emit('update:loading',false)
},
pullChangeOk(){
this.$emit("refresh",'pull')
},
// 触底加载中。
pullBottom(){
if(this.finish) return;
this.bottomLoadding = false;
if(this.loading === false){
this.$emit('update:loading',true)
this.$emit("refresh",'bottom')
}
},
},
}
</script>
<style lang="scss" scoped>
.tm-pullBottom{
height: 100%;
.tm-pullBottom-sroll{
position: relative;
.tm-pullBottom-top{
width: 100%;
position: relative;
.tm-pullBottom-top-icon{
transition: all 0.5s;
&.pull{
transform: rotate(180deg);
}
}
}
}
}
</style>
@@ -0,0 +1,624 @@
<template>
<view class="tm-quickCity fulled">
<view @click="qiehuan"><slot ></slot></view>
<view @click.stop="qiehuan" v-if="showbgOpen" class="tm-quickCity-fullbg fulled fixed b-0 l-0" :style="{height:sysHeight+'px'}">
<view :style="{height:(height+250+content_jian_height)+'rpx'}" @click.stop="" :class="[black_tmeme?'grey-darken-5 bk':'white']" class=" tm-quickCity-wk absolute fulled round-t-10" >
<view class="tm-quickCity-title px-32 pt-32 shadow-4 relative">
<view class="text-size-g text-weight-b mb-32 relative">
<view>
请选择城市位置
</view>
<view @click.stop="qiehuan" class="tm-calendar-close rounded flex-center" :class="black_tmeme?'grey-darken-3':'grey-lighten-3'">
<tm-icons dense name="icon-times" size="24" :color="black_tmeme?'white':'grey'"></tm-icons>
</view>
</view>
<view class="flex-start pb-24">
<view :class="[pageIndex==0?`outlined ${color_tmeme} text-weight-b`:'outlined grey',black_tmeme?'bk':'']" @click="indexChange(0)" class=" px-12 py-6 round-10 flex-center text-size-s mr-24 flex-shrink">
<text>{{childrenIndx[0]!==null?childrenIndx[0].title:'请选择省'}}</text>
</view>
<view v-if="childrenIndx[0]!==null" class="flex-center pr-24">
<tm-icons :color="black_tmeme?'white':'grey'" :size="24" dense name="icon-angle-right"></tm-icons>
</view>
<view style="max-width: 120rpx;" :class="[pageIndex==1?`outlined ${color_tmeme} text-weight-b`:'outlined grey',black_tmeme?'bk':'']" @click="indexChange(1)" v-if="childrenIndx[0]!==null" class="px-12 py-6 round-10 flex-center text-size-s mr-24 pb-10 ">
<text class="text-overflow">{{childrenIndx[1]!==null?childrenIndx[1].title:'请选择市'}}</text>
</view>
<view v-if="childrenIndx[1]!==null" class="flex-center pr-24">
<tm-icons :color="black_tmeme?'white':'grey'" :size="24" dense name="icon-angle-right"></tm-icons>
</view>
<view style="max-width: 220rpx;" :class="[pageIndex==2?`outlined ${color_tmeme} text-weight-b`:'outlined grey',black_tmeme?'bk':'']" @click="indexChange(2)" v-if="childrenIndx[1]!==null" class="px-12 py-6 round-10 flex-center text-size-s mr-24 pb-10 ">
<text class="text-overflow">{{childrenIndx[2]!==null?childrenIndx[2].title:'请选择县/区'}}</text>
</view>
</view>
</view>
<view v-if="hotCity_chuli.length>0" class="tm-quickCity-jg py-24">
<view :class="[black_tmeme?'grey-darken-5':'grey-lighten-4 text']" class="text-size-s text-weight-b py-12 px-32">热门城市</view>
<view class="px-20 pt-12">
<tm-tags model="text" rounded :black="black_tmeme" @click="selectedDefaultHot(index)" v-for="(item,index) in hotCity_chuli" :key="index" color="white" :fllowTheme="fllowTheme">
{{item[1]?item[1]:item[0]}}
</tm-tags>
</view>
</view>
<view v-show="pageIndex==0">
<tm-quickIndex :black="black_tmeme" :fllowTheme="false" :color="color_tmeme" key="ref_k_1" v-model="active" :height="content_height" :list="list">
<template v-slot:cell="{data}">
<view @click="cityClick_pr(data,0)" :class="[data.black?'bk':'',data.children!==data.total-1?'border-grey-lighten-4-b-1':'']" class="py-24 mx-32 flex-between">
<view class="text-size-n flex-start" >
<view v-if="data.item['checked']" class="mr-24">
<tm-icons :color="data.color" dense name="icon-check"></tm-icons>
</view>
<text :class="[data.item['checked']?'text-'+data.color+' text-weight-b':'']" class="text-size-n">
{{data.title}}
</text>
</view>
</view>
</template>
</tm-quickIndex>
</view>
<view v-if="pageIndex==1">
<tm-quickIndex :black="black_tmeme" :fllowTheme="false" :color="color_tmeme" key="ref_k_2" v-model="active_2" v-if="childrenIndx[0]" :height="content_height" :list="childrenIndx[0].children">
<template #cell="{data}">
<view @click="cityClick_pr(data,1)" :class="[data.black?'bk':'',data.children!==data.total-1?'border-grey-lighten-4-b-1':'']" class="py-24 mx-32 border-grey-lighten-4-b-1 flex-between">
<view class="text-size-n flex-start" >
<view v-if="data.item['checked']" class="mr-24">
<tm-icons :color="data.color" dense name="icon-check"></tm-icons>
</view>
<text :class="[data.item['checked']?'text-'+data.color+' text-weight-b':'']" class="text-size-n">
{{data.title}}
</text>
</view>
</view>
</template>
</tm-quickIndex>
</view>
<view v-if="pageIndex==2">
<tm-quickIndex :black="black_tmeme" :fllowTheme="false" :color="color_tmeme" key="ref_k_3" v-model="active_3" v-if="childrenIndx[1]" :height="content_height" :list="childrenIndx[1].children">
<template #cell="{data}">
<view @click="cityClick_pr(data,2)" :class="[data.black?'bk':'',data.children!==data.total-1?'border-grey-lighten-4-b-1':'']" class="py-24 mx-32 border-grey-lighten-4-b-1 flex-between">
<view class="text-size-n flex-start" >
<view v-if="data.item['checked']" class="mr-24">
<tm-icons :color="data.color" dense name="icon-check"></tm-icons>
</view>
<text :class="[data.item['checked']?'text-'+data.color+' text-weight-b':'']" class="text-size-n">
{{data.title}}
</text>
</view>
</view>
</template>
</tm-quickIndex>
</view>
</view>
</view>
</view>
</template>
<script>
/**
* 城区索引选择
* @property {Boolean} value = [true|false] 显示和隐藏组件,请使用v-model或者value.sync你也可以不提供些参数,使用默认插槽也可打开,请见示例。
* @property {Array} default-value = [] 默认选择的地址:[],字符串数组,如["江西省","南昌市"]。使用.sync双向绑定。
* @property {Array} hot-city = [] 默认选择的地址:[],格式同default-value
* @property {String} color = [] 默认:primary,主题色
* @property {Number} height = [] 默认:1000,组件高度,单位rpx
* @property {Boolean} black = [true|false] 默认:false,是否深色模式
* @property {Function} change 选中地址时触发,返回当前选中的地区数组。
*/
import provinceData from '@/tm-vuetify/tool/util/province.js';
import cityData from '@/tm-vuetify/tool/util/city.js';
import areaData from '@/tm-vuetify/tool/util/area.js';
import queryCnChart from "@/tm-vuetify/tool/function/findCnChart.js";
import tmQuickIndex from "@/tm-vuetify/components/tm-quickIndex/tm-quickIndex.vue"
import tmListitem from "@/tm-vuetify/components/tm-listitem/tm-listitem.vue"
import tmGrouplist from "@/tm-vuetify/components/tm-grouplist/tm-grouplist.vue"
import tmIcons from "@/tm-vuetify/components/tm-icons/tm-icons.vue"
import tmTags from "@/tm-vuetify/components/tm-tags/tm-tags.vue"
export default {
components:{tmQuickIndex,tmListitem,tmGrouplist,tmIcons,tmTags
},
name:"tm-quickCity",
model:{
event:'input',
prop:'value'
},
props:{
defaultValue:{
type:Array,
default:()=>{
return [];
}
},
hotCity:{
type:Array,
default:()=>[["江西省","南昌市"],["北京市"],["广东省","广州市"],["浙江省","杭州市"],["重庆市"],["湖南省","长沙市"],["湖北省","武汉市"],["四川省","成都市"]]
},
color:{
type:String,
default:"primary"
},
height:{
type:String|Number,
default:900
},
value:{
type:Boolean,
default:false
},
black:{
type:Boolean,
default:null
},
// 跟随主题色的改变而改变。
fllowTheme:{
type:Boolean|String,
default:true
}
},
data() {
return {
list:[],
active:0,
active_2:0,
active_3:0,
childrenIndx:[null,null,null],
indexobj:{
pr:null,
ar:null,
co:null
},
pageIndex:0,
sysHeight:0,
showbgOpen:false,
content_height:0,
content_jian_height:0,
hotCity_chuli:[]
};
},
watch:{
value:function(val){
this.show = val;
}
},
destroyed() {
this.list=[];
this.childrenIndx=[];
},
computed:{
black_tmeme: function() {
if (this.black !== null) return this.black;
return this.$tm.vx.state().tmVuetify.black;
},
color_tmeme:function(){
if(this.$tm.vx.state().tmVuetify.color!==null&&this.$tm.vx.state().tmVuetify.color && this.fllowTheme){
return this.$tm.vx.state().tmVuetify.color;
}
return this.color;
},
show:{
get:function(){
return this.showbgOpen;
},
set:function(val){
this.showbgOpen = val;
this.$emit('input',val);
this.$emit('update:value',val);
}
}
},
async mounted() {
this.sysHeight = uni.getSystemInfoSync().windowHeight;
this.show = this.value;
this.content_height = this.height;
this.hotCity_chuli_fun();
await this.chiliFormatCity_area()
await this.setDefaultValue();
await this.content_heightToh();
},
methods: {
hotCity_chuli_fun(){
let hot = uni.$tm.deepClone(this.hotCity);
let hostar = [];
hot.forEach(item=>{
if(item.length==1) hostar.push([item[0],null,null])
if(item.length==2) hostar.push([item[0],item[1],null])
if(item.length==3) hostar.push([item[0],item[1],item[2]])
})
this.hotCity_chuli = [...hostar];
},
async qiehuan(){
this.show = !this.showbgOpen
await this.content_heightToh();
},
async content_heightToh(){
// if(!this.showbgOpen) return;
if(this.hotCity_chuli.length==0){
return this.height;
}
this.$nextTick(async function(){
let h = await this.$Querey(".tm-quickCity-jg",this).catch(e=>{});
if(!h[0]) return;
h = h[0].height;
let blv = uni.upx2px(4) / 4;
this.content_jian_height = (h+h*blv)
this.content_height = parseInt(this.height - this.content_jian_height);
})
},
async selectedDefaultHot(index){
this.$emit('update:defaultValue',this.hotCity[index]);
let chl = this.childrenIndx[0];
if(chl){
this.$set(this.list[chl.index].children[chl.childrenIndex],'checked',false)
}
this.childrenIndx=[null,null,null];
this.$nextTick(async function(){
await this.setDefaultValue()
this.cityClick_pr(this.list[this.childrenIndx[0]],this.childrenIndx[0])
})
},
async setDefaultValue(){
let t = this;
function chuili(ix){
let pitem = null;
let chilIndex=-1;
let pdindex = -1;
if(ix==0&&t.defaultValue[ix]){
pdindex = t.list.findIndex((item)=>{
let citem = item.children
let index = citem.findIndex(ditem=>{
if(ditem.title == t.defaultValue[ix]){
pitem = ditem;
}
return ditem.title == t.defaultValue[ix]
})
if(index>-1){
chilIndex = index;
}
return index>-1;
})
}else if(t.defaultValue[ix]){
let dsst = t.childrenIndx[ix-1].children
pdindex = dsst.findIndex((item)=>{
let citem = item.children
let index = citem.findIndex(ditem=>{
if(ditem.title == t.defaultValue[ix]){
pitem = ditem;
}
return ditem.title == t.defaultValue[ix]
})
if(index>-1){
chilIndex = index;
}
return index>-1;
})
}
return {prevent:pdindex,chileIndex:chilIndex,item:pitem};
}
let d = chuili(0)
if(d.prevent>-1){
let chl = this.childrenIndx[0];
if(chl){
this.$set(this.list[chl.index].children[chl.childrenIndex],'checked',false)
}
t.childrenIndx.splice(0,1,{
index:d.prevent,
childrenIndex:d.chileIndex,
title:d.item.title,
children:t.chuliKey(d.item.children)
})
t.active = d.prevent;
this.$set(this.list[d.prevent].children[d.chileIndex],'checked',true)
}
if(t.defaultValue[1]){
let d = chuili(1)
let chl = this.childrenIndx[1];
if(chl){
this.$set(this.childrenIndx[0].children[chl.index].children[chl.childrenIndex],'checked',false)
}
t.childrenIndx.splice(1,1,{
index:d.prevent,
childrenIndex:d.chileIndex,
title:d.item.title,
children:t.chuliKey(d.item.children)
})
this.$set(this.childrenIndx[0].children[d.prevent].children[d.chileIndex],'checked',true)
}else{
}
await uni.$tm.sleep(50)
if(t.defaultValue[2]){
let d = chuili(2)
let chl = this.childrenIndx[2];
if(chl){
this.$set(this.childrenIndx[1].children[chl.index].children[chl.childrenIndex],'checked',false)
}
t.childrenIndx.splice(2,1,{
index:d.prevent,
childrenIndex:d.chileIndex,
title:d.item.title,
children:[]
})
this.$set(this.childrenIndx[1].children[d.prevent].children[d.chileIndex],'checked',true)
}
},
indexChange(index){
let t= this;
this.pageIndex = index;
this.childrenIndx.forEach((item,idx)=>{
if(idx===index && item!==null){
if(t.defaultValue[idx]==item.title){
if(idx==0){
t.active = item.index;
}else if(idx==1){
t.active_2 = item.index;
}else if(idx==2){
t.active_3 = item.index;
}
}
}
})
},
cityClick_pr(data_item,tindex){
if(tindex===0){
let chl = this.childrenIndx[0];
if(chl){
this.$set(this.list[chl.index].children[chl.childrenIndex],'checked',false)
}
this.childrenIndx=[{
index:data_item.prevent,
childrenIndex:data_item.children,
title:data_item.title,
children:this.chuliKey(data_item.item.children)
},null,null]
this.$set(this.indexobj,'pr',data_item.prevent)
this.$set(this.indexobj,'ar',null)
this.$set(this.indexobj,'co',null)
this.pageIndex=1;
this.$set(this.list[data_item.prevent].children[data_item.children],'checked',true)
}else if(tindex===1){
let chl = this.childrenIndx[1]
if(chl){
this.$set(this.childrenIndx[0].children[chl.index].children[chl.childrenIndex],'checked',false)
}
this.childrenIndx.splice(1,1,{
index:data_item.prevent,
childrenIndex:data_item.children,
title:data_item.title,
children:this.chuliKey(data_item.item.children)
})
this.childrenIndx.splice(2,1,null)
this.$set(this.indexobj,'pr',data_item.prevent)
this.$set(this.indexobj,'ar',data_item.prevent)
this.$set(this.indexobj,'co',null)
this.pageIndex=2;
this.$set(this.childrenIndx[0].children[data_item.prevent].children[data_item.children],'checked',true)
}else if(tindex===2){
let chl = this.childrenIndx[2]
if(chl){
this.$set(this.childrenIndx[1].children[chl.index].children[chl.childrenIndex],'checked',false)
}
this.childrenIndx.splice(2,1,{
index:data_item.prevent,
childrenIndex:data_item.children,
title:data_item.title,
children:[]
})
this.$set(this.indexobj,'pr',data_item.prevent)
this.$set(this.indexobj,'ar',data_item.prevent)
this.$set(this.indexobj,'co',data_item.prevent)
this.$set(this.childrenIndx[1].children[data_item.prevent].children[data_item.children],'checked',true)
}
let cdite = this.defaultValue;
this.childrenIndx.forEach((item,index)=>{
if(item){
cdite.splice(index,1,item.title)
}else{
cdite.splice(index,1,null)
}
})
this.$emit("update:defaultValue",cdite)
this.$emit("change",cdite)
},
//处理地区数据以便符合规范。
async chiliFormatCity_area() {
let list = [];
let list_index_Str = []
provinceData.forEach((item,index)=>{
let quick = queryCnChart(item.label[0])["0"];
if(item.label=='重庆市'){
quick = 'C'
}
list.push({
id:item.value,
text:item.label,
quick:quick,
children:[]
})
})
cityData.forEach((item,index)=>{
item.forEach((citem,cindex)=>{
list[index].children.push({
id:citem.value,
text:citem.label,
quick:queryCnChart(citem.label[0])["0"],
children:[]
})
})
})
list.forEach((item,index)=>{
item.children.forEach((citem,cindex)=>{
areaData[index][cindex].forEach(jitem=>{
list[index].children[cindex].children.push({
id:jitem.value,
quick:queryCnChart(jitem.label[0])["0"],
text:jitem.label
})
})
})
})
function sortFun(a,b){
var nameA = a.quick.toUpperCase(); // ignore upper and lowercase
var nameB = b.quick.toUpperCase(); // ignore upper and lowercase
if (nameA < nameB) {
return -1;
}
if (nameA > nameB) {
return 1;
}
// names must be equal
return 0;
}
function sort(itemArray){
if(typeof itemArray === 'object' && Array.isArray(itemArray)){
itemArray.sort(sortFun)
}else{
return itemArray;
}
for(let i=0;i<itemArray.length;i++){
let cl = itemArray[i]['children'];
if(typeof cl === 'object' && Array.isArray(cl)){
itemArray[i]['children'] = sort(cl);
}
}
return itemArray;
}
let plst = sort(list);
function fenzu(ar){
let jg = {};
for(let i=0;i<ar.length;i++){
let cl = ar[i]['children'];
if(typeof cl === 'object' && Array.isArray(cl)){
if(typeof jg[ar[i].quick] !=='undefined'){
jg[ar[i].quick].push({
title:ar[i].text,
index:ar[i].quick,
children:fenzu(cl)
})
}else{
jg[ar[i].quick] = [];
jg[ar[i].quick].push({
title:ar[i].text,
index:ar[i].quick,
children:fenzu(cl)
})
}
}else{
if(typeof jg[ar[i].quick] !=='undefined'){
jg[ar[i].quick].push({
title:ar[i].text,
index:ar[i].quick,
children:ar[i]
})
}else{
jg[ar[i].quick] = [];
jg[ar[i].quick].push({
title:ar[i].text,
index:ar[i].quick,
children:ar[i]
})
}
}
}
return jg;
}
let ruslt = fenzu(plst);
let tddd = this.chuliKey(ruslt);
this.list = tddd;
return tddd;
},
chuliKey(arritem){
let ruslt = arritem;
let zuihojg = [];
let keyarray = Object.keys(arritem);
for(let i=0;i<keyarray.length;i++){
zuihojg.push({
title:keyarray[i],
index:keyarray[i],
children:ruslt[keyarray[i]]
})
}
return zuihojg;
}
},
}
</script>
<style lang="scss" scoped>
.tm-quickCity{
.tm-quickCity-fullbg{
background-color: rgba(0,0,0,0.3);
z-index: 501;
.tm-quickCity-wk{
bottom: 0;
left: 0;
.tm-quickCity-title{
z-index: 10;
.tm-calendar-close {
position: absolute;
top: 0rpx;
right: 0rpx;
height: 50rpx;
line-height: 50rpx;
width: 50rpx;
}
}
}
}
}
</style>
@@ -0,0 +1,328 @@
<template>
<view class="tm-quickIndex " :style="{
height: activeHeight_watch + 'px'
}">
<view :style="{
height: activeHeight_watch + 'px'
}">
<tm-loadding v-if="loadding" label="处理中..."></tm-loadding>
<scroll-view scroll-y :class="[black_tmeme?'grey-darken-4':'white']" :style="{
height: activeHeight_watch + 'px',
}" @scroll="scrollIn" :scroll-into-view="guid+'_'+(isScroll?'':active_value)">
<view v-for="(item,index) in dataList" :key="index" :id="guid+'_'+index" class="tm-quickIndex-item">
<view :class="[black_tmeme?'grey-darken-5':'grey-lighten-4 text']" class=" text-size-s text-weight-b px-32 py-12">{{item.title}}</view>
<view>
<view v-for="(item2,index2) in item.children" :key="index2">
<slot name="cell" :data="{prevent:index,children:index2,total:item.children.length,item:item2,title:item2[rangKey],color:color_tmeme,black:black_tmeme}">
<view :class="[index2!==item.children.length-1?'border-grey-lighten-4-b-1 ':'',black_tmeme?'bk':'']" class="mx-32 py-24 flex-start" @click="changeIndex(index,index2,item2)">
<view v-if="item2['icon']" style="width: 48rpx;height: 48rpx;" class="mr-24 rounded flex-center overflow">
<tm-icons :size="48" :name="item2['icon']"></tm-icons>
</view>
<view class="text-size-n">
{{item2[rangKey]}}
</view>
</view>
</slot>
</view>
</view>
</view>
</scroll-view>
</view>
<view class="tm-quickIndex-index flex-center flex-col pr-16" :style="{
height: activeHeight_watch + 'px'
}">
<view v-if="showtips"
:class="[`text-${color_tmeme}`,black_tmeme?'bk':'']"
class="tm-quickIndex-index-Tips absolute rounded shadow-10 flex-center white text-size-g text-weight-b">
{{ returnIndexStr(scrollIndx) }}
</view>
<!-- <view v-if="scrollInBarIndex"
:class="[`text-${color}`]"
class="tm-quickIndex-index-Tips absolute rounded shadow-10 flex-center white text-size-g text-weight-b">
{{returnIndexStr(scrollIndx)}} :class="[scrollIndx==index?`text-${color} text-weight-b`:'']"
</view> -->
<view v-if="activeHeight_watch>0" @touchend.stop.prevent="indexMove($event,'end')" @touchmove.stop.prevent="indexMove($event,'scroll')"
class="tm-quickIndex-index-Bk round-24 shadow-3 " :class="[black_tmeme?'grey-darken-5 bk':'white']">
<view @click.stop="acitveItemClick($event,index)"
class="tm-quickIndex-index-item text-size-xxs flex-center px-2"
v-for="(item,index) in dataList" :key="index">
{{
returnIndexStr(index)
}}
</view>
</view>
</view>
</view>
</template>
<script>
/**
* 快速索引
* @property {Array} list = [] 默认:[],列表数据,格式:[{title:"汽车品牌",children:[{title:"宝马"},{title:"奔驰"}]}]
* @property {String} rang-key = [] 默认:'title',列表对象key
* @property {String | Number} height = [] 默认:0,高度,默认为0时,自动使用屏幕的高度。
* @property {Number} value = [] 默认:0,当前滚动的索引位置,推荐使用v-model或者value.sync
* @property {String} color = [] 默认:primary,主题色。
* @property {Function} change 点击列表项时产生的事件,返回参数:{prent:父Index,children:子index,data:项数据。}
* @example <tm-quickIndex :list='[{title:"汽车品牌",children:[{title:"宝马"},{title:"奔驰"}]}]'></tm-quickIndex>
* 如果 不提供index索引字符将截取title第一个字符作为索引。如果title第一个没有将使用自建的数字索引。
*/
import tmIcons from "@/tm-vuetify/components/tm-icons/tm-icons.vue"
import tmLoadding from "@/tm-vuetify/components/tm-loadding/tm-loadding.vue"
export default {
components:{tmIcons,tmLoadding},
name: 'tm-quickIndex',
model: {
prop: 'value',
event: 'input'
},
props: {
// 高度,默认为0时,自动使用屏幕的高度。
height: {
type: String | Number,
default: 0
},
// 当前滚动的位置。
value: {
type: Number,
default: 0
},
// 当前滚动的位置。
color: {
type: String,
default: "primary"
},
list: {
type: Array,
default: () => {
return [];
}
},
rangKey: {
type: String,
default: "title"
},
black: {
type: String|Boolean,
default: null
},
// 跟随主题色的改变而改变。
fllowTheme:{
type:Boolean|String,
default:true
}
},
watch: {
value: function() {
this.active = this.value;
this.isScroll=false;
this.scrollIndx = this.value;
},
list:{
deep:true,
handler(){
this.dataList = this.list;
}
}
},
computed: {
black_tmeme: function() {
if (this.black !== null) return this.black;
return this.$tm.vx.state().tmVuetify.black;
},
color_tmeme:function(){
if(this.$tm.vx.state().tmVuetify.color!==null&&this.$tm.vx.state().tmVuetify.color && this.fllowTheme){
return this.$tm.vx.state().tmVuetify.color;
}
return this.color;
},
active: {
get: function() {
return this.active_value;
},
set: async function(val) {
this.active_value = val;
this.$emit('input', val);
this.$emit('update:value', val);
let t = this;
this.showtips = true;
let idx = 5655555
clearTimeout(idx)
idx = setTimeout(function(){
t.showtips = false;
},500)
}
},
activeHeight_watch: {
get: function() {
return this.activeHeight;
},
set: function(val) {
this.activeHeight = val;
}
}
},
data() {
return {
minTop:0,
activeHeight: 0,
guid: "",
active_value: 0,
listBound: [],
nowIndex: 0,
showtips: false,
isScroll: true,
quinkBar: null,
scrollIndx: 0,
scrollInBarIndex: false,
dataList:[],
loadding:true
};
},
async mounted() {
this.guid = uni.$tm.guid();
let t = this;
this.activeHeight_watch = uni.upx2px(this.height);
this.loadding=true;
await uni.$tm.sleep(50)
this.dataList = [...this.list];
this.$nextTick(async function() {
if (!this.activeHeight_watch) {
let sysinfo = uni.getSystemInfoSync();
this.activeHeight_watch = sysinfo.windowHeight;
}
let df = await this.$Querey(".tm-quickIndex",this).catch(e=>{});
this.minTop = df[0].top;
let indexbar = await t.$Querey(".tm-quickIndex-index-Bk", t).catch(e => {})
t.quinkBar = indexbar[0]
await uni.$tm.sleep(100)
t.active = t.value;
uni.createSelectorQuery().in(t).selectAll('.tm-quickIndex-item')
.boundingClientRect(res => {
res.forEach(item => {
t.listBound.push(item.top)
})
t.loadding=false;
}).exec()
});
},
methods: {
returnIndexStr(index){
let item = this.list[index];
if(!item || typeof item === 'undefined') return;
if(item['index']&& typeof item['index'] !=='undefined'){
return item['index'];
}else{
if(item[this.rangKey][0]&& typeof item[this.rangKey][0] !=='undefined'){
return item[this.rangKey][0];
}
}
return index+1
},
scrollIn(e) {
let t = this;
let y = e.detail.scrollTop;
this.isScroll = true;
function chatIndex(min) {
let index = 0;
for (let i = 0; i < t.listBound.length; i++) {
if (t.listBound[i] >= min + t.minTop+1) {
index = i;
break;
}
}
return index;
}
this.nowIndex = chatIndex(y) - 1;
},
changeIndex(prentindex, childrenindex, item) {
this.$emit('change', {
prent: prentindex,
children: childrenindex,
data: item
})
},
async acitveItemClick(e, indx) {
this.isScroll = false;
if (this.list.length <= 0) return;
this.active = indx;
},
async indexMove(e, type) {
let t = this;
if (this.list.length <= 0) return;
if (e.changedTouches.length > 1) return;
let y = e.changedTouches[0].clientY;
let itemHeight = uni.upx2px(40);
let ClickTop = e.target.offsetTop;
let index = 0;
if (y <= this.quinkBar.top) {
index = 0;
} else if (y >= this.quinkBar.bottom) {
index = this.list.length - 1;
} else {
let xy = y - this.quinkBar.top
index = Math.floor(xy / itemHeight);
}
if(index>=this.list.length-1) index = this.list.length-1
if(index<=0) index = 0;
this.isScroll = false;
if(this.scrollIndx!==index){
this.scrollIndx = index
}
if(this.active!==index){
this.active = index;
}
if (type == 'end') {
t.scrollInBarIndex = false;
} else {
t.scrollInBarIndex = true;
}
}
},
};
</script>
<style lang="scss" scoped>
.tm-quickIndex {
position: relative;
.tm-quickIndex-index {
position: absolute;
right: 0upx;
top: 0;
.tm-quickIndex-index-item {
width: 40rpx;
height: 40rpx;
// background: rgba(255,255,255,0.1);
}
.tm-quickIndex-index-Tips {
right: 160rpx;
width: 100rpx;
height: 100rpx;
}
}
}
</style>
+263
View File
@@ -0,0 +1,263 @@
<template>
<view @click="onclick" class=" tm-checkbox " :class="[dense?'':'pa-20',inline?'d-inline-block ':'fulled']">
<view class="flex-start fulled">
<slot name="default" :checkData="{label:label,checked:changValue}" :on="onclick">
<view :style="{width: sizes.wk,height: sizes.wk}" class="tm-checkbox-boey relative d-inline-block"
:class="[black?'bk':'','flex-shrink mr-10 ',
changValue?'ani':'',
changValue?color_tmeme+' border-'+(borderColor||color_tmeme)+'-a-1':'border-'+(borderColor||color_tmeme)+'-a-1',
disabled?'grey-lighten-2 border-grey-lighten-1-a-1':'',
round==='rounded'?'rounded':'round-'+round]"
>
<view :class="[changValue?'ani_toMaxToMin_on':'']" class="absolute flex-center" style="width: 100%;height: 100%;">
<tm-icons dense v-show="model === 'normal'" :size="sizes.gou" :color="disabled?'opacity-5 white':'white'" :name="changValue?icon:' '"></tm-icons>
<view v-show="model === 'round'&&changValue" class=" rounded d-inline-block"
:class="[disabled?'opacity-5 white':'white']"
:style="{width: sizes.yuan,height: sizes.yuan}"></view>
</view>
</view>
</slot>
<view v-if="label" :class="[black?'bk':'','px-10 ','flex-start fulled']" :style="{minHeight: sizes.wk}" class=" tm-checkbox-boey-label ">
<view class="flex-center fulled-height ">
<slot name="label" :label="{label:label,checked:changValue}">
<text class=" text-size-n">{{label}}</text>
</slot>
</view>
</view>
</view>
</view>
</template>
<script>
/**
* 单选框
* @description 可以单独或者在tm-groupradio中使用。
* @property {Boolean} value = [true|false] 如果想双向绑定需要value.snyc等同v-model。推荐v-model.
* @property {Function} input 等同value.snyc和v-model和change
* @property {Function} change 变化是会返回 {index,checked,value:name携带的数据}
* @property {Boolean} disabled = [true|false] 默认false,禁用
* @property {String} color = [primary|blue] 默认primary,主题色名称。
* @property {String} border-color = [] 默认 '',边线主题色,默认同color可不填。
* @property {String} model = [normal|round] 默认normal, 内部:normal打勾,round:内部为圆点
* @property {String} icon = [icon-check] 默认icon-check,自定义选中时的图标。
* @property {String|Number} round = [2|rounded] 默认2, 圆角,rounded时即圆形。
* @property {String|Number} size = [] 默认32, 大小单位upx
* @property {String|Boolean} dense = [true|false] 默认false, 是否去除外间隙。
* @property {String} label = [] 默认"", 文右边显示的选项文字
* @property {String|Boolean} black = [true|false] 默认false, 暗黑模式
* @property {String|Boolean} inline = [true|false] 默认false, 是否内联模式
* @property {String | Array | Object | Number} name = [] 默认 "", 选中时携带的自定义数据,会通过change带回。
* @example <tm-radio v-model="checked" label="按个计算"></tm-radio>
*/
import tmIcons from "@/tm-vuetify/components/tm-icons/tm-icons.vue"
export default {
components:{tmIcons},
name:'tm-radio',
model:{
prop: 'value',
event: 'input'
},
props:{
//是否内联模式
inline:{
type:Boolean,
default:true
},
// 禁用。
disabled:Boolean,
// 使用时::checked.sync 需要带sync
value:Boolean,
color:{
type:String,
default:'primary'
},
borderColor: {
type: String,
default: ''
},
// 内部:normal打勾,round:内部为圆点
model:{
type:String,
default:'round'
},
// 自定义选中时的图标。
icon:{
type:String,
default:'icon-check'
},
// 外部形状:== rounded时即圆形。
round:{
type:String|Number,
default:'rounded'
},
// 单位upx
size:{
type:String|Number,
default:38
},
// 是否去除外间隙。
dense:{
type:Boolean|String,
default:false
},
// 名称。
label:{
type:String,
default:''
},
black:{
type:Boolean|String,
default:false
},
name:{
type:String|Array|Object|Number,
default:''
},
// 跟随主题色的改变而改变。
fllowTheme:{
type:Boolean|String,
default:true
}
},
data() {
return {
};
},
mounted() {
},
watch:{
value:function(newval,oldval){
if(newval !== oldval){
this.change();
}
}
},
computed:{
color_tmeme:function(){
if(this.$tm.vx.state().tmVuetify.color!==null&&this.$tm.vx.state().tmVuetify.color && this.fllowTheme){
return this.$tm.vx.state().tmVuetify.color;
}
return this.color;
},
changValue:{
get:function(){
return this.value;
},
set:function(newValue){
this.$emit('input',newValue )
// 如果不想用v-model. 直接使用value,需要:value.sync
this.$emit('update:value',newValue )
}
},
sizes:function(){
return {
wk:uni.upx2px(this.size)+'px',
gou:uni.upx2px(this.size/3*2)+'px',
yuan:uni.upx2px(this.size/2)+'px',
}
}
},
methods: {
onclick(e){
let t= this;
if(this.disabled) return;
this.changValue = true;
},
change() {
let box = [];
let selfIndex;
let __uid = this._uid;
let t = this;
function findchild(p,index){
let preat = p;
if(preat.$options?.name==='tm-radio'){
if(preat._uid!==__uid){
if(preat.changValue===true && preat.changValue === t.changValue){
preat.changValue = false;
}
}else if(preat._uid===__uid){
box.push({index:index,name:preat.name,checked:preat.changValue})
selfIndex = index;
}
}else{
if(preat.$children.length>0){
preat.$children.forEach(item=>{
findchild(item,index++);
})
}
}
};
let preat = this.$tm.getParentAls('tm-groupradio', this.$parent);
// 如果不在tm-groupradio里面将不作反选。并且始终为true.
if(preat){
findchild(preat,0);
t.$emit('change',{index:selfIndex,checked:t.changValue,name:t.name});
preat.change(box)
}else{
this.$emit('change',{index:0,checked:this.changValue,name:this.name});
}
}
},
}
</script>
<style lang="scss" scoped>
.tm-checkbox{
vertical-align: middle;
.tm-checkbox-boey,.tm-checkbox-boey-label{
vertical-align: middle;
}
.ani {
animation: ani 0.2s linear;
}
.ani_toMaxToMin_on {
animation: ani_toMaxToMin_on 0.35s linear;
}
@keyframes ani_toMaxToMin_on {
0% {
transform: scale(0.7);
opacity:0.7;
}
50% {
transform: scale(1.5)
}
100% {
transform: scale(1);
opacity:1;
}
}
@keyframes ani {
0% {
transform: scale(0.9)
}
50% {
transform: scale(1.1)
}
100% {
transform: scale(0.9)
}
}
}
</style>
+148
View File
@@ -0,0 +1,148 @@
<template>
<view class="tm-rate d-inline-block">
<view @touchstart="ishover=false" @touchend="ishover=disabled?false:true" v-for="(item,index) in num" :key="index" class="d-inline-block" :class="[ishover&&index+1==indexStar?'ani':'','pr-'+margin]">
<tm-icons :black="black_tmeme" dense @click="clicSelect(index+1)" :size="size" :color="index+1 <= indexStar?color_tmeme:uncolor"
:name="icon"></tm-icons>
</view>
<slot name="default" :num="num"><text v-if="showNum" :class="['text-'+color_tmeme]">{{indexStar}}</text></slot>
</view>
</template>
<script>
/**
* 评分
* @property {String} color = [] 默认:primary,选中的颜色
* @property {String} uncolor = [] 默认:grey-lighten-2,未选中的颜色
* @property {Number} num = [] 默认:5,数量
* @property {Number} value = [] 默认:0,当前的评分,推荐:value.sync或者v-model.
* @property {Number} size = [] 默认:32,单位upx,图标大小。
* @property {Number} margin = [] 默认:16,单位upx,间隙。
* @property {Boolean} disabled = [] 默认:false,是否禁用。
* @property {Boolean} black = [] 默认:null,暗黑模式。
* @property {Boolean} show-num = [] 默认:false,是否展示评分数字。
* @property {Boolean} icon = [] 默认:icon-collection-fill,图片名称,可以自定义其它的。
* @property {String} name = [] 默认:'',提交表单时的的字段名称标识
* @property {Function} change 评分改变时触发,参数当前的评分。
*/
import tmIcons from "@/tm-vuetify/components/tm-icons/tm-icons.vue"
export default {
components:{tmIcons},
name: "tm-rate",
model: {
prop: "value",
event: "input"
},
props: {
//提交表单时的的字段名称
name:{
type:String,
default:''
},
color: {
type: String,
default: "primary"
},
uncolor: {
type: String,
default: "grey-lighten-2"
},
black:{
type:Boolean|String,
default:null
},
num: {
type: Number,
default: 5
},
value: {
type: Number,
default: 0
},
size: {
type: Number,
default: 42
},
margin: {
type: Number,
default: 16
},
disabled: {
type: Boolean,
default: false
},
showNum:{
type: Boolean,
default: false
},
icon:{
type: String,
default: 'icon-collection-fill'
},
// 跟随主题色的改变而改变。
fllowTheme:{
type:Boolean|String,
default:true
}
},
computed: {
black_tmeme:function(){
if(this.black!==null) return this.black;
return this.$tm.vx.state().tmVuetify.black;
},
color_tmeme:function(){
if(this.$tm.vx.state().tmVuetify.color!==null&&this.$tm.vx.state().tmVuetify.color && this.fllowTheme){
return this.$tm.vx.state().tmVuetify.color;
}
return this.color;
},
indexStar: {
get: function() {
return this.value;
},
set: function(val) {
let dval = val;
if(val > this.num) dval = this.num;
this.$emit("input",val)
this.$emit("update:value",val)
this.$emit("change",val)
}
}
},
data() {
return {
ishover:false,
};
},
methods: {
clicSelect(index) {
if (this.disabled) return;
this.indexStar = index;
}
},
}
</script>
<style lang="scss" scoped>
.tm-rate{
.ani {
animation: ani 0.2s linear;
}
}
@keyframes ani {
0% {
transform: scale(0.85)
}
50% {
transform: scale(1.2)
}
100% {
transform: scale(0.85)
}
}
</style>
+170
View File
@@ -0,0 +1,170 @@
<!-- 长宽比例组件 -->
<template>
<view class="tm-ratio--wk d-inline-block">
<view class="tm-ratio" :class="[color,black=='true'||black===true?'bk':'']" :style="style">
<slot name="default" :data="style"></slot>
</view>
</view>
</template>
<script>
/**
*
* 长宽比例组件
* @property {String} ratio = [] 默认:4/3,比例
* @property {Number | String} width = [] 默认:NaN,默认会根据父组件自动计算宽高,指定宽。
* @property {Number | String} height = [] 默认:NaN,默认会根据父组件自动计算宽高,指定高。
* @property {String} color = [white] 默认:white,背景色,主题名称。
* @property {Boolean} black = [true|false] 默认:false,暗黑模式
* @example <tm-ratio height="240" ratio="16/9" ><template v-slot="{data}">{{data.width}}</template></tm-ratio>
*
*/
export default {
props: {
// 比例如:16/9,4/3,5/4
ratio: {
type: String,
default: '4/3'
},
width: {
type: Number | String,
default: NaN
},
height: {
type: Number | String,
default: NaN
},
color: {
type: String,
default: "white"
},
black: {
type: String|Boolean,
default: false
},
},
data() {
return {
style: '',
style_obj:{}
};
},
mounted() {
let t = this;
const query = uni.createSelectorQuery().in(this);
query.select('.tm-ratio--wk').boundingClientRect().exec(dsd => {
var ds = dsd[0];
let wsys = uni.getSystemInfoSync();
let maxWidth = wsys.windowWidth - ds.left - ds.right;
if (maxWidth <= 0) maxWidth = ds.width;
query.select('.tm-ratio').boundingClientRect(data => {
let rt = t.ratio;
if (!rt || rt.indexOf('/') == -1 || rt.split('/').length !== 2) {
rt = '4/3'
}
let ro = rt.split('/');
let ws = !isNaN(parseInt(ro[0])) ? parseInt(ro[0]) : 4;
let hs = !isNaN(parseInt(ro[1])) ? parseInt(ro[1]) : 3;
let bl = hs / ws;
if (isNaN(t.width) && isNaN(t.height)) {
t.style ={
width: data.width + 'px',
height: data.width * bl + 'px',
minHeight: data.width + 'px',
minWidth: data.width + 'px'
};
t.style_obj = uni.$tm.objToString({
width: data.width,
height: data.width * bl,
minHeight: data.width ,
minWidth: data.width
});
return;
}
if (!isNaN(t.width) && isNaN(t.height)) {
let width = uni.upx2px(t.width)
t.style = {
width: (width > maxWidth ? maxWidth : width) + 'px',
height: width * bl + 'px',
minHeight: width * bl + 'px',
minWidth: (width > maxWidth ? maxWidth : width) + 'px'
};
t.style_obj = uni.$tm.objToString({
width: (width > maxWidth ? maxWidth : width) ,
height: width * bl,
minHeight: width * bl ,
minWidth: (width > maxWidth ? maxWidth : width)
});
return;
}
if (isNaN(t.width) && !isNaN(t.height)) {
let height = uni.upx2px(t.height)
let xsw = height / bl;
t.style = {
width: (xsw > maxWidth ? maxWidth : xsw) + 'px',
height: height + 'px',
minHeight: height + 'px',
minWidth: (xsw > maxWidth ? maxWidth : xsw) + 'px'
};
t.style_obj = uni.$tm.objToString({
width: (xsw > maxWidth ? maxWidth : xsw) ,
height: height,
minHeight: height ,
minWidth: (xsw > maxWidth ? maxWidth : xsw)
});
return;
}
// 如果同时提供了宽高,那么按比例。以最长边为第一值。
if (!isNaN(t.width) && !isNaN(t.height)) {
// 第一值,默认为宽度。
let xnh = uni.upx2px(t.width);
let isW = true;
if (xnh < uni.upx2px(t.height)) {
xnh = uni.upx2px(t.height);
isW = false;
}
let w = 0;
let h = 0;
if (isW) {
w = xnh;
h = xnh * bl;
} else {
w = xnh / bl;
h = xnh;
}
t.style = {
width: w + 'px',
height: h + 'px',
minHeight: h + 'px',
minWidth: w + 'px'
};
t.style_obj = uni.$tm.objToString({
width: w ,
height: h ,
minHeight: h ,
minWidth: w
});
return;
}
}).exec();
})
}
}
</script>
<style lang="scss" scoped>
</style>
@@ -0,0 +1,449 @@
<template>
<view class="tm-render" @touchmove.stop.prevent="">
<!-- #ifdef MP-WEIXIN || MP-QQ || MP-KUAISHOU -->
<canvas :style="{width:`${c_w}px`,height:`${c_h}px`}" @mouseup="touchend" @mousedown="touchstart"
@mousemove="touchmove" @touchend="touchend" @touchmove="touchmove" @touchstart="touchstart" id="exid"
canvas-id="exid" type="2d"></canvas>
<!-- #endif -->
<!-- #ifndef MP-WEIXIN || MP-QQ || MP-KUAISHOU -->
<canvas :style="{width:`${c_w}px`,height:`${c_h}px`}" @mouseup="touchend" @mousedown="touchstart"
@mousemove="touchmove" @touchend="touchend" @touchmove="touchmove" @touchstart="touchstart" id="exid"
canvas-id="exid" ></canvas>
<!-- #endif -->
</view>
</template>
<script>
import CRender from '@/tm-vuetify/tool/function/crender/class/crender.class'
// import Graph from '@/tm-vuetify/tool/function/crender/class/graph.class'
// import {CRender,GRAPHS} from '@/tm-vuetify/tool/function/render/index.js'
let render = null;
export default {
name: "tm-render",
props: {
width: {
type: Number,
default: 0
},
height: {
type: Number,
default: 600
}
},
computed: {
c_w: {
get: function() {
return this.cavan_width;
},
set: function(val) {
this.cavan_width = val;
},
},
c_h: function() {
return uni.upx2px(this.height);
}
},
created() {
const dpr = uni.getSystemInfoSync().pixelRatio
this.dpr = dpr;
this.c_w = uni.upx2px(this.width);
},
data() {
return {
cavan_width: 0,
canvaConfig: null,
dragGrpahId: '',//当前正在拖动或者点击的项目id.
old_x: 0,
old_y: 0,
isDrag: false,
dpr: 1
};
},
mounted() {
this.$nextTick(function() {
this.inits();
})
},
destroyed() {
render = null;
clearTimeout(555)
},
methods: {
async inits() {
let t = this;
let res = await this.$Querey('.tm-render', this).catch(e => {})
let p = res[0];
t.c_w = p.width || 300;
t.canvaConfig = p;
//#ifdef MP-WEIXIN || MP-QQ || MP-KUAISHOU
uni.createSelectorQuery().in(t).select('#exid').fields({
node: true,
context: true,
}, function(res) {
let canvas = res.node;
let ctx = canvas.getContext('2d')
ctx['dpr'] = t.dpr;
ctx['scaledpr'] = 10 / (10 * t.dpr);
const w = ctx['width'] = t.c_w
const h = ctx['height'] = t.c_h
// canvas.width = res[0].width * dpr
// canvas.height = res[0].height * dpr
const dpr = uni.getSystemInfoSync().pixelRatio
canvas.width = w * dpr
canvas.height = h * dpr
// 设置 canvas 坐标原点
ctx.translate(0, 0);
ctx.scale(dpr, dpr)
render = new CRender(ctx, t, canvas)
t.$nextTick(function() {
t.$emit('render', render.area);
})
}).exec()
//#endif
//#ifndef MP-WEIXIN || MP-QQ || MP-KUAISHOU
let ctx = uni.createCanvasContext('exid', t)
ctx['dpr'] = t.dpr;
ctx['scaledpr'] = 10 / (10 * t.dpr);
const w = ctx['width'] = t.c_w
const h = ctx['height'] = t.c_h
render = new CRender(ctx, t)
t.$nextTick(function() {
t.$emit('render', render.area);
})
//#endif
},
wait(time) {
return new Promise(resolve => setTimeout(resolve, time))
},
getTextWidthAndPos(shape) {
if (!render) return [0, 0, 0, 0];
let {
content,
position,
maxWidth,
rowGap
} = shape.shape
const {
textBaseline,
fontSize,
textAlign
} = shape.style
let [x, y] = position
content = content.split('\n')
const rowNum = content.length
const lineHeight = fontSize + rowGap
const allHeight = rowNum * lineHeight - rowGap
const twidth = render.ctx.measureText(content + "").width;
if (textBaseline === 'middle') {
y -= allHeight * rowNum + fontSize / 2
}
if (textBaseline === 'bottom') {
y += fontSize
}
if (textAlign === 'center') {
x -= twidth / 2
y += fontSize
}
return [x, y, twidth, allHeight]
// measureText
},
getRender() {
return render;
},
async addGraph(obj) {
let t = this;
let pf = obj;
if (typeof obj == 'object' && Array.isArray(obj)) {
let c = obj.filter(el => {
return {
...el,
tmid: uni.$tm.guid()
};
})
pf = c;
} else if (typeof obj == 'object' && !Array.isArray(obj)) {
pf = [{
...pf,
tmid: uni.$tm.guid()
}]
}
let graphs = pf.map(config => render.add(config))
graphs.forEach((graph, i) => {
const config = pf[i]
t.updateGraphConfigByKey(graph, config)
})
await render.launchAnimation()
//释放内存。
// graphs = []
return graphs.length == 1 ? graphs[0] : graphs;
},
//添加完毕需要更新下,才会显示。
updateGraphConfigByKey(graph, config) {
const keys = Object.keys(config)
keys.forEach(async key => {
if (key === 'shape' || key === 'style') {
// graph.animation('shape', {x:config.shape.x+5}, true)
await graph.animation(key, config[key], true)
} else {
graph[key] = config[key]
}
})
},
touchend(event) {
let t = this;
let evx = 0;
let evy = 0;
//触摸
if (event.type.indexOf('mouse') == -1 && event.changedTouches.length == 1) {
evx = event.changedTouches[0].x
evy = event.changedTouches[0].y
//电脑端。
} else {
evx = event.pageX - this.canvaConfig.left
evy = event.pageY - this.canvaConfig.top;
}
let x = evx;
let y = evy;
this.dragGrpahId = "";
this.isDrag = false
//触发画板的事件。
this.$emit('touchend', {
x: x,
y: y
})
//在那个元素上离开的。
let gps = render.graphs;
let isClickGrpahs = gps.filter((el, index) => {
if (el.name == 'text') {
let rect = t.getTextWidthAndPos(el);
el.hoverRect = rect
}
return el.hoverCheck([x, y], el) || el.hoverCheckProcessor([x, y], el);
});
if (isClickGrpahs.length > 0) {
let nowgap = isClickGrpahs[0];
// 执行元素上绑定的事件。
if (nowgap[event.type]) nowgap[event.type].call(nowgap, {
x: x,
y: y
});
}
},
touchmove(event) {
let t = this;
let evx = 0;
let evy = 0;
let isPc = false
//触摸
if (event.type.indexOf('mouse') == -1 && event.changedTouches.length == 1) {
evx = event.changedTouches[0].x
evy = event.changedTouches[0].y
isPc = false
//电脑端。
} else {
evx = event.pageX - this.canvaConfig.left
evy = event.pageY - this.canvaConfig.top;
isPc = true;
}
let movex = evx - this.old_x;
let movey = evy - this.old_y;
let x = evx;
let y = evy;
// 触发发画板的事件
this.$emit('touchmove', {
x: x,
y: y
})
if(this.isDrag==false) return;
//在哪个元素移动的。
let gps = render.graphs;
let isClickGrpahs = gps.filter((el, index) => {
return (el.hoverCheck([x, y], el) || el.hoverCheckProcessor([x, y], el))&&el.tmid==t.dragGrpahId;
});
if (isClickGrpahs.length > 0) {
let nowgap = isClickGrpahs[0];
if (isPc) {
movex = evx - this.old_x;
movey = evy - this.old_y;
}
if ((nowgap.drag === true && this.isDrag == true) || (nowgap.drag === true && isPc == false)) {
if (nowgap.name == "circle" || nowgap.name == "ellipse" ||
nowgap.name == "ring" || nowgap.name == "arc" || nowgap.name == "regPolygon") {
nowgap.attr('shape', {
rx: movex,
ry: movey
})
} else if (nowgap.name == "rect" ||nowgap.name == 'rectRound'|| nowgap.name == "path"|| nowgap.name == "image"|| nowgap.name == "star" || nowgap.name =='arrow') {
nowgap.attr('shape', {
x: movex,
y: movey
})
} else if (nowgap.name == "text") {
nowgap.attr('shape', {
position: [movex, movey]
})
}
// 执行元素上绑定的事件。
if (nowgap[event.type]) nowgap[event.type].call(nowgap, {
x: movex,
y: movey
});
// if(nowgap['mousemove']||nowgap['touchmove']){
// if (nowgap['mousemove']) nowgap.mousemove.call(nowgap,{x:movex,y:movey})
// if (nowgap['touchmove']) nowgap.touchmove.call(nowgap,{x:movex,y:movey})
// }
}
//配置不允许拖出边界。
// if(this.dragGrpahId === nowgap.tmid
// && movex+nowgap.shape.w<this.canvaConfig.width
// && movey+nowgap.shape.h<this.canvaConfig.height
// && x>=0&&y>=0&&movex>=0&&movey>=0
// ){
// }
// this.$emit('shape:touchmove',{x:x,y:y,shape:nowgap})
}
},
touchstart(event) {
let t = this;
let evx = 0;
let evy = 0;
let isPc = false
//触摸
if (event.type.indexOf('mouse') == -1 && event.changedTouches.length == 1) {
evx = event.changedTouches[0].x
evy = event.changedTouches[0].y
isPc = false
//电脑端。
} else {
evx = event.pageX - this.canvaConfig.left
evy = event.pageY - this.canvaConfig.top
isPc = true;
}
let x = evx
let y = evy
let gps = render.graphs;
//点中了哪些图片,第一个是最顶层的,依次类推。
let isClickGrpahs = gps.filter((el, index) => {
// if (el.name == 'text') {
// let rect = t.getTextWidthAndPos(el);
// el.hoverRect = rect
// }
// 要判断谁的层级高就是先托动谁。
return el.hoverCheck([x, y], el) || el.hoverCheckProcessor([x, y], el);
});
if (isClickGrpahs.length > 0) {
var indexOfMax = 0;
var max = isClickGrpahs.reduce( (a,c,i) => c.index > a ? (indexOfMax = i,c.index) : a, 0)
let nowgap = isClickGrpahs[indexOfMax];
if (nowgap.drag === true) {
this.dragGrpahId = nowgap.tmid;
let gapPos = [];
if (nowgap.name == "circle" || nowgap.name == "ellipse" ||
nowgap.name == "ring" || nowgap.name == "arc" || nowgap.name == "regPolygon"
) {
gapPos = [nowgap.shape.rx, nowgap.shape.ry]
} else if (nowgap.name == "rect" ||nowgap.name == 'rectRound'|| nowgap.name == "path"|| nowgap.name == "image"|| nowgap.name == "star"|| nowgap.name =='arrow') {
gapPos = [nowgap.shape.x, nowgap.shape.y]
} else if (nowgap.name == "text") {
gapPos = nowgap.shape.position
}
if (isPc) {
this.old_x = evx - gapPos[0]
this.old_y = evy - gapPos[1];
} else {
this.old_x = x - gapPos[0];
this.old_y = y - gapPos[1];
}
this.isDrag = true
}
// 执行元素上绑定的事件。
if (nowgap[event.type]) nowgap[event.type].call(nowgap, {
x: x,
y: y
});
// if(nowgap['mousedwon']||nowgap['touchstart']||nowgap['mouseenter']){
// if (nowgap['mousedwon']) nowgap.mousedwon.call(nowgap,{x:x,y:y})
// if (nowgap['touchstart']) nowgap.touchstart.call(nowgap,{x:x,y:y})
// if (nowgap['mouseenter']) nowgap.mouseenter.call(nowgap,{x:x,y:y})
// }
} else {
this.dragGrpahId = ""
}
this.$emit('touchstart', {
x: x,
y: y
})
},
},
}
</script>
<style lang="scss">
body {}
</style>
@@ -0,0 +1,787 @@
<template>
<view class="tm-render" @touchmove.stop.prevent="">
<!-- #ifdef MP-WEIXIN || MP-QQ || MP-KUAISHOU || MP-ALIPAY -->
<canvas :style="{width:`${c_w}px`,height:`${c_h}px`}" @mouseup="touchend" @mousedown="touchstart"
@mousemove="touchmove" @touchend="touchend" @touchmove="touchmove" @touchstart="touchstart" id="exid"
canvas-id="exid" type="2d"></canvas>
<!-- #endif -->
<!-- #ifndef MP-WEIXIN || MP-QQ || MP-KUAISHOU || MP-ALIPAY || APP-PLUS -->
<canvas :style="{width:`${c_w}px`,height:`${c_h}px`}"
@mouseup="touchend"
@mousedown="touchstart"
@mousemove="touchmove"
@touchend="touchend"
@touchmove="touchmove"
@touchstart="touchstart"
id="exid"
canvas-id="exid" ></canvas>
<!-- #endif -->
<!-- #ifdef APP-PLUS-->
<canvas
@mouseup="CRender.touchend_app"
@mousedown="CRender.touchstart_app"
@mousemove="CRender.touchmove_app"
@touchend="CRender.touchend_app"
@touchmove="CRender.touchmove_app"
@touchstart="CRender.touchstart_app"
:prop="app_opts" :change:prop="CRender.update_app_opts" :style="{width:`${c_w}px`,height:`${c_h}px`}" id="exid" canvas-id="exid" ></canvas>
<!-- #endif -->
</view>
</template>
<script>
// #ifndef APP-PLUS
import CRender from '@/tm-vuetify/tool/function/crender/class/crender.class'
// #endif
let render = null;
export default {
name: "tm-render",
props: {
width: {
type: Number,
default: 0
},
height: {
type: Number,
default: 600
}
},
computed: {
c_w: {
get: function() {
return this.cavan_width;
},
set: function(val) {
this.cavan_width = val;
},
},
c_h: function() {
return uni.upx2px(this.height);
}
},
created() {
const dpr = uni.getSystemInfoSync().pixelRatio
this.dpr = dpr;
this.c_w = uni.upx2px(this.width);
},
data() {
return {
cavan_width: 0,
canvaConfig: null,
dragGrpahId: '',//当前正在拖动或者点击的项目id.
old_x: 0,
old_y: 0,
isDrag: false,
dpr: 1,
app_opts:{
render_app:null,
graph:null,
fun:[],
canvaConfig:null
}
};
},
mounted() {
this.$nextTick(async function() {
// #ifndef APP-PLUS
this.inits();
// #endif
// #ifdef APP-PLUS
let res = await this.$Querey('.tm-render', this).catch(e => {})
let p = res[0];
this.$set(this.app_opts,'canvaConfig',p)
// #endif
})
},
destroyed() {
render = null;
clearTimeout(555)
},
methods: {
async inits() {
let t = this;
let res = await this.$Querey('.tm-render', this).catch(e => {})
let p = res[0];
t.c_w = p.width || 300;
t.canvaConfig = p;
//#ifdef MP-WEIXIN || MP-QQ || MP-KUAISHOU
uni.createSelectorQuery().in(t).select('#exid').fields({
node: true,
context: true,
}, function(res) {
let canvas = res.node;
let ctx = canvas.getContext('2d')
ctx['dpr'] = t.dpr;
ctx['scaledpr'] = 10 / (10 * t.dpr);
const w = ctx['width'] = t.c_w
const h = ctx['height'] = t.c_h
// canvas.width = res[0].width * dpr
// canvas.height = res[0].height * dpr
const dpr = uni.getSystemInfoSync().pixelRatio
canvas.width = w * dpr
canvas.height = h * dpr
// 设置 canvas 坐标原点
ctx.translate(0, 0);
ctx.scale(dpr, dpr)
render = new CRender(ctx, t, canvas)
t.$nextTick(function() {
// t.$emit('render', render.area);
// 12296升级改造
t.$emit('render', render.area);
})
}).exec()
//#endif
//#ifndef MP-WEIXIN || MP-QQ || MP-KUAISHOU || MP-ALIPAY || APP-PLUS
let ctx = uni.createCanvasContext('exid', t)
ctx['dpr'] = t.dpr;
ctx['scaledpr'] = 10 / (10 * t.dpr);
const w = ctx['width'] = t.c_w
const h = ctx['height'] = t.c_h
render = new CRender(ctx, t)
t.$nextTick(function() {
// 12296升级改造
t.$emit('render', render.area);
// t.$emit('render', render.area);
})
//#endif
},
wait(time) {
return new Promise(resolve => setTimeout(resolve, time))
},
getTextWidthAndPos(shape) {
// #ifdef APP-PLUS
return [0,0,0,0];
// #endif
if (!render) return [0, 0, 0, 0];
let {
content,
position,
maxWidth,
rowGap
} = shape.shape
const {
textBaseline,
fontSize,
textAlign
} = shape.style
let [x, y] = position
content = content.split('\n')
const rowNum = content.length
const lineHeight = fontSize + rowGap
const allHeight = rowNum * lineHeight - rowGap
const twidth = render.ctx.measureText(content + "").width;
if (textBaseline === 'middle') {
y -= allHeight * rowNum + fontSize / 2
}
if (textBaseline === 'bottom') {
y += fontSize
}
if (textAlign === 'center') {
x -= twidth / 2
y += fontSize
}
return [x, y, twidth, allHeight]
// measureText
},
getRender() {
// #ifdef APP-PLUS
return this.app_opts.render_app;
// #endif
return render;
},
renderCavans(e){
render = e;
//app触发这里。
this.$emit('render', render.area);
},
//app-plus代理执行函数,参数一为函数名称,参数二为参数。
appCallFun(funName,arg){
// #ifdef APP-PLUS
this.$set( this.app_opts,'fun',[funName,arg])
// #endif
// #ifndef APP-PLUS
console.error("此函数为APP-PLUS专用,详见:https://jx2d.cn/guid/render/%E5%BC%80%E5%A7%8B%E4%BD%BF%E7%94%A8.html")
// #endif
},
async addGraph(obj) {
let t = this;
let pf = obj;
// #ifdef APP-PLUS
this.$set( this.app_opts,'graph',obj)
return
// #endif
if (typeof obj == 'object' && Array.isArray(obj)) {
let c = obj.filter(el => {
return {
...el,
tmid: uni.$tm.guid()
};
})
pf = c;
} else if (typeof obj == 'object' && !Array.isArray(obj)) {
pf = [{
...pf,
tmid: uni.$tm.guid()
}]
}
let graphs = pf.map(config => render.add(config))
graphs.forEach((graph, i) => {
const config = pf[i]
t.updateGraphConfigByKey(graph, config)
})
await render.launchAnimation()
//释放内存。
// graphs = []
return graphs.length == 1 ? graphs[0] : graphs;
},
//添加完毕需要更新下,才会显示。
updateGraphConfigByKey(graph, config) {
const keys = Object.keys(config)
keys.forEach(async key => {
if (key === 'shape' || key === 'style') {
// graph.animation('shape', {x:config.shape.x+5}, true)
await graph.animation(key, config[key], true)
} else {
graph[key] = config[key]
}
})
},
touchend(event) {
let t = this;
let evx = 0;
let evy = 0;
//触摸
if (event.type.indexOf('mouse') == -1 && event.changedTouches.length == 1) {
evx = event.changedTouches[0].x
evy = event.changedTouches[0].y
//电脑端。
} else {
evx = event.pageX - this.canvaConfig.left
evy = event.pageY - this.canvaConfig.top;
}
let x = evx;
let y = evy;
this.dragGrpahId = "";
this.isDrag = false
//触发画板的事件。
this.$emit('touchend', {
x: x,
y: y
})
//在那个元素上离开的。
let gps = render.graphs;
let isClickGrpahs = gps.filter((el, index) => {
if (el.name == 'text') {
let rect = t.getTextWidthAndPos(el);
el.hoverRect = rect
}
return el.hoverCheck([x, y], el) || el.hoverCheckProcessor([x, y], el);
});
if (isClickGrpahs.length > 0) {
let nowgap = isClickGrpahs[0];
// 执行元素上绑定的事件。
if (nowgap[event.type]) nowgap[event.type].call(nowgap, {
x: x,
y: y
});
}
},
touchmove(event) {
let t = this;
let evx = 0;
let evy = 0;
let isPc = false
//触摸
if (event.type.indexOf('mouse') == -1 && event.changedTouches.length == 1) {
evx = event.changedTouches[0].x
evy = event.changedTouches[0].y
isPc = false
//电脑端。
} else {
evx = event.pageX - this.canvaConfig.left
evy = event.pageY - this.canvaConfig.top;
isPc = true;
}
let movex = evx - this.old_x;
let movey = evy - this.old_y;
let x = evx;
let y = evy;
// 触发发画板的事件
this.$emit('touchmove', {
x: x,
y: y
})
if(this.isDrag==false) return;
//在哪个元素移动的。
let gps = render.graphs;
let isClickGrpahs = gps.filter((el, index) => {
return (el.hoverCheck([x, y], el) || el.hoverCheckProcessor([x, y], el))&&el.tmid==t.dragGrpahId;
});
if (isClickGrpahs.length > 0) {
let nowgap = isClickGrpahs[0];
if (isPc) {
movex = evx - this.old_x;
movey = evy - this.old_y;
}
if ((nowgap.drag === true && this.isDrag == true) || (nowgap.drag === true && isPc == false)) {
if (nowgap.name == "circle" || nowgap.name == "ellipse" ||
nowgap.name == "ring" || nowgap.name == "arc" || nowgap.name == "regPolygon") {
nowgap.attr('shape', {
rx: movex,
ry: movey
})
} else if (nowgap.name == "rect" ||nowgap.name == 'rectRound'|| nowgap.name == "path"|| nowgap.name == "image"|| nowgap.name == "star" || nowgap.name =='arrow') {
nowgap.attr('shape', {
x: movex,
y: movey
})
} else if (nowgap.name == "text") {
nowgap.attr('shape', {
position: [movex, movey]
})
}
// 执行元素上绑定的事件。
if (nowgap[event.type]) nowgap[event.type].call(nowgap, {
x: movex,
y: movey
});
// if(nowgap['mousemove']||nowgap['touchmove']){
// if (nowgap['mousemove']) nowgap.mousemove.call(nowgap,{x:movex,y:movey})
// if (nowgap['touchmove']) nowgap.touchmove.call(nowgap,{x:movex,y:movey})
// }
}
//配置不允许拖出边界。
// if(this.dragGrpahId === nowgap.tmid
// && movex+nowgap.shape.w<this.canvaConfig.width
// && movey+nowgap.shape.h<this.canvaConfig.height
// && x>=0&&y>=0&&movex>=0&&movey>=0
// ){
// }
// this.$emit('shape:touchmove',{x:x,y:y,shape:nowgap})
}
},
touchstart(event) {
let t = this;
let evx = 0;
let evy = 0;
let isPc = false
//触摸
if (event.type.indexOf('mouse') == -1 && event.changedTouches.length == 1) {
evx = event.changedTouches[0].x
evy = event.changedTouches[0].y
isPc = false
//电脑端。
} else {
evx = event.pageX - this.canvaConfig.left
evy = event.pageY - this.canvaConfig.top
isPc = true;
}
let x = evx
let y = evy
let gps = render.graphs;
//点中了哪些图片,第一个是最顶层的,依次类推。
let isClickGrpahs = gps.filter((el, index) => {
// 要判断谁的层级高就是先托动谁。
return el.hoverCheck([x, y], el) || el.hoverCheckProcessor([x, y], el);
});
if (isClickGrpahs.length > 0) {
var indexOfMax = 0;
var max = isClickGrpahs.reduce( (a,c,i) => c.index > a ? (indexOfMax = i,c.index) : a, 0)
let nowgap = isClickGrpahs[indexOfMax];
if (nowgap.drag === true) {
this.dragGrpahId = nowgap.tmid;
let gapPos = [];
if (nowgap.name == "circle" || nowgap.name == "ellipse" ||
nowgap.name == "ring" || nowgap.name == "arc" || nowgap.name == "regPolygon"
) {
gapPos = [nowgap.shape.rx, nowgap.shape.ry]
} else if (nowgap.name == "rect" ||nowgap.name == 'rectRound'|| nowgap.name == "path"|| nowgap.name == "image"|| nowgap.name == "star"|| nowgap.name =='arrow') {
gapPos = [nowgap.shape.x, nowgap.shape.y]
} else if (nowgap.name == "text") {
gapPos = nowgap.shape.position
}
if (isPc) {
this.old_x = evx - gapPos[0]
this.old_y = evy - gapPos[1];
} else {
this.old_x = x - gapPos[0];
this.old_y = y - gapPos[1];
}
this.isDrag = true
}
// 执行元素上绑定的事件。
if (nowgap[event.type]) nowgap[event.type].call(nowgap, {
x: x,
y: y
});
} else {
this.dragGrpahId = ""
}
this.$emit('touchstart', {
x: x,
y: y
})
},
},
}
</script>
<script module="CRender" lang="renderjs">
var cdr = require('@/tm-vuetify/tool/function/crender/crender.min.js')
const CRender = window.CRender.CRender
var render_app;
export default {
mounted() {
// #ifdef APP-PLUS
this.$nextTick(function(){
this.initsAppH5();
})
// #endif
},
methods: {
initsAppH5(e) {
let t = this;
let canvas = document.querySelector('#exid').querySelector('canvas')
let canvasPrent = document.querySelector('.tm-render')
let w = canvasPrent.offsetWidth;
let h = canvasPrent.offsetHeight;
canvas.style.width = w+'px';
canvas.style.height = h+'px';
render_app = new CRender(canvas)
this.$ownerInstance.$vm.$set(this.$ownerInstance.$vm.app_opts,'render_app',render_app)
setTimeout(function() {
t.$ownerInstance.callMethod('renderCavans', render_app)
}, 10);
},
update_app_opts(newValue, oldValue, ownerInstance, instance) {
if(newValue.graph){
let graph = render_app.add(newValue.graph)
this.$ownerInstance.$vm.app_opts.graph=null;
}
if(newValue.fun){
if(typeof(newValue.fun)=="object" && Array.isArray(newValue.fun)){
if(newValue.fun.length>0){
if(newValue.fun.length==1){
render_app[newValue.fun[0]]();
}else if(newValue.fun.length==2){
render_app[newValue.fun[0]](newValue.fun[1])
}
}
}
}
},
wait(time) {
return new Promise(resolve => setTimeout(resolve, time))
},
getTextWidthAndPos(shape) {
// #ifdef APP-PLUS
return [0,0,0,0];
// #endif
if (!render) return [0, 0, 0, 0];
let {
content,
position,
maxWidth,
rowGap
} = shape.shape
const {
textBaseline,
fontSize,
textAlign
} = shape.style
let [x, y] = position
content = content.split('\n')
const rowNum = content.length
const lineHeight = fontSize + rowGap
const allHeight = rowNum * lineHeight - rowGap
const twidth = render.ctx.measureText(content + "").width;
if (textBaseline === 'middle') {
y -= allHeight * rowNum + fontSize / 2
}
if (textBaseline === 'bottom') {
y += fontSize
}
if (textAlign === 'center') {
x -= twidth / 2
y += fontSize
}
return [x, y, twidth, allHeight]
// measureText
},
touchend_app(event) {
let t = this;
let evx = 0;
let evy = 0;
//触摸
if (event.type.indexOf('mouse') == -1 && event.changedTouches.length == 1) {
evx = event.changedTouches[0].x
evy = event.changedTouches[0].y
//电脑端。
} else {
evx = event.pageX - this.app_opts.canvaConfig.left
evy = event.pageY - this.app_opts.canvaConfig.top;
}
let x = evx;
let y = evy;
this.dragGrpahId = "";
this.isDrag = false
//触发画板的事件。
this.$emit('touchend', {
x: x,
y: y
})
//在那个元素上离开的。
let gps = render_app.graphs;
let isClickGrpahs = gps.filter((el, index) => {
if (el.name == 'text') {
let rect = t.getTextWidthAndPos(el);
el.hoverRect = rect
}
return el.hoverCheck([x, y], el) || el.hoverCheckProcessor([x, y], el);
});
if (isClickGrpahs.length > 0) {
let nowgap = isClickGrpahs[0];
// 执行元素上绑定的事件。
if (nowgap[event.type]) nowgap[event.type].call(nowgap, {
x: x,
y: y
});
}
},
touchmove_app(event) {
let t = this;
let evx = 0;
let evy = 0;
let isPc = false
//触摸
if (event.type.indexOf('mouse') == -1 && event.changedTouches.length == 1) {
evx = event.changedTouches[0].x
evy = event.changedTouches[0].y
isPc = false
//电脑端。
} else {
evx = event.pageX - this.app_opts.canvaConfig.left
evy = event.pageY - this.app_opts.canvaConfig.top;
isPc = true;
}
let movex = evx - this.old_x;
let movey = evy - this.old_y;
let x = evx;
let y = evy;
// 触发发画板的事件
this.$emit('touchmove', {
x: x,
y: y
})
if(this.isDrag==false) return;
//在哪个元素移动的。
let gps = render_app.graphs;
let isClickGrpahs = gps.filter((el, index) => {
return (el.hoverCheck([x, y], el) || el.hoverCheckProcessor([x, y], el))&&el.tmid==t.dragGrpahId;
});
if (isClickGrpahs.length > 0) {
let nowgap = isClickGrpahs[0];
if (isPc) {
movex = evx - this.old_x;
movey = evy - this.old_y;
}
if ((nowgap.drag === true && this.isDrag == true) || (nowgap.drag === true && isPc == false)) {
if (nowgap.name == "circle" || nowgap.name == "ellipse" ||
nowgap.name == "ring" || nowgap.name == "arc" || nowgap.name == "regPolygon") {
nowgap.attr('shape', {
rx: movex,
ry: movey
})
} else if (nowgap.name == "rect" ||nowgap.name == 'rectRound'|| nowgap.name == "path"|| nowgap.name == "image"|| nowgap.name == "star" || nowgap.name =='arrow') {
nowgap.attr('shape', {
x: movex,
y: movey
})
} else if (nowgap.name == "text") {
nowgap.attr('shape', {
position: [movex, movey]
})
}
// 执行元素上绑定的事件。
if (nowgap[event.type]) nowgap[event.type].call(nowgap, {
x: movex,
y: movey
});
}
}
},
touchstart_app(event) {
let t = this;
let evx = 0;
let evy = 0;
let isPc = false
//触摸
if (event.type.indexOf('mouse') == -1 && event.changedTouches.length == 1) {
evx = event.changedTouches[0].x
evy = event.changedTouches[0].y
isPc = false
//电脑端。
} else {
evx = event.pageX - this.app_opts.canvaConfig.left
evy = event.pageY - this.app_opts.canvaConfig.top
isPc = true;
}
let x = evx
let y = evy
let gps = render_app.graphs;
//点中了哪些图片,第一个是最顶层的,依次类推。
let isClickGrpahs = gps.filter((el, index) => {
return el.hoverCheck([x, y], el) || el.hoverCheckProcessor([x, y], el);
});
if (isClickGrpahs.length > 0) {
var indexOfMax = 0;
var max = isClickGrpahs.reduce( (a,c,i) => c.index > a ? (indexOfMax = i,c.index) : a, 0)
let nowgap = isClickGrpahs[indexOfMax];
if (nowgap.drag === true) {
this.dragGrpahId = nowgap.tmid;
let gapPos = [];
if (nowgap.name == "circle" || nowgap.name == "ellipse" ||
nowgap.name == "ring" || nowgap.name == "arc" || nowgap.name == "regPolygon"
) {
gapPos = [nowgap.shape.rx, nowgap.shape.ry]
} else if (nowgap.name == "rect" ||nowgap.name == 'rectRound'|| nowgap.name == "path"|| nowgap.name == "image"|| nowgap.name == "star"|| nowgap.name =='arrow') {
gapPos = [nowgap.shape.x, nowgap.shape.y]
} else if (nowgap.name == "text") {
gapPos = nowgap.shape.position
}
if (isPc) {
this.old_x = evx - gapPos[0]
this.old_y = evy - gapPos[1];
} else {
this.old_x = x - gapPos[0];
this.old_y = y - gapPos[1];
}
this.isDrag = true
}
// 执行元素上绑定的事件。
if (nowgap[event.type]) nowgap[event.type].call(nowgap, {
x: x,
y: y
});
} else {
this.dragGrpahId = ""
}
this.$emit('touchstart', {
x: x,
y: y
})
},
}
}
</script>
<style lang="scss">
body {}
</style>
+105
View File
@@ -0,0 +1,105 @@
<template>
<view @click="$emit('click',$event)" :gutter="gutter" class="tm--row" :class="[preatClass]" >
<view class="tm--row--body" :style="style" :class="[customClass]">
<view v-show="chuliWsok==true">
<slot></slot>
</view>
</view>
</view>
</template>
<script>
/**
* 栅格排版ROW
* @description 请注意,它需要配合col使用,不能单独使用。
* @property {String} justify = [center|flex-start|flex-start|space-between|space-around] 横向对齐方向
* @property {String} align = [center|flex-start|flex-end] 子项目纵向对齐方式。
* @property {String|Number} width = [100%|auto] 宽度,可以是数字其它百分比,数字时单位为px
* @property {String|Number} height = [100%|auto] 高度,可以是数字其它百分比,数字时单位为px
* @property {Array} gutter = [] 默认:[0,0]左右,和上下间距。优先级别小于col组件内部的间距。
* @property {String} custom-class = [] 自定义类。
*/
export default {
name:"tm-row",
props:{
// 自定义类。
customClass: {
type: String,
default: ''
},
// 自定义类。
preatClass: {
type: String,
default: ''
},
// 子项目横向对齐方式。
justify:{
type:String,
default:'flex-start'
},
// 子项目纵向对齐方式。
align:{
type:String,
default:'center'
},
width: {
type: String | Number,
default: '100%'
},
height: {
type: String | Number,
default: ""
},
// 左右,和上下间距。优先级别小于col组件内部的间距。
gutter:{
type:Array,
default:()=>{
return [0,0]
}
}
},
data() {
return {
width_px:0,
children_num:0,
style:'',
chuliWsok:false,//处理完宽度计算信息再进行显示内部内容。
};
},
async mounted() {
let t = this;
await this.$Querey('.tm--row',this).then(preantw=>{
t.width_px = preantw[0].width;
// #ifndef H5
t.children_num = t.$children.length;
// #endif
// #ifdef H5
t.children_num = t.$children[0].$children[0].$children[0].$children.length;
// #endif
t.style = uni.$tm.objToString({
'justify-content':t.justify,
'align-items':t.align,
'width':t.width,
'height':!t.height?'default':(uni.upx2px(t.height)+'px')
},';');
t.chuliWsok = true;
}).catch(e=>{})
}
}
</script>
<style lang="scss" scoped>
.tm--row{
display: flex;
flex-flow: row wrap;
width: auto;
.tm--row--body{
height: 100%;
flex-flow: row wrap;
}
}
</style>
@@ -0,0 +1,106 @@
<template>
<view class="tm-scroll relative" :style="{width:`${width}rpx`}">
<scroll-view scroll-anchoring @scroll="onscrollEvent" :style="{width:`${width}rpx`}" scroll-x>
<view class="fulled " style="white-space: nowrap;">
<slot></slot>
</view>
</scroll-view>
<view v-if="showDot" style="height: 24rpx;"></view>
<view v-if="showDot" class="absolute tm-scroll-bar fulled flex-center">
<view class="tm-scroll-bar-active grey-lighten-2 round-24 overflow"
:class="[$tm.vx.state().tmVuetify.black?'bk':'']">
<view :style="{marginLeft:`${left}px`}" class="tm-scroll-bar-active-bar round-24"
:class="[color_tmeme]"></view>
</view>
</view>
</view>
</template>
<script>
/**
* 横向滚动
* @property {Number} width = [] 默认650,单位rpx,宽度
* @property {String} color = [] 默认primary,任意主题色名称
* @property {Boolean|String} show-dot = [] 默认true,是否显示指示点。
*/
export default {
name: "tm-scroll",
props: {
width: {
type: Number | String,
default: 650,
},
color: {
type: String,
default: 'primary',
},
// 跟随主题色的改变而改变。
fllowTheme: {
type: Boolean | String,
default: true
},
//是否显示指示点。
showDot: {
type: Boolean | String,
default: true
}
},
computed: {
color_tmeme: function() {
if (this.$tm.vx.state().tmVuetify.color !== null && this.$tm.vx.state().tmVuetify.color && this
.fllowTheme) {
return this.$tm.vx.state().tmVuetify.color;
}
return this.color;
},
},
data() {
return {
left: 0,
totlal_w: 0,
totlal_L: 0,
}
},
methods: {
onscrollEvent(e) {
// clearTimeout(this.timeid)
let t = this;
let dbw = uni.upx2px(100);
let dbw_active_w = dbw * 0.4;
let aw = uni.upx2px(t.width);
let totlal_w = e.detail.scrollWidth - aw;
let totlal_L = e.detail.scrollLeft;
if (totlal_L <= 0) {
totlal_L = 0;
}
if (totlal_L >= totlal_w) {
totlal_L = totlal_w;
}
let bl = totlal_L / totlal_w;
let ml = (dbw - dbw_active_w) * bl;
t.left = ml;
}
}
}
</script>
<style lang="scss" scoped>
.tm-scroll {
.tm-scroll-bar {
bottom: 0;
left: 0;
.tm-scroll-bar-active {
height: 10rpx;
width: 100rpx;
.tm-scroll-bar-active-bar {
height: 10rpx;
width: 40%;
}
}
}
}
</style>
@@ -0,0 +1,210 @@
<template>
<view class="tm-search fulled py-12"
:class="[
black_tmeme?'grey-darken-5':bgColor,
`shadow-${shadow}`
]"
>
<view @click="$emit('click')" class="tm-search-body flex-around">
<view v-if="showLeft" class="flex-shrink pl-32 flex-center">
<slot name="left">
<tm-icons @click="$emit('cancel')" :black="black_tmeme" :color="color_tmeme" dense :name="leftIcon"></tm-icons>
</slot>
</view>
<view class="flex-between fulled py-10 mx-32" :class="[
black_tmeme?' grey-darken-5':'grey-lighten-4',
`round-${round}`
]">
<view v-if="prefixText" class="px-16 flex-shrink flex-center text-size-n text-grey">
{{prefixText}}
</view>
<view v-if="prefixIcon" class="pl-16 flex-shrink flex-center">
<tm-icons :black="black_tmeme" :color="insertColor_thmeme" dense :name="prefixIcon"></tm-icons>
</view>
<input @focus="$emit('focus',$event)" @blur="$emit('blur',$event)" confirm-type="search" type="search" @confirm="confirm" v-model="valData" :disabled="disabled" :placeholder="placeholder" class="flat fulled text-size-n pl-16 py-4 tm-search-body-searc" :class="[`text-align-${align}`]" />
<view v-if="clear&&valData.length>0" class="px-16 flex-shrink flex-center">
<tm-icons @click="valData='';$emit('clear',valData)" :size="24" :black="black_tmeme" :color="insertColor_thmeme" dense name="icon-times"></tm-icons>
</view>
<view v-if="suffixIcon" class="px-16 flex-shrink flex-center">
<slot name="suffixIcon">
<tm-icons @click="confirm" :black="black_tmeme" :color="insertColor_thmeme" dense :name="suffixIcon"></tm-icons>
</slot>
</view>
</view>
<view @click="confirm" v-if="showRight" class="flex-shrink mr-32 text-size-n" :class="[
`text-${color_tmeme}`
]">
<slot name="right">
<text class="text-size-n">{{confirmText}}</text>
</slot>
</view>
</view>
</view>
</template>
<script>
/**
* 搜索框
* @property {String} value = [] 默认:'',建议valu.sync或者v-model双向绑定输入的词。
* @property {Boolean} disabled = [true|false] 默认:false,是否禁用。
* @property {String} left-icon = [] 默认:icon-angle-left,左边图标名称。
* @property {String} prefix-text = [] 默认:'',输入框内前缀文字。
* @property {String} prefix-icon = [] 默认:'icon-search',输入框内前缀图标。
* @property {String} insert-color = [] 默认:'primary',输入框内图标的主题色。
* @property {String} suffix-icon = [] 默认:'',输入框内后缀图标。
* @property {Boolean} show-left = [true|false] 默认:false,是否显示左边部分
* @property {String} confirm-text = [] 默认:搜索, 右边文字
* @property {Boolean} show-right = [] 默认:true ,是否显示右边
* @property {String} color = [] 默认:primary , 外部图标文字的主题色。
* @property {String} bg-color = [] 默认:white , 搜索框的背景。
* @property {Boolean} black = [true|false] 默认:null , 是否暗黑模式
* @property {Boolean} clear = [true|false] 默认:false , 是否显示清除图标
* @property {Number} round = [] 默认:2 , 输入框内圆角。
* @property {Number} shadow = [] 默认:2 , 搜索框的投影
* @property {String} placeholder = [] 默认:'请输入关键词' , 输入框的点位符
* @property {String} align = [left|right|center] 默认:'left' , 输入框输入对齐的方式。
* @property {Function} cancel 点击左边图标产生的事件。
* @property {Function} clear 点击清除图标时触发相关事件。
* @property {Function} confirm 输入法键盘上点击确认,输入框后缀图标,右边部分点击时产生的事件,含当前输入的内容。
* @property {Function} click 点击整体组件的事件。
*/
import tmIcons from "@/tm-vuetify/components/tm-icons/tm-icons.vue"
export default {
components:{tmIcons},
name:"tm-search",
model:{
prop:'value',
event:'input'
},
props:{
clear:{
type:Boolean,
default:false
},
value:{
type:String,
default:''
},
disabled:{
type:Boolean|String,
default:false
},
leftIcon:{
type:String,
default:'icon-angle-left'
},
// 输入框内的前缀文字
prefixText:{
type:String,
default:''
},
//输入框内的前缀图标。
prefixIcon:{
type:String,
default:'icon-search'
},
// 输入框内部主题
insertColor:{
type:String,
default:'primary'
},
//输入框内的后缀图标。
suffixIcon:{
type:String,
default:''
},
showLeft:{
type:Boolean,
default:false
},
confirmText:{
type:String,
default:'搜索'
},
showRight:{
type:Boolean,
default:true
},
color:{
type:String,
default:'primary'
},
bgColor:{
type:String,
default:'white'
},
black:{
type:Boolean|String,
default:null
},
round:{
type:Number,
default:2
},
shadow:{
type:Number,
default:4
},
placeholder:{
type:String,
default:"请输入关键词"
},
align:{
type:String,
default:'left' //left|right|center
},
// 跟随主题色的改变而改变。
fllowTheme:{
type:Boolean|String,
default:true
}
},
computed:{
black_tmeme: function() {
if (this.black !== null) return this.black;
return this.$tm.vx.state().tmVuetify.black;
},
color_tmeme:function(){
if(this.$tm.vx.state().tmVuetify.color!==null&&this.$tm.vx.state().tmVuetify.color && this.fllowTheme){
return this.$tm.vx.state().tmVuetify.color;
}
return this.color;
},
insertColor_thmeme:function(){
if(this.$tm.vx.state().tmVuetify.color!==null&&this.$tm.vx.state().tmVuetify.color && this.fllowTheme){
return this.$tm.vx.state().tmVuetify.color;
}
return this.insertColor;
},
valData:{
get:function(){
return this.value;
},
set:function(val){
this.$emit('input',val)
}
}
},
data() {
return {
};
},
methods: {
confirm(e) {
this.$emit('confirm',this.value)
}
},
}
</script>
<style lang="scss" scoped>
.tm-search-body-searc{
-webkit-appearance: none !important;
}
</style>
@@ -0,0 +1,290 @@
<template>
<view class="tm-segTabs d-inline-block relative ">
<view class="tm-segTabs-wkbody fulled flex-start relative" :class="['pa-'+gutter]"
:style="{width:width>0?width+'rpx':'auto'}">
<view @click="clickItem(index, item)" :class="[
`px-${margin[0]} py-${margin[1]}`,
`text-size-${fontSize}`,
`round-${round}`,
active_index == index
? 'text-weight-b ' + (black_tmeme ? `text-grey-lighten-3 ` : `text-${color_tmeme}`)
: black_tmeme
? `text-grey bk`
: `text-${color}`
]" :id="'tm-segTabs-item-' + index" v-for="(item, index) in listData" :key="index"
class="tm-segTabs-item flex-shrink flex-center" :style="{
width:width_item+'px'
}">
<slot name="default"
:item="{color:color_tmeme, data: item, index: index, isActive: active_index == index }">
{{ returnKeyValue(item) }}
</slot>
</view>
</view>
<view :class="[black_tmeme ? 'grey-darken-5' : bgColor, `round-${round}`]"
class="tm-segTabs-bg absolute l-0 t-0 fulled " :style="{ height: body_height + 'px' }">
<view
:class="[`shadow-${activeColor}-${shadow}`,black_tmeme ? 'grey-darken-3' : activeColor, `round-${round}`,aniOn?'aniOn':'',`mt-${gutter} ml-${gutter/2}`]"
class="tm-segTabs-bg-bar relative"
:style="{ width: `${active_barWidth}px`, height: `${active_barHeight}px`,transform: `translateX(${left}px)` }">
</view>
</view>
</view>
</template>
<script>
/**
* 分段器选项卡
* @property {Number} value = [] 默认0,当前激活的选项.
* @property {Array} list = [] 默认数据,对象数组或者字符串数组
* @property {String} rang-key = [] 默认text,list对象数组时取文本的字段名称
* @property {String} color = [] 默认black,默认的文字颜色
* @property {String} bg-color = [] 默认grey-lighten-4,默认的背景色
* @property {String} active-font-color = [] 默认black,激活的文本色
* @property {String} active-color = [] 默认black,激活项的背景色
* @property {String} font-size = [] 默认 n,字号,xxs,xs,s,n,g,lg,xl
* @property {Array} margin = [] 默认 [24,10],左右和上下的间距,调整它可以控制宽度和高度。
* @property {Number} round = [] 默认4 圆角
* @property {Number} shadow = [] 默认4 投影
* @property {Number} gutter = [] 默认4 四边的间隙
* @property {Number} width = [] 默认0, 整体的宽度,默认不自动宽度,提供了后,项目内的宽度为均分此宽度。
* @property {Boolean} black = [] 默认false 是否暗黑模式
* @property {Boolean} fllow-theme = [] 默认true 是否跟随主题切换主色。
*/
export default {
name: 'tm-segTabs',
props: {
value: {
type: Number,
defalut: 0
},
list: {
type: Array,
default: () => []
},
//整体的宽度,不设置使用默认计算的宽度。
width: {
type: Number,
default: 0
},
// 四周的间隙
gutter: {
type: Number,
default: 4
},
shadow: {
type: Number,
default: 4
},
margin: {
type: Array,
default: () => [24, 10]
},
rangKey: {
type: String,
default: 'text'
},
color: {
type: String,
default: 'black'
},
bgColor: {
type: String,
default: 'grey-lighten-4'
},
activeFontColor: {
type: String,
default: 'black'
},
activeColor: {
type: String,
default: 'white'
},
fontSize: {
type: String,
default: 'n'
},
round: {
type: String | Number,
default: 4
},
// 跟随主题色的改变而改变。
fllowTheme: {
type: Boolean | String,
default: true
},
black: {
type: Boolean | String,
default: null
}
},
data() {
return {
body_height: 0,
active_barHeight: 0,
active_barWidth: 0,
aniOn: false,
left: 0,
preventLeft: 0,
width_item_w: 0,
};
},
watch: {
value(newValue, oldValue) {
this.active_index = newValue;
},
list: {
deep: true,
async handler() {
this.width_item = this.width;
await this.setInits();
}
}
},
computed: {
width_item: {
get: function() {
return this.width_item_w;
},
set: function(val) {
if (val == 0) {
this.width_item_w = 'auto'
} else {
this.width_item_w = (uni.upx2px(val) / this.list.length)
}
}
},
black_tmeme: function() {
if (this.black !== null) return this.black;
return this.$tm.vx.state().tmVuetify.black;
},
color_tmeme: function() {
if (this.$tm.vx.state().tmVuetify.color !== null && this.$tm.vx.state().tmVuetify.color && this
.fllowTheme) {
return this.$tm.vx.state().tmVuetify.color;
}
return this.activeFontColor;
},
listData: function() {
return this.list;
},
active_index: {
get: function() {
return this.value;
},
set: function(val) {
this.active = val;
this.$emit('input', val);
this.$emit('update:value', val);
// this.$emit('change', val);
this.$nextTick(function() {
this.setDefaultPos();
});
}
}
},
created() {
this.active_index = this.value;
this.width_item = this.width;
},
mounted() {
let t = this;
uni.$tm.sleep(30).then(()=>{
t.setInits();
})
},
methods: {
setInits() {
let t = this;
this.width_item = this.width;
this.$nextTick(function() {
uni.createSelectorQuery().in(t).select('.tm-segTabs-wkbody')
.boundingClientRect().select('#tm-segTabs-item-' + this.active).boundingClientRect()
.exec(function(tx) {
let p = tx[0]
if (!p) return;
t.body_height = p.height;
t.preventLeft = p.left;
let p1 = tx[1]
if (!p1) return;
t.active_barHeight = p1.height;
let left = 0;
if (t.width == 0) {
t.active_barWidth = p1.width;
left = p1.left;
} else {
t.active_barWidth = t.width_item
left = t.preventLeft + t.width_item * t.active;
}
let lsl = Math.floor((t.gutter / 2))
t.left = left - t.preventLeft - uni.upx2px(lsl);
t.aniOn = true;
})
});
},
returnKeyValue(item) {
if (typeof item == 'string') {
return item;
}
if (typeof item == 'object') {
return item[this.rangKey];
}
},
setDefaultPos() {
let t = this;
uni.createSelectorQuery().in(t).select('#tm-segTabs-item-' + this.active)
.boundingClientRect().exec(
function(p1) {
if (!p1[0]) return;
t.active_barHeight = p1[0].height;
t.active_barWidth = p1[0].width;
let lsl = Math.floor((t.gutter / 2))
t.left = p1[0].left - t.preventLeft - uni.upx2px(lsl);
})
},
clickItem(index, item) {
this.active_index = index;
this.$emit('change', index);
this.$nextTick(function() {
this.setDefaultPos();
});
}
}
};
</script>
<style lang="scss">
.tm-segTabs {
.tm-segTabs-wkbody {
z-index: 2;
.tm-segTabs-item {
transition: all 0.2s linear;
}
}
.tm-segTabs-bg {
.tm-segTabs-bg-bar {
&.aniOn {
transition: all 0.2s ease-in-out;
}
}
box-shadow: 0 0 3px 2px rgba(0, 0, 0, 0.02) inset;
}
}
</style>
@@ -0,0 +1,167 @@
<template>
<view class="tm-shareSheet">
<tm-poup :black="black_tmeme" @change="toogle" ref="pop" v-model="showpop" height="auto" >
<view class="tm-shareSheet-title pa-32 pb-32 relative" :class="[black_tmeme?'grey-darken-5':'white']">
<view class="text-size-s text-align-center text-grey-lighten-1">{{title}}</view>
<view class="tm-shareSheet-close rounded flex-center absolute" :class="black_tmeme?'grey-darken-3':'grey-lighten-3'">
<tm-icons @click="close" name="icon-times" size="24" :color="black_tmeme?'white':'grey'"></tm-icons>
</view>
</view>
<block v-for="(item2,index2) in actions" :key="index2">
<view class="py-32 pa-32">
<scroll-view scroll-x class="tm-shareSheet-wk flex-start">
<view @click="onclick(index,item)" v-for="(item,index) in item2" :key="index" class="tm-shareSheet-zuti ">
<view class="tm-shareSheet-zuti-img flex-center rounded" :style="{
background:black_tmeme?'#424242':item.bgcolor
}">
<tm-icons :black="black_tmeme" :name="item.icon" size="45" :color="black_tmeme?'white':item.color"></tm-icons>
</view>
<view class="tm-shareSheet-zuti-text text-size-xs my-10 text-grey">{{item.name}}</view>
</view>
</scroll-view>
</view>
<view v-if="(index2+1)%2!=0&&actions.length>1" class="my-5 mx-32" >
<view class=" border-b-1" :class="[black_tmeme?'bk':'']"></view>
</view>
</block>
<view class="safe--hei"></view>
</tm-poup>
</view>
</template>
<script>
/**
* 分享面板
* @description 动作面板,从底部弹出的操作菜单。
* @property {Boolean} black = [true|false] 默认:false,暗黑模式
* @property {Boolean} click-close = [true|false] 默认:true,点击图标后是否立即关闭弹层。
* @property {Boolean} value = [true|false] 默认:false,显示菜单,推荐使用v-model,使用value.sync达到双向绑定。
* @property {String} title = [] 默认:'请操作',弹出层的标题。
* @property {Array} actions = [] 默认:[],格式见文档,操作数组。{name:"微信",bgcolor:"#07c160",icon:"icon-weixin",color:"white"}
* @property {Function} change 点击项目时触发,返回:{index:项目索引,data:actions的对象数据}
* @property {Function} input 弹层显示和隐藏时,将会触发。
* @example <tm-shareSheet ></tm-shareSheet>
*/
import tmIcons from "@/tm-vuetify/components/tm-icons/tm-icons.vue"
import tmPoup from "@/tm-vuetify/components/tm-poup/tm-poup.vue"
export default {
components:{tmIcons,tmPoup},
name:"tm-shareSheet",
model:{
prop:'value',
event:'input'
},
props:{
value:{
type:Boolean,
default:false
},
black:{
type:Boolean,
default:null
},
clickClose:{
type:Boolean,
default:true
},
title:{
type:String,
default:'立即分享给好友吧'
},
actions:{
type:Array,
default:()=>{
return [
[
{name:"微信",bgcolor:"#07c160",icon:"icon-weixin",color:"white"},
{name:"朋友圈",bgcolor:"#04c887",icon:"icon-pengyouquan",color:"white"},
{name:"微博",bgcolor:"#e91e38",icon:"icon-weibo",color:"white"},
{name:"QQ",bgcolor:"#1dc0fd",icon:"icon-QQ",color:"white"},
{name:"小程序",bgcolor:"#1893fd",icon:"icon-xiaochengxu",color:"white"},
],
[
{name:"复制链接",bgcolor:"#f5f5f5",icon:"icon-connection",color:"grey-darken-1"},
{name:"分享海报",bgcolor:"#f5f5f5",icon:"icon-picture",color:"grey-darken-1"},
{name:"二维码",bgcolor:"#f5f5f5",icon:"icon-qrcode",color:"grey-darken-1"},
{name:"分享",bgcolor:"#f5f5f5",icon:"icon-share1",color:"grey-darken-1"}
]
]
}
}
},
watch:{
value:function(){
this.showpop = this.value;
}
},
data() {
return {
showpop:false,
};
},
computed: {
black_tmeme: function() {
if (this.black !== null) return this.black;
return this.$tm.vx.state().tmVuetify.black;
}
},
mounted() {
this.showpop = this.value;
},
methods: {
close(){
this.$refs.pop.close();
},
toogle(e){
let t = this;
if(e){
this.$nextTick(function(){
if(this.dataValue != this.defaultValue){
this.dataValue = this.defaultValue;
}
})
}
this.$emit('input',e);
this.$emit('update:value',e);
},
onclick(index,item){
this.$emit('change',{index:index,data:item})
if(this.clickClose === true){
this.$refs.pop.close();
}
}
},
}
</script>
<style lang="scss" scoped>
.tm-shareSheet-title {
.tm-shareSheet-close {
top: 32upx;
right: 32upx;
width: 50upx;
height: 50upx;
}
}
.tm-shareSheet-wk{
white-space: nowrap;
width: 100%;
.tm-shareSheet-zuti{
width: 140upx;
flex-shrink: 0;
text-align: center;
display: inline-block;
.tm-shareSheet-zuti-img{
margin: auto;
width: 100upx;
height: 100upx;
}
}
}
</style>
+180
View File
@@ -0,0 +1,180 @@
<template>
<!-- 基础容器 -->
<view
@click="$emit('click', $event)"
class="sheet fulled"
:style="[
bgColor?{backgroundColor: bgColor }:'',
widths?{width:widths}:'',
heights?{height:heights}:'',
]"
:class="[
text ? 'text' : '',
flat ? 'flat' : '',
'shadow-' + shadowthemeName + '-' + shadow,
'round-' + round,
black_tmeme ? 'bk' : '',
black_tmeme == 'true' || black_tmeme === true ? 'grey-darken-5' : (bgColor?'':color),
dense === true || dense == 'true' ? 'nom' : '',
'mx-'+margin[0],'my-'+margin[1],
'px-'+padding[0],'py-'+padding[1],
classs,
border ? 'border-a-1' : '',
outlined ? 'outlined' : ''
]"
>
<view class="fulled" v-if="showSheet">
<slot name="default"></slot>
</view>
</view>
</template>
<script>
/**
* 基础容器
* @description 基本是大部分组件的基础组件。
* @property {String | Boolean} black = [true|false] 暗黑模式。
* @property {String} classname = [] 自定认容器类
* @property {String|Number} round = [] 圆角
* @property {Array} margin = [] 外间距默认[32,32]
* @property {Array} padding = [] 内间距默认[32,32]
* @property {Boolean|String} dense = [] 默认false,去除内部和外部间距。
* @property {String|Number} width = [100%|auto] 宽度数字时单位为upx.可以是百分比
* @property {String|Number} height = [100%|auto] 宽度数字时单位为upx.可以是百分比
* @property {String} color = [white|blue|primary] 主题颜色名称。默认:white
* @property {String} bgColor = [] 自定义背景颜色rgb,rgba,#0000等格式。
* @property {String|Number} shadow = [5|10] 投影大小
* @property {Boolean|String} border = [true|false] 是否开启边线
* @property {Boolean|String} flat = [true|false] 是否开启扁平模式。
* @property {Boolean|String} text = [true|false] 是否开启文本模式
* @example <tm-sheet :margin="[32,32]" >9</tm-sheet>
*/
export default {
props: {
black: {
type: String | Boolean,
default: null
},
classname: {
type: String,
default: ''
},
round: {
type: String | Number,
default: '4'
},
margin:{
type:Array,
default:()=>{return [32,32]; }
},
padding:{
type:Array,
default:()=>{return [32,32]; }
},
dense: {
type: Boolean|String,
default: false
},
width: {
type: String | Number,
default: 'auto'
},
height: {
type: String | Number,
default: 'auto'
},
// 主题颜色名称。
color: {
type: String,
default: 'white'
},
// 自定义背景色。
bgColor: {
type: String,
default: ''
},
shadow: {
type: String | Number,
default: 5
},
border: {
type: Boolean|String,
default: false
},
outlined: {
type: Boolean|String,
default: false
},
flat: {
type: Boolean,
default: false
},
// 是否为文本模式,即减淡背景颜色。
text: {
type: String | Boolean,
default: false
}
},
computed: {
// 投影的颜色名字。
shadowthemeName: function() {
if (!this.color) return 'none';
return this.color.split('-')[0];
},
classs: function() {
return ' ' + this.classname + ' ';
},
widths: function() {
if (typeof this.width === 'string') {
if (/([rpx|upx|rem|em|vx|vh|px|%]|auto)$/.test(this.width)) {
return this.width;
}
return uni.upx2px(parseInt(this.width)) + 'px';
}
if (typeof this.width == 'number') return uni.upx2px(this.width) + 'px';
},
heights: function() {
if (typeof this.height === 'string') {
if (/([rpx|upx|rem|em|vx|vh|px|%]|auto)$/.test(this.height)) {
return this.height;
}
return uni.upx2px(parseInt(this.height)) + 'px';
}
if (typeof this.height == 'number') return uni.upx2px(this.height) + 'px';
},
black_tmeme:function(){
if(this.black!==null) return this.black;
return this.$tm.vx.state().tmVuetify.black;
}
},
data() {
return {
showSheet:true,
};
},
created() {
// #ifdef APP-VUE || APP-PLUS || MP
this.showSheet = false;
// #endif
},
mounted() {
let t= this;
// #ifdef APP-VUE || APP-PLUS || MP
setTimeout(function() {
t.showSheet = true;
}, 30);
// #endif
}
};
</script>
<style lang="scss" scoped>
.nom{
margin: 0 !important;
padding: 0 !important;
}
</style>
@@ -0,0 +1,191 @@
<template>
<view class="tm-signBoard" >
<!-- @touchmove.stop.prevent="stopEvent" -->
<!-- #ifdef H5 || APP-VUE || APP-PLUS -->
<canvas
:style="{
width: ctx_w + 'px',
height: ctx_h + 'px'
}"
:disable-scroll="true"
@touchstart="touchstart"
@touchmove.stop.prevent="touchsmove"
@touchend="touchsend"
@mousedown="touchstart"
@mousemove.stop.prevent="touchsmove"
@mouseup="touchsend"
:canvas-id="uid"
:id="uid"
></canvas>
<!-- #endif -->
<!-- #ifdef MP -->
<canvas
:style="{
width: ctx_w + 'px',
height: ctx_h + 'px'
}"
:disable-scroll="true"
@touchstart="touchstart"
@touchmove="touchsmove"
@touchend="touchsend"
@mousedown="touchstart"
@mousemove.stop.prevent="touchsmove"
@mouseup="touchsend"
canvas-id="tm-signBoard-id"
id="tm-signBoard-id"
></canvas>
<!-- #endif -->
</view>
</template>
<script>
/**
* 手写签名板
* @description 手写签名板,笔峰效果,通过ref执行clear和save功能。
* @property {Number} line-width = [] 默认:6,线宽度。
* @property {String} line-color = [] 默认:'#000000',线颜色
* @property {Number} width = [] 默认:0, 画布宽,默认使用父组件的宽高。单位upx
* @property {Number} height = [] 默认:400 画布高,单位upx
* @example <tm-signBoard></tm-signBoard>
*/
export default {
name: 'tm-signBoard',
props: {
lineWidth: {
type: Number,
default: 6
},
lineColor: {
type: String,
default: '#000000'
},
// 默认使用父组件的宽高。单位upx
width: {
type: Number,
default: 0
},
// 单位upx
height: {
type: Number,
default: 400
}
},
data() {
return {
ctx: null,
uid:"f",
ctx_w: 0,
ctx_h: 0
};
},
created() {
// #ifdef H5 || APP-VUE || APP-PLUS
this.uid = this.$tm.guid();
// #endif
},
async mounted() {
let qins = await this.$Querey('.tm-signBoard',this,50).catch(e => {});
this.ctx_w = uni.upx2px(this.width) || qins[0].width;
this.ctx_h = uni.upx2px(this.height) || qins[0].height;
this.$nextTick(function() {
// #ifdef H5 || APP-VUE || APP-PLUS
this.ctx = uni.createCanvasContext(this.uid, this);
// #endif
// #ifdef MP
this.ctx = uni.createCanvasContext('tm-signBoard-id', this);
// #endif
this.handwriting = new uni.$tm.HandwritingSelf(this.ctx,this.ctx_w,this.ctx_h,this.lineWidth,this.lineColor)
});
},
methods: {
touchstart(event) {
event.preventDefault()
event.stopPropagation()
if (event.type.indexOf('mouse')==-1&&event.changedTouches.length==1) {
var touch = event.changedTouches[0];
this.handwriting.down(touch.x,touch.y);
}else{
this.handwriting.down(event.pageX-event.currentTarget.offsetLeft,event.pageY-event.currentTarget.offsetTop);
}
},
touchsmove(event) {
event.preventDefault()
event.stopPropagation()
if (event.type.indexOf('mouse')==-1&&event.changedTouches.length == 1) {
var touch = event.changedTouches[0];
this.handwriting.move(touch.x,touch.y);
}else{
this.handwriting.move(event.pageX-event.currentTarget.offsetLeft,event.pageY-event.currentTarget.offsetTop);
}
},
touchsend(event) {
event.preventDefault()
event.stopPropagation()
if (event.type.indexOf('mouse')==-1&&event.changedTouches.length == 1) {
var touch = event.changedTouches[0];
this.handwriting.up(touch.x,touch.y);
}else{
this.handwriting.up(event.pageX-event.currentTarget.offsetLeft,event.pageY-event.currentTarget.offsetTop);
}
},
clear() {
if (!this.ctx) return;
this.ctx.clearRect(0, 0, this.ctx_w, this.ctx_h);
this.ctx.draw(false);
this.$nextTick(function() {
// #ifdef H5
this.ctx = uni.createCanvasContext(this.uid, this);
// #endif
// #ifndef H5
this.ctx = uni.createCanvasContext('tm-signBoard-id', this);
// #endif
this.handwriting = new uni.$tm.HandwritingSelf(this.ctx,this.ctx_w,this.ctx_h,this.lineWidth,this.lineColor)
});
},
save() {
let t = this;
return new Promise((su, fa) => {
if (!this.ctx) {
fa('ctx未初始化');
return;
}
let uid =this.uid;
// #ifndef H5
uid='tm-signBoard-id'
// #endif
uni.canvasToTempFilePath(
{
x: 0,
y: 0,
width: t.ctx_w,
height: t.ctx_h,
canvasId: uid,
success: function(res) {
// 在H5平台下,tempFilePath 为 base64
su(res.tempFilePath);
},
fail: res => {
fa(res);
}
},
);
});
},
stopEvent(event) {
event.preventDefault()
event.stopPropagation()
}
}
};
</script>
<style lang="scss"></style>
@@ -0,0 +1,94 @@
<template>
<view class="tm-skeleton fulled">
<view v-if="model=='card'">
<view :class="[`round-${round}`,$tm.vx.state().tmVuetify.black ? 'black bk' : '',color]" class=" tm-skeleton-card"></view>
<view :class="[`round-${round}`,$tm.vx.state().tmVuetify.black ? 'black bk' : '',color]" class="tm-skeleton-item my-24"></view>
<view :class="[`round-${round}`,$tm.vx.state().tmVuetify.black ? 'black bk' : '',color]" class="tm-skeleton-item my-24" style="width: 60%;"></view>
<view :class="[`round-${round}`,$tm.vx.state().tmVuetify.black ? 'black bk' : '',color]" class="tm-skeleton-item my-24" style="width: 30%;"></view>
</view>
<view v-if="model=='cardActions'">
<view :class="[`round-${round}`,$tm.vx.state().tmVuetify.black ? 'black bk' : '',color]" class=" tm-skeleton-card"></view>
<view :class="[`round-${round}`,$tm.vx.state().tmVuetify.black ? 'black bk' : '',color]" class="tm-skeleton-item my-24"></view>
<view :class="[`round-${round}`,$tm.vx.state().tmVuetify.black ? 'black bk' : '',color]" class="tm-skeleton-item " ></view>
<view class="flex-between">
<view :class="[`round-${round}`,$tm.vx.state().tmVuetify.black ? 'black bk' : '',color]" class="tm-skeleton-item my-24" style="width: 40%;"></view>
<view :class="[`round-${round}`,$tm.vx.state().tmVuetify.black ? 'black bk' : '',color]" class="tm-skeleton-item my-24" style="width: 15%;"></view>
</view>
</view>
<view v-if="model=='list'">
<view :class="[`round-${round}`,$tm.vx.state().tmVuetify.black ? 'black bk' : '',color]" class="tm-skeleton-item my-24" style="width: 60%;"></view>
<view :class="[`round-${round}`,$tm.vx.state().tmVuetify.black ? 'black bk' : '',color]" class="tm-skeleton-item my-24" ></view>
<view :class="[`round-${round}`,$tm.vx.state().tmVuetify.black ? 'black bk' : '',color]" class="tm-skeleton-item my-24" ></view>
<view :class="[`round-${round}`,$tm.vx.state().tmVuetify.black ? 'black bk' : '',color]" class="tm-skeleton-item my-24" style="width: 30%;"></view>
</view>
<view v-if="model=='listAvatr'" class="flex-top-start">
<view style="width: 100rpx;height: 100rpx;" :class="[$tm.vx.state().tmVuetify.black ? 'black bk' : '',color]" class="rounded flex-shrink mr-32"></view>
<view class="fulled">
<view :class="[`round-${round}`,$tm.vx.state().tmVuetify.black ? 'black bk' : '',color]" class="tm-skeleton-item mb-24" style="width: 60%;"></view>
<view :class="[`round-${round}`,$tm.vx.state().tmVuetify.black ? 'black bk' : '',color]" class="tm-skeleton-item my-24" ></view>
<view :class="[`round-${round}`,$tm.vx.state().tmVuetify.black ? 'black bk' : '',color]" class="tm-skeleton-item my-24" ></view>
<view :class="[`round-${round}`,$tm.vx.state().tmVuetify.black ? 'black bk' : '',color]" class="tm-skeleton-item my-24" style="width: 30%;"></view>
</view>
</view>
<view v-if="model=='grid'" class="flex-top-start flex-wrap">
<view v-for="(item,index) in 16" :key="index" style="width: 25%;">
<view :class="[`round-${round}`,$tm.vx.state().tmVuetify.black ? 'black bk' : '',color]" class="tm-skeleton-item ma-12" style="height: 80rpx;"></view>
</view>
</view>
</view>
</template>
<script>
/**
* 骨架加载器
* @property {String} model = [card|cardActions|list|listAvatr|grid] 默认card类型
* @property {Number} round = [] 默认6,圆角
* @property {String} color = [] 默认grey-lighten-3,背景色
*/
export default {
name:"tm-skeleton",
props:{
model:{
type:String,
default:'card',//card,cardActions,list,listAvatr,listAvatr,grid
},
round:{
type:Number|String,
default:6
},
color:{
type:String,
default:'grey-lighten-3'
}
},
data() {
return {
};
}
}
</script>
<style lang="scss" scoped>
.tm-skeleton{
animation: ssy 1s ease infinite;
.tm-skeleton-card{
height: 300rpx;
}
.tm-skeleton-item{
height: 40rpx;
}
}
@keyframes ssy{
0%{
opacity: 0.4;
}
50%{
opacity: 1;
}
100%{
opacity: 0.4;
}
}
</style>
@@ -0,0 +1,469 @@
<template>
<view class="fulled tm-slider " :class="[step > 0 ? 'pb-36' : 'pb-24']"
:style="{ height: vertical ? heightpx + 'px' : 'auto', width: vertical ? '5px' : '100%'}">
<view class=" tm-slider-id fulled " :class="[vertical ? 'vertical' : 'flex-between']">
<!-- 左边label -->
<view v-if="showLeft" class="label_slider left flex-col text-size-xs text-grey-darken-1"
:class="[vertical ? '' : 'flex-center']">
<slot name="left" :data="{ value: value, color: color_tmeme, icon: leftIcon,max:max }">
<tm-icons v-if="leftIcon && !vertical" size="28" :name="leftIcon" :color="color_tmeme"></tm-icons>
<view class="text-size-xs text-grey-darken-1 ">{{ value }}</view>
</slot>
</view>
<!-- 条子内容 -->
<view class="slider_id "
:style="{ width: vertical ? 'auto' : sliderWidth + 'px', height: vertical ? sliderWidth + 'px' : '5px' }">
<view class="slider_id_bg round-10" :class="[bgColor,black_tmeme?'bk':'']"></view>
<view
:style="{ width: vertical ? '100%' : active_width + '%', height: vertical ? active_width + '%' : '100%' }"
class="slider_id_active round-10 " :class="[color_tmeme,black_tmeme?'bk':'']"></view>
<view :style="{ left: vertical ? 0 : barLeft + 'px', top: vertical ? barLeft + 'px' : 0 }"
@touchcancel="barEnd" @touchstart="barStart" @touchend="barEnd" @touchmove.stop.prevent="barMove"
@mouseleave="barEnd" @mousedown="barStart" @mouseup="barEnd" @mousemove.stop.prevent="barMove"
class="slider_bar border-white-a-2 rounded" :class="[color_tmeme,black_tmeme?'bk':'', ` shadow-${color_tmeme}-10`]">
<view v-if="showTopTips||showTip" class="slider_bar_showbg border-white-a-1" :class="[color_tmeme,black_tmeme?'bk':'']"></view>
<view v-if="showTopTips||showTip" class="slider_bar_num text-size-xs flex-center" :class="[color_tmeme,black_tmeme?'bk':'']">
<slot name="tips" :data="value">{{ value }}</slot>
</view>
</view>
<!-- 步长刻度尺 -->
<view class="kdc_wk ">
<view v-for="(item, index) in kdcNum" :key="index"
:style="{ left: vertical ? 0 : item.left, top: vertical ? item.left : 0 }" :class="[color_tmeme,black_tmeme?'bk':'']"
class="kdc_wk_item rounded border-white-a-1 ">
<view class="kdc_wk_item_label pl-0" :class="[vertical ? 'pl-24' : 'pa-24']">
<text class="text-size-xs text-grey-darken-1 ">{{ item.kedu }}</text>
</view>
</view>
</view>
</view>
<!-- 右边label -->
<view v-if="showRight" class="label_slider right flex-col text-size-xs text-grey-darken-1"
:class="[vertical ? 'flex-end' : 'flex-center']">
<slot name="right" :data="{ value: value, color: color_tmeme, icon: rightIcon,max:max }">
<tm-icons v-if="rightIcon && !vertical" size="28" :name="rightIcon" :color="color_tmeme"></tm-icons>
<view class="text-size-xs text-grey-darken-1 ">{{ max }}</view>
</slot>
</view>
</view>
</view>
</template>
<script>
/**
* 滑块
* @property {Number} value = [0] 赋值,如果需要同步使用value.sync推荐使用v-model绑定。
* @property {Boolean} vertical = [true|false] 默认:false,是否启用竖向模式。
* @property {Number} height = [200] 默认:200,竖向模式时才有作用。
* @property {Number} width = [] 默认:0,横向时起作用,如果为0自动获取外层宽度。
* @property {Boolean} show-left = [true|false] 默认:false,显示左边数据。
* @property {Boolean} show-right = [true|false] 默认:false,显示右边数据。
* @property {Number} max = [] 默认:100,显示的最大刻度。
* @property {Number} value-diog = [] 默认:0,取值小数点后几位
* @property {Boolean} disabled = [true|false] 默认:false, 是否禁用。
* @property {Boolean} showTip = [true|false] 默认:false, 始终显示进度标签。
* @property {Number} step = [10|20] 默认:0, 步长,设置请尽量大于20.太小滑动容易过小出问题。
* @property {String} color = [primary] 默认:primary, 主题颜色名称。
* @property {String} bg-color = [grey-lighten-2] 默认:grey-lighten-2, 底部不活动的背景色,颜色名称。
* @property {String} left-icon = [] 默认:icon-minus, 左边图标
* @property {String} right-icon = [] 默认:icon-plus, 右边图标
* @property {String} name = [] 默认:'',提交表单时的的字段名称标识
* @property {Function} change 同v-model和value相等的参数,变动时触发。
* @example <tm-slider v-model="checked" ></tm-slider>
*
*/
import tmIcons from "@/tm-vuetify/components/tm-icons/tm-icons.vue"
export default {
name:'tm-slider',
components:{tmIcons},
model:{
prop:'value',
event:'input'
},
props: {
//提交表单时的的字段名称
name:{
type:String,
default:''
},
vertical: Boolean, //是否启用竖向模式。需要和height配合使用。
// 单位upx.
height: {
type: Number,
default: 200
},
width: {
type: Number,
default: 0
},
showLeft: Boolean, //显示左边
showRight: Boolean, //显示右边
// 最大刻度。
max: {
type: Number,
default: 100
},
// 默认的数据。不能大于max.,使用.sync修饰。双向绑定数据。
value: {
type: Number,
default: 0
},
// 取值小数点后几位。默认为0
valueDiog: {
type: Number,
default: 0
},
// 是否禁用。
disabled: Boolean,
//步长。默认为0,设置请尽量大于0.太小滑动容易过小出问题。
step: {
type: Number,
default: 0
},
// 主题颜色名称。
color: {
type: String,
default: 'primary'
},
// 底部不活动的背景色,颜色名称。。
bgColor: {
type: String,
default: 'grey-lighten-2'
},
// 左边图标名称。
leftIcon: {
type: String | Boolean,
default: 'icon-minus'
},
// 右边图标名称。
rightIcon: {
type: String | Boolean,
default: 'icon-plus'
},
// 始终显示进度提示窗
showTip: {
type: Boolean,
default: false
},
// 跟随主题色的改变而改变。
fllowTheme:{
type:Boolean|String,
default:true
},
black: {
type: Boolean,
default: null
},
},
watch: {
value: function(val) {
let rdl = this.sliderWidth * (Math.abs(val) / Math.abs(this.max));
this.barLeft = rdl >= this.sliderWidth || rdl < 0 ? this.sliderWidth : rdl;
},
barLeft: function() {
let value = (this.barLeft / this.sliderWidth) * this.max;
if (!isNaN(value)) {
if (this.valueDiog > 0) {
let s = value.toString();
let st = s.split('.');
let ruslt = 0;
if (st.length > 1) {
ruslt = parseFloat(st[0] + '.' + st[1].substring(0, this.valueDiog));
} else {
ruslt = parseFloat(s);
}
this.$emit('update:value', ruslt);
this.$emit('input', ruslt);
this.$emit('change', ruslt);
} else {
this.$emit('update:value', parseInt(value));
this.$emit('input', parseInt(value));
this.$emit('change', parseInt(value));
}
}
}
},
computed: {
black_tmeme: function() {
if (this.black !== null) return this.black;
return this.$tm.vx.state().tmVuetify.black;
},
// 计算刻度尺的数量
kdcNum: function() {
if (Math.abs(this.step) <= 0) return [];
let jlv = Math.abs(this.step) / Math.abs(this.max);
let left_width = jlv * (this.sliderWidth - 0);
let kd_num = parseInt(this.sliderWidth / left_width);
let ar = [];
for (let i = 0; i <= kd_num; i++) {
ar.push({
index: i, //顺序
left: i * left_width + 'px', //距离左边距离
kedu: this.step * i //当前刻度数量。
});
}
return ar;
},
//比例
active_width: function() {
return (this.barLeft / this.sliderWidth) * 100;
},
heightpx: function() {
return uni.upx2px(this.height);
},
color_tmeme:function(){
if(this.$tm.vx.state().tmVuetify.color!==null&&this.$tm.vx.state().tmVuetify.color && this.fllowTheme){
return this.$tm.vx.state().tmVuetify.color;
}
return this.color;
},
},
data() {
return {
sliderWidth: 0,
x: 0,
startMove: false,
barLeft: 0,
isError: false,
showTopTips: false
};
},
async mounted() {
this.$nextTick(async function() {
await this.getwidth();
if (Math.abs(this.value) > Math.abs(this.max)) {
this.isError = true;
return;
}
let rdl = this.sliderWidth * (Math.abs(this.value) / Math.abs(this.max));
this.barLeft = rdl >= this.sliderWidth || rdl < 0 ? this.sliderWidth : rdl;
});
},
methods: {
barStart(e) {
if (this.disabled || this.isError) return;
if(e.type.indexOf('mouse')>-1&&e.changedTouches.length==0){
this.x = (this.vertical ? e.pageY : e.pageX) - this.barLeft;
this.startMove = true;
this.showTopTips = true;
}else{
if (e.changedTouches.length > 0) {
this.x = (this.vertical ? e.changedTouches[0].pageY : e.changedTouches[0].pageX) - this.barLeft;
this.startMove = true;
this.showTopTips = true;
}
}
this.$nextTick(function(){
this.$emit('start',this.value)
})
},
barEnd(e) {
if (this.disabled || this.isError) return;
this.startMove = false;
this.showTopTips = false;
this.$nextTick(function(){
this.$emit('end',this.value)
})
},
barMove(e) {
if (this.disabled || this.isError) return;
if (!this.startMove) return;
let left = 0;
if(e.type.indexOf('mouse')>-1&&e.changedTouches.length==0){
left = (this.vertical ? e.pageY : e.pageX) - this.x;
}else{
left = (this.vertical ? e.changedTouches[0].pageY : e.changedTouches[0].pageX) - this.x;
}
if (left <= 0) {
this.barLeft = 0;
return;
}
if (left >= this.sliderWidth) {
this.barLeft = this.sliderWidth;
return;
}
let nowStep = parseInt(Math.abs(left - this.barLeft));
let bdi = parseInt((this.step / this.max) * this.sliderWidth);
if (nowStep >= bdi) {
// 每一小段的值。
if(this.step!==0){
let kedud = this.sliderWidth / (this.max / this.step);
this.barLeft = Math.round(left / kedud) * kedud;
}else{
this.barLeft = left;
}
}
},
async getwidth() {
let res = await this.$Querey('.tm-slider-id', this).catch(e=>{});
res[0].width = res[0].width||uni.upx2px(this.width);
res[0].height = res[0].height||uni.upx2px(this.height);
if (this.showLeft === false && this.showRight === false) {
this.sliderWidth = this.vertical ? res[0].height : res[0].width;
return;
}
if (this.showLeft !== false && this.showRight !== false) {
this.sliderWidth = (this.vertical ? res[0].height : res[0].width) - uni.upx2px(this.vertical ? 50 :
100) * 2;
return;
}
if (this.showLeft === true || this.showRight === true) {
this.sliderWidth = (this.vertical ? res[0].height : res[0].width) - uni.upx2px(this.vertical ? 50 :
100);
}
}
}
};
</script>
<style lang="scss" scoped>
.label_slider {
flex-shrink: 0;
&.left,
&.right {
width: 100upx;
}
}
.tm-slider-id {
width: 100%;
.slider_id {
position: relative;
height: 4px;
.slider_id_bg {
width: 100%;
height: 100%;
}
.slider_id_active {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
z-index: 5;
}
.slider_bar {
position: absolute;
width: 40upx;
height: 40upx;
left: 0;
top: 0;
margin-top: -22upx;
z-index: 10;
.slider_bar_showbg {
position: absolute;
width: 50upx;
height: 50upx;
border-radius: 30upx;
border-bottom-left-radius: 5upx;
transform: rotate(-45deg);
bottom: 60upx;
left: -9upx;
animation: roteScaleTop_BG 0.3s ease-in-out;
}
.slider_bar_num {
position: absolute;
width: 50upx;
height: 50upx;
border-radius: 50upx;
left: -8upx;
bottom: 60upx;
line-height: 50upx;
text-align: center;
background: transparent !important;
animation: roteScaleTop 0.3s ease-in-out;
}
}
.kdc_wk {
width: 100%;
height: 100%;
position: absolute;
z-index: 6;
left: 0;
top: -2upx;
.kdc_wk_item {
width: 10upx;
height: 10upx;
position: absolute;
z-index: 7;
text-align: center;
.kdc_wk_item_label {
margin-left: -100%;
}
}
}
}
&.vertical {
height: 100%;
.label_slider {
flex-shrink: 0;
&.left,
&.right {
width: auto;
height: 50upx;
}
}
.slider_bar {
margin-left: -18upx;
}
.kdc_wk {
top: 0upx;
.kdc_wk_item {
.kdc_wk_item_label {
margin-left: 100%;
margin-top: -15upx;
}
}
}
}
}
@keyframes roteScaleTop_BG{
0%{
transform: scale(0.9) translateY(20rpx) rotate(-45deg);
opacity: 0;
}
100%{
transform: scale(1) translateY(0rpx) rotate(-45deg);
opacity: 1;
}
}
@keyframes roteScaleTop{
0%{
transform: scale(0.9) translateY(20rpx);
opacity: 0;
}
100%{
transform: scale(1) translateY(0rpx);
opacity: 1;
}
}
</style>
@@ -0,0 +1,188 @@
<template>
<view class="tm-sliderNav fulled-height">
<scroll-view scroll-y :style="{
height:activeHeight+'px',
width:activeWidth+'px',
}">
<tm-listitem :disabled="item['disabled']==true?true:false" :black="black_tmeme"
@click="change(index)"
v-for="(item,index) in formaData" :key='index'
:color="activeIndex==index?(text?' text ' + color_tmeme:'white'):bgColor"
:show-right-icon="false"
:margin="[0,0]"
:padding="[10,32]"
:fontSize="fontSize"
:class-style="activeIndex==index?`border-${color_tmeme}-l-2`:`border-${bgColor}-l-2`"
round="0" shadow="0"
>
<tm-badges color='red' :fllowTheme="false" :offset="[12,-10]" v-if="item.dot>0&&dot" :dot="false" :label="item.dot"></tm-badges>
<view class="" >
<text :class="[`text-${activeIndex==index?color_tmeme:fontColor}`,item['disabled']?'opacity-3':'']">{{item[rangKey]}}</text>
</view>
</tm-listitem>
</scroll-view>
</view>
</template>
<script>
/**
* 侧边导航
* @property {Number|String} height = [] 默认0,单位upx,自动使用父组件的高度。
* @property {Number|String} width = [] 默认190,单位upx,宽度
* @property {Number} round = [] 默认4,单位upx,圆角
* @property {Array} list = [] 默认[],数据结构对象,必须需要含有唯一标识符id即可。[{title:'苏州'}],,可以含有dot,显示角标。每一个类目和子类可以含有disabled值,用来禁用类目或者选项。
* @property {String} rang-key = [] 默认'title',数据结构对象中,需要展示标签的key
* @property {String} color = [] 默认 primary,主题色名
* @property {String} bg-color = [] 默认 grey-lighten-5,主题色名未选择时的背景色。
* @property {String} font-color = [] 默认 grey,未选中时的文字颜色
* @property {String} font-size = [] 默认 28,项目文字大小单位rpx
* @property {Boolean} black = [] 默认 false,是否暗黑模式。
* @property {Boolean} dot = [] 默认 false,是否显示角标数字,当前选中的数量。
* @property {Boolean} text = [] 默认 false,使用主题文本色为高亮色。
* @property {Function} change 每次选择变化都会触发全局change,
* @example <tm-sliderNav :text="true" :list="list"></tm-sliderNav>
*/
import tmListitem from "@/tm-vuetify/components/tm-listitem/tm-listitem.vue"
import tmGrouplist from "@/tm-vuetify/components/tm-grouplist/tm-grouplist.vue"
import tmBadges from "@/tm-vuetify/components/tm-badges/tm-badges.vue"
export default {
components:{tmListitem,tmGrouplist,tmBadges},
name:"tm-sliderNav",
props:{
// 高度,默认为0时,自动使用父组件的高度.单位upx
height: {
type: String | Number,
default: 0
},
//单位upx
width: {
type: String | Number,
default: 190
},
//单位upx
round: {
type: Number,
default: 4
},
// 对象数组
list:{
type:Array,
default:()=>{
return [];
}
},
// 显示标签的key.
rangKey:{
type:String,
default:'title'
},
// 主题色名
color:{
type:String,
default:'primary'
},
fontColor:{
type:String,
default:'grey-darken-1'
},
black:{
type:Boolean|String,
default:null
},
// 是否显示角标数字当前选中的数量。
dot:{
type:Boolean,
default:true
},
// 使用主题文本色为高亮色。
text:{
type:Boolean,
default:false
},
fontSize:{
type:String|Number,
default:'28'
},
// 未选择时的背景色。
bgColor:{
type:String,
default:'grey-lighten-5'
},
// 跟随主题色的改变而改变。
fllowTheme:{
type:Boolean|String,
default:true
}
},
watch:{
list:{
deep:true,
handler(){
this.dataList = this.list;
}
}
},
data() {
return {
activeHeight:0,
activeWidth:0,
activeIndex:0,
formaData:[]
};
},
computed:{
dataList:{
get:function(){
return this.formaData
},
set:function(val){
let t = this;
let p = [...val];
for(let j=0;j<p.length;j++){
if(!p[j].hasOwnProperty('dot')){
p[j]['dot'] = 0;
}
}
this.formaData = p;
}
},
black_tmeme: function() {
if (this.black !== null) return this.black;
return this.$tm.vx.state().tmVuetify.black;
},
color_tmeme:function(){
if(this.$tm.vx.state().tmVuetify.color!==null&&this.$tm.vx.state().tmVuetify.color && this.fllowTheme){
return this.$tm.vx.state().tmVuetify.color;
}
return this.color;
},
},
mounted() {
let t = this;
this.$nextTick(async function() {
this.activeHeight = uni.upx2px(this.height);
if (!this.activeHeight) {
let wsz = await this.$Querey(".tm-sliderNav",this).catch(e=>{})
this.activeHeight = wsz[0].height||250;
this.activeWidth =uni.upx2px(this.width)||100;
}
this.dataList = this.list;
});
},
methods: {
change(index) {
this.activeIndex=index;
this.$emit('change',index);
}
},
}
</script>
<style lang="scss">
</style>
@@ -0,0 +1,572 @@
<template>
<view class="d-block tm-slider " :class="[step > 0 ? 'pb-36' : 'pb-24']"
:style="{ height: vertical ? heightpx + 'px' : 'auto', width: vertical ? '5px' : 'auto' }">
<view class=" tm-slider-id " :class="[vertical ? 'vertical' : 'flex-between']">
<!-- 左边label -->
<view v-if="showLeft" class="label_slider left flex-col text-size-xs text-grey-darken-1"
:class="[vertical ? '' : 'flex-center']">
<slot name="left" :data="{ value: value, color: color_tmeme, icon: leftIcon,max:max }">
<tm-icons v-if="leftIcon && !vertical" size="28" :name="leftIcon" :color="color_tmeme"></tm-icons>
<view class="text-size-xs text-grey-darken-1 ">{{ value[0] }}</view>
</slot>
</view>
<!-- 条子内容 -->
<view class="slider_id "
:style="{ width: vertical ? 'auto' : sliderWidth + 'px', height: vertical ? sliderWidth + 'px' : '5px' }">
<view class="slider_id_bg round-10" :class="[bgColor,black_tmeme?'bk':'']"></view>
<view
:style="{left:vertical?'0':bar_minLeft+'px',
top:vertical?bar_minLeft+'px':0,
width: vertical ? '100%' : active_width + '%', height: vertical ? active_width + '%' : '100%' }"
class="slider_id_active round-10 " :class="[color_tmeme,black_tmeme?'bk':'']"></view>
<view :style="{ left: vertical ? 0 : barLeft + 'px', top: vertical ? barLeft + 'px' : 0 }"
@touchcancel="barEnd" @touchstart="barStart" @touchend="barEnd" @touchmove.stop.prevent="barMove"
@mouseleave="barEnd" @mousedown="barStart" @mouseup="barEnd" @mousemove.stop.prevent="barMove"
class="slider_bar border-white-a-2 rounded" :class="[color_tmeme,black_tmeme?'bk':'', ` shadow-${color_tmeme}-10`]">
<view v-if="showTopTips||showTip" class="slider_bar_showbg border-white-a-1" :class="[color_tmeme,black_tmeme?'bk':'']">
</view>
<view v-if="showTopTips||showTip" class="slider_bar_num text-size-xs flex-center" :class="[color_tmeme,black_tmeme?'bk':'']">
<slot name="tipsLeft" :data="value">{{ value[0] }}</slot>
</view>
</view>
<view :style="{ left: vertical ? 0 : barLeft_1 + 'px', top: vertical ? barLeft_1 + 'px' : 0 }"
@touchcancel="barEnd_1" @touchstart="barStart_1" @touchend="barEnd_1" @touchmove.stop.prevent="barMove_1"
@mouseleave="barEnd_1" @mousedown="barStart_1" @mouseup="barEnd_1" @mousemove.stop.prevent="barMove_1"
class="slider_bar border-white-a-2 rounded" :class="[color_tmeme,black_tmeme?'bk':'', ` shadow-${color_tmeme}-10`]">
<view v-if="showTopTips||showTip" class="slider_bar_showbg border-white-a-1" :class="[color_tmeme,black_tmeme?'bk':'']">
</view>
<view v-if="showTopTips||showTip" class="slider_bar_num text-size-xs flex-center" :class="[color_tmeme,black_tmeme?'bk':'']">
<slot name="tipsRight" :data="value">{{ value[1] }}</slot>
</view>
</view>
<!-- 步长刻度尺 -->
<view class="kdc_wk ">
<view v-for="(item, index) in kdcNum" :key="index"
:style="{ left: vertical ? 0 : item.left, top: vertical ? item.left : 0 }" :class="[color_tmeme,black_tmeme?'bk':'']"
class="kdc_wk_item rounded border-white-a-1 ">
<view class="kdc_wk_item_label pl-0" :class="[vertical ? 'pl-24' : 'pa-24']">
<text class="text-size-xs text-grey-darken-1 ">{{ item.kedu }}</text>
</view>
</view>
</view>
</view>
<!-- 右边label -->
<view v-if="showRight" class="label_slider right flex-col text-size-xs text-grey-darken-1"
:class="[vertical ? 'flex-end' : 'flex-center']">
<slot name="right" :data="{ value: value, color: color_tmeme, icon: rightIcon,max:max }">
<tm-icons v-if="rightIcon && !vertical" size="28" :name="rightIcon" :color="color_tmeme"></tm-icons>
<view class="text-size-xs text-grey-darken-1 ">{{ max }}</view>
</slot>
</view>
</view>
</view>
</template>
<script>
/**
* 滑块
* @property {Number} value = [0] 赋值,如果需要同步使用value.sync推荐使用v-model绑定。
* @property {Boolean} vertical = [true|false] 默认:false,是否启用竖向模式。
* @property {Number} height = [200] 默认:200,竖向模式时才有作用。
* @property {Number} width = [] 默认:0,横向时起作用,如果为0自动获取外层宽度。
* @property {Boolean} show-left = [true|false] 默认:false,显示左边数据。
* @property {Boolean} show-right = [true|false] 默认:false,显示右边数据。
* @property {Number} max = [] 默认:100,显示的最大刻度。
* @property {Number} value-diog = [] 默认:0,取值小数点后几位
* @property {Boolean} disabled = [true|false] 默认:false, 是否禁用。
* @property {Boolean} showTip = [true|false] 默认:false, 始终显示进度标签。
* @property {Number} step = [10|20] 默认:0, 步长,设置请尽量大于20.太小滑动容易过小出问题。
* @property {String} color = [primary] 默认:primary, 主题颜色名称。
* @property {String} bg-color = [grey-lighten-2] 默认:grey-lighten-2, 底部不活动的背景色,颜色名称。
* @property {String} left-icon = [] 默认:icon-minus, 左边图标
* @property {String} right-icon = [] 默认:icon-plus, 右边图标
* @property {Function} change 同v-model和value相等的参数,变动时触发。
* @example <tm-slider v-model="checked" ></tm-slider>
*
*/
import tmIcons from "@/tm-vuetify/components/tm-icons/tm-icons.vue"
export default {
components:{tmIcons},
name: "tm-sliders",
model: {
prop: 'value',
event: 'input'
},
props: {
black: {
type: Boolean,
default: null
},
vertical: Boolean, //是否启用竖向模式。需要和height配合使用。
// 单位upx.
height: {
type: Number,
default: 200
},
width: {
type: Number,
default: 0
},
showLeft: Boolean, //显示左边
showRight: Boolean, //显示右边
// 最大刻度。
max: {
type: Number,
default: 100
},
// 默认的数据。不能大于max.,使用.sync修饰。双向绑定数据。
value: {
type: Array,
default: ()=>{
return [0,0];
}
},
// 取值小数点后几位。默认为0
valueDiog: {
type: Number,
default: 0
},
// 是否禁用。
disabled: Boolean,
//步长。默认为0,设置请尽量大于0.太小滑动容易过小出问题。
step: {
type: Number,
default: 0
},
// 主题颜色名称。
color: {
type: String,
default: 'primary'
},
// 底部不活动的背景色,颜色名称。。
bgColor: {
type: String,
default: 'grey-lighten-2'
},
// 左边图标名称。
leftIcon: {
type: String | Boolean,
default: 'icon-minus'
},
// 右边图标名称。
rightIcon: {
type: String | Boolean,
default: 'icon-plus'
},
// 始终显示进度提示窗
showTip: {
type: Boolean,
default: false
},
// 跟随主题色的改变而改变。
fllowTheme:{
type:Boolean|String,
default:true
}
},
watch: {
value: {
handler(val) {
let rdl = this.sliderWidth * (Math.abs(val[0]) / Math.abs(this.max));
this.barLeft = rdl >= this.sliderWidth || rdl < 0 ? this.sliderWidth : rdl;
rdl = this.sliderWidth * (Math.abs(val[1]) / Math.abs(this.max));
this.barLeft_1 = rdl >= this.sliderWidth || rdl < 0 ? this.sliderWidth : rdl;
},
deep:true
},
barLeft: function() {
let value = (this.barLeft / this.sliderWidth) * this.max;
if (!isNaN(value)) {
if (this.valueDiog > 0) {
let s = value[0].toString();
let st = s.split('.');
let ruslt = 0;
if (st.length > 1) {
ruslt = parseFloat(st[0] + '.' + st[1].substring(0, this.valueDiog));
} else {
ruslt = parseFloat(s);
}
this.$emit('update:value', [ruslt,this.value[1]]);
this.$emit('input', [ruslt,this.value[1]]);
this.$emit('change', [ruslt,this.value[1]]);
} else {
this.$emit('update:value', [parseInt(value),this.value[1]]);
this.$emit('input',[parseInt(value),this.value[1]]);
this.$emit('change', Math.abs(parseInt(value-this.value[1])));
}
}
},
barLeft_1: function() {
let value = (this.barLeft_1 / this.sliderWidth) * this.max;
if (!isNaN(value)) {
if (this.valueDiog > 0) {
let s = value.toString();
let st = s.split('.');
let ruslt = 0;
if (st.length > 1) {
ruslt = parseFloat(st[0] + '.' + st[1].substring(0, this.valueDiog));
} else {
ruslt = parseFloat(s);
}
this.$emit('update:value', [this.value[0],ruslt]);
this.$emit('input', [this.value[0],ruslt]);
this.$emit('change', Math.abs(this.value[0]-ruslt));
} else {
this.$emit('update:value', [this.value[0],parseInt(value)]);
this.$emit('input', [this.value[0],parseInt(value)]);
this.$emit('change', Math.abs(parseInt(value-this.value[0])));
}
}
}
},
computed: {
black_tmeme: function() {
if (this.black !== null) return this.black;
return this.$tm.vx.state().tmVuetify.black;
},
// 计算刻度尺的数量
kdcNum: function() {
if (Math.abs(this.step) <= 0) return [];
let jlv = Math.abs(this.step) / Math.abs(this.max);
let left_width = jlv * (this.sliderWidth - 0);
let kd_num = parseInt(this.sliderWidth / left_width);
let ar = [];
for (let i = 0; i <= kd_num; i++) {
ar.push({
index: i, //顺序
left: i * left_width + 'px', //距离左边距离
kedu: this.step * i //当前刻度数量。
});
}
return ar;
},
//比例
active_width: function() {
return (Math.abs(this.barLeft - this.barLeft_1) / this.sliderWidth) * 100;
},
heightpx: function() {
return uni.upx2px(this.height);
},
bar_minLeft:function(){
let value = this.barLeft;
if(this.barLeft >= this.barLeft_1){
value = this.barLeft_1;
}
return value;
},
color_tmeme:function(){
if(this.$tm.vx.state().tmVuetify.color!==null&&this.$tm.vx.state().tmVuetify.color && this.fllowTheme){
return this.$tm.vx.state().tmVuetify.color;
}
return this.color;
},
},
data() {
return {
sliderWidth: 0,
x: 0,
startMove: false,
barLeft: 0,
barLeft_1: 0,
isError: false,
showTopTips: false
};
},
async mounted() {
this.$nextTick(async function() {
await this.getwidth();
if (Math.abs(this.value[1]) > Math.abs(this.max)) {
this.isError = true;
return;
}
let rdl = this.sliderWidth * (Math.abs(this.value[0]) / Math.abs(this.max));
this.barLeft = rdl >= this.sliderWidth || rdl < 0 ? this.sliderWidth : rdl;
let rdl_1 = this.sliderWidth * (Math.abs(this.value[1]) / Math.abs(this.max));
this.barLeft_1 = rdl_1 >= this.sliderWidth || rdl_1 < 0 ? this.sliderWidth : rdl_1;
});
},
methods: {
barStart(e) {
if (this.disabled || this.isError) return;
if(e.type.indexOf('mouse')>-1&&e.changedTouches.length==0){
this.x = (this.vertical ? e.pageY : e.pageX) - this.barLeft;
this.startMove = true;
this.showTopTips = true;
}else{
if (e.changedTouches.length > 0) {
this.x = (this.vertical ? e.changedTouches[0].pageY : e.changedTouches[0].pageX) - this.barLeft;
this.startMove = true;
this.showTopTips = true;
}
}
this.$nextTick(function(){
this.$emit('start',this.value)
})
},
barEnd(e) {
if (this.disabled || this.isError) return;
this.startMove = false;
this.showTopTips = false;
this.$nextTick(function(){
this.$emit('end',this.value)
})
},
barMove(e) {
if (this.disabled || this.isError) return;
if (!this.startMove) return;
let left = 0;
if(e.type.indexOf('mouse')>-1&&e.changedTouches.length==0){
left = (this.vertical ? e.pageY : e.pageX) - this.x;
}else{
left = (this.vertical ? e.changedTouches[0].pageY : e.changedTouches[0].pageX) - this.x;
}
if (left <= 0) {
this.barLeft = 0;
return;
}
if (left >= this.sliderWidth) {
this.barLeft = this.sliderWidth;
return;
}
let nowStep = parseInt(Math.abs(left - this.barLeft));
let bdi = parseInt((this.step / this.max) * this.sliderWidth);
if (nowStep >= bdi) {
// 每一小段的值。
if (this.step !== 0) {
let kedud = this.sliderWidth / (this.max / this.step);
this.barLeft = Math.round(left / kedud) * kedud;
} else {
this.barLeft = left;
}
}
},
barStart_1(e) {
if (this.disabled || this.isError) return;
if(e.type.indexOf('mouse')>-1){
this.x = (this.vertical ? e.pageY : e.pageX) - this.barLeft_1;
this.startMove = true;
this.showTopTips = true;
}else{
if (e.changedTouches.length > 0) {
this.x = (this.vertical ? e.changedTouches[0].pageY : e.changedTouches[0].pageX) - this.barLeft_1;
this.startMove = true;
this.showTopTips = true;
}
}
},
barEnd_1(e) {
if (this.disabled || this.isError) return;
this.startMove = false;
this.showTopTips = false;
},
barMove_1(e) {
if (this.disabled || this.isError) return;
if (!this.startMove) return;
let left = 0;
if(e.type.indexOf('mouse')>-1){
left = (this.vertical ? e.pageY : e.pageX) - this.x;
}else{
left = (this.vertical ? e.changedTouches[0].pageY : e.changedTouches[0].pageX) - this.x;
}
if (left <= 0) {
this.barLeft_1 = 0;
return;
}
if (left >= this.sliderWidth) {
this.barLeft_1 = this.sliderWidth;
return;
}
let nowStep = parseInt(Math.abs(left - this.barLeft_1));
let bdi = parseInt((this.step / this.max) * this.sliderWidth);
if (nowStep >= bdi) {
// 每一小段的值。
if (this.step !== 0) {
let kedud = this.sliderWidth / (this.max / this.step);
this.barLeft_1 = Math.round(left / kedud) * kedud;
} else {
this.barLeft_1 = left;
}
}
},
async getwidth() {
let res = await this.$Querey('.tm-slider-id', this).catch(e=>{});
res[0].width = res[0].width||uni.upx2px(this.width);
res[0].height = res[0].height||uni.upx2px(this.height);
if (this.showLeft === false && this.showRight === false) {
this.sliderWidth = this.vertical ? res[0].height : res[0].width;
return;
}
if (this.showLeft !== false && this.showRight !== false) {
this.sliderWidth = (this.vertical ? res[0].height : res[0].width) - uni.upx2px(this.vertical ? 50 :
100) * 2;
return;
}
if (this.showLeft === true || this.showRight === true) {
this.sliderWidth = (this.vertical ? res[0].height : res[0].width) - uni.upx2px(this.vertical ? 50 :
100);
}
}
}
};
</script>
<style lang="scss" scoped>
.label_slider {
flex-shrink: 0;
&.left,
&.right {
width: 100upx;
}
}
.tm-slider-id {
width: 100%;
.slider_id {
position: relative;
height: 4px;
.slider_id_bg {
width: 100%;
height: 100%;
}
.slider_id_active {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
z-index: 5;
}
.slider_bar {
position: absolute;
width: 40upx;
height: 40upx;
left: 0;
top: 0;
margin-top: -22upx;
z-index: 10;
.slider_bar_showbg {
position: absolute;
width: 50upx;
height: 50upx;
border-radius: 30upx;
border-bottom-left-radius: 5upx;
transform: rotate(-45deg);
bottom: 60upx;
left: -9upx;
// transition: translateY;
animation: roteScaleTop_BG 0.3s ease-in-out;
}
.slider_bar_num {
position: absolute;
width: 50upx;
height: 50upx;
border-radius: 50upx;
left: -8upx;
bottom: 60upx;
line-height: 50upx;
text-align: center;
background: transparent !important;
animation: roteScaleTop 0.3s ease-in-out;
}
}
.kdc_wk {
width: 100%;
height: 100%;
position: absolute;
z-index: 6;
left: 0;
top: -2upx;
.kdc_wk_item {
width: 10upx;
height: 10upx;
position: absolute;
z-index: 7;
text-align: center;
.kdc_wk_item_label {
margin-left: -100%;
}
}
}
}
&.vertical {
height: 100%;
.label_slider {
flex-shrink: 0;
&.left,
&.right {
width: auto;
height: 50upx;
}
}
.slider_bar {
margin-left: -18upx;
}
.kdc_wk {
top: 0upx;
.kdc_wk_item {
.kdc_wk_item_label {
margin-left: 100%;
margin-top: -15upx;
}
}
}
}
}
@keyframes roteScaleTop_BG{
0%{
transform: scale(0.9) translateY(20rpx) rotate(-45deg);
opacity: 0;
}
100%{
transform: scale(1) translateY(0rpx) rotate(-45deg);
opacity: 1;
}
}
@keyframes roteScaleTop{
0%{
transform: scale(0.9) translateY(20rpx);
opacity: 0;
}
100%{
transform: scale(1) translateY(0rpx);
opacity: 1;
}
}
</style>
@@ -0,0 +1,315 @@
<template>
<view class="tm-stepper d-inline-block">
<view class="flex-center" :style="{ width: `${width}rpx` }">
<view :class="[isJianDisabled||disabled?'opacity-6 gray':'']">
<tm-button @touchcancel="endlongpressEvent" @touchend="endlongpressEvent"
@touchstart="$emit('touchstart', 'minus')" @longpress="longpressEvent('-')" :fllowTheme="fllowTheme"
:disabled="isJianDisabled || disabled ? true : false"
:shadow="isJianDisabled || disabled ? 0 : shadwo_num" :black="black_tmeme" @click="setStep('-')"
:item-class="circular?' pa-0':` round-l-${round} `" icon-size="24" :round="circular?round:0"
:theme="disabled ? '' : color_tmeme" :width="height" :height="height" block icon="icon-minus">
</tm-button>
</view>
<input v-if="fixed!=0" @blur="inputVal" @input="inputVal" :disabled="(disabled||disabledInput)"
v-model="setVal" type="digit" :style="{ height: `${height}rpx`, width: `calc(100% - ${height}rpx)` }"
class="text-align-center text-size-n fulled"
:class="[`text-${font_color}`, black_tmeme&&!circular ? 'grey-darken-4 bk' : '',black_tmeme? 'text-grey-lighten-4' : '',circular?'':'grey-lighten-4']" />
<input v-if="fixed==0" @blur="inputVal" @input="inputVal" :disabled="(disabled||disabledInput)"
v-model="setVal" type="number" :style="{ height: `${height}rpx`, width: `calc(100% - ${height}rpx)` }"
class="text-align-center grey-lighten-4 text-size-n fulled"
:class="[`text-${font_color}`, black_tmeme&&!circular ? 'grey-darken-4 bk' : '',black_tmeme? 'text-grey-lighten-4' : '',circular?'':'grey-lighten-4']" />
<view :class="[isAddDisabled||disabled?'opacity-6 gray':'']">
<tm-button @touchcancel="endlongpressEvent" @touchend="endlongpressEvent"
@touchstart="$emit('touchstart', 'add')" @longpress="longpressEvent('+')" :fllowTheme="fllowTheme"
:shadow="isAddDisabled || disabled ? 0 : shadwo_num" :black="black_tmeme" @click="setStep('+')"
:item-class="circular?' pa-0':` round-r-${round} `" icon-size="24" :round="circular?round:0"
:theme="disabled ? '' : color_tmeme" :disabled="isAddDisabled || disabled ? true : false"
:width="height" :height="height" block icon="icon-plus"></tm-button>
</view>
</view>
</view>
</template>
<script>
/**
* 步进器
* @property {String|Number} value = [] 默认:'',值,推荐使用value.sync或者v-model
* @property {Boolean} disabled = [] 默认:false,是否禁用
* @property {Boolean} black = [] 默认:false,是否暗黑模式。
* @property {Number|String} step = [] 默认:1 步幅。
* @property {String} color = [] 默认:primary 主题色。
* @property {String} fontColor = [] 默认:black 输入框的文字主题色。
* @property {String|Number} round = [] 默认:3 圆角。
* @property {String|Number} shadow = [] 默认:3 圆角。
* @property {String|Number} max = [] 默认:999 最大值。
* @property {String|Number} min = [] 默认:0 最小值。
* @property {String|Number} width = [] 默认:200 宽度,单位rpx。
* @property {String|Number} height = [] 默认:70 高度,单位rpx。
* @property {String} name = [] 默认:'',提交表单时的的字段名称标识
* @property {Boolean|String} disabledInput = [] 默认:false,是否禁用输入框
* @property {Boolean|String} circular = [] 默认:false,按钮四角是否跟随圆角
* @property {Number} fixed = [] 默认:0 小数点位数
* @example <tm-stepper value="50"></tm-stepper>
*/
import tmButton from '@/tm-vuetify/components/tm-button/tm-button.vue';
export default {
components: {
tmButton
},
name: 'tm-stepper',
model: {
prop: 'value',
event: 'input'
},
props: {
value: {
type: Number | String,
default: 0
},
//提交表单时的的字段名称
name: {
type: String,
default: ''
},
disabled: {
type: Boolean,
default: false
},
//禁用输入功能
disabledInput: {
type: Boolean | String,
default: false
},
black: {
type: Boolean | String,
default: null
},
// 步幅,默认1
step: {
type: Number,
default: 1
},
//固定小数点位数,0表示整数
fixed: {
type: Number,
default: NaN
},
color: {
type: String,
default: 'primary'
},
fontColor: {
type: String,
default: 'black'
},
round: {
type: String | Number,
default: 3
},
circular: {
type: Boolean | String,
default: false
},
shadow: {
type: String | Number,
default: 3
},
// 跟随主题色的改变而改变。
fllowTheme: {
type: Boolean | String,
default: true
},
max: {
type: Number | String,
default: 999
},
min: {
type: Number | String,
default: 0
},
height: {
type: Number | String,
default: 60
},
width: {
type: Number | String,
default: 180
},
//回调函数。默认返回true即增减,否则不执行增减。
callback: {
type: Function | Object | Boolean,
default: true
}
},
data() {
return {
setVal: '',
timeid: 598985656
};
},
mounted() {
this.setVal = this.value;
},
watch: {
value: function(val) {
this.jianchData(parseFloat(val));
}
},
computed: {
isJianDisabled() {
if (isNaN(parseInt(this.setVal))) return false;
if (parseInt(this.setVal) <= this.min) return true;
return false;
},
isAddDisabled() {
if (isNaN(parseInt(this.setVal))) return false;
if (parseInt(this.setVal) >= this.max) return true;
return false;
},
black_tmeme: function() {
if (this.black !== null) return this.black;
return this.$tm.vx.state().tmVuetify.black;
},
color_tmeme: function() {
if (this.$tm.vx.state().tmVuetify.color !== null && this.$tm.vx.state().tmVuetify.color && this
.fllowTheme) {
return this.$tm.vx.state().tmVuetify.color;
}
return this.color;
},
font_color: function() {
if (this.fontColor) return this.fontColor;
return this.color;
},
shadwo_num: function() {
if (typeof this.shadow !== 'undefined') return this.shadow;
return 3;
}
},
destroyed() {
clearInterval(this.timeid);
},
methods: {
async setStep(ty) {
if (this.disabled) return;
if (typeof this.callback !== 'boolean' && this.callback !== true) {
uni.showLoading({
title: '...',
mask: true
})
let p = await this.callasync();
uni.hideLoading();
if (p !== true) return;
}
this.$nextTick(function() {
var val = parseFloat(this.value);
if (!isNaN(this.fixed) && this.fixed > 0) {
val = val.toFixed(this.fixed)
if (isNaN(val) || val == 0 || val == '0' || val == '' || !val) {
val = '0.' + this.strWidth(this.fixed) + this.step
}
val = parseFloat(val)
let setval = '0.' + this.strWidth(this.fixed) + this.step
setval = parseFloat(setval);
if (ty == '+') {
val += setval
} else {
val -= setval
}
} else if (!isNaN(this.fixed) && this.fixed == 0) {
val = val.toFixed(this.fixed)
val = parseInt(val)
if (ty == '+') {
val += this.step
} else {
val -= this.step
}
} else if (isNaN(this.fixed)) {
if (ty == '+') {
val += this.step
} else {
val -= this.step
}
}
if (val < 0) {
if (val <= this.min) {
val = this.min;
}
clearInterval(this.timeid);
} else if (val >= this.max) {
val = this.max;
clearInterval(this.timeid);
}
const realVal = val;
this.setVal = isNaN(realVal) ? '' : String(val);
this.$emit('input', this.setVal);
this.$emit('update:value', this.setVal);
this.$emit('change', this.setVal);
});
},
inputVal(e) {
var val = parseFloat(e.detail.value)
this.jianchData(val);
},
strWidth(len) {
let v = '';
for (let i = 0; i < len - 1; i++) {
v += '0';
}
return v;
},
jianchData(val) {
this.$nextTick(function() {
if (!isNaN(this.fixed) && this.fixed > 0) {
val = val.toFixed(this.fixed)
if (isNaN(val) || val == 0 || val == '0' || val == '' || !val) {
val = '0.' + this.strWidth(this.fixed) + this.step
}
} else if (!isNaN(this.fixed) && this.fixed == 0) {
val = val.toFixed(this.fixed)
}
const realval = val;
if (val < this.min) {
val = String(this.min);
}
if (val > this.max) {
val = String(this.max);
}
this.setVal = isNaN(parseFloat(realval)) ? '' : String(val);
this.$emit('input', this.setVal);
this.$emit('update:value', this.setVal);
this.$emit('change', this.setVal);
});
},
longpressEvent(ty) {
if (this.disabled) return;
let t = this;
clearInterval(this.timeid);
this.timeid = setInterval(async function() {
await t.setStep(ty);
}, 250);
},
endlongpressEvent(ty) {
clearInterval(this.timeid);
},
//异步回调
async callasync() {
let verify = this.callback;
verify = await verify()
if (typeof verify === 'function') {
verify = await verify()
}
if (typeof verify !== 'boolean') verify = true;
return verify;
}
}
};
</script>
<style lang="scss"></style>
+268
View File
@@ -0,0 +1,268 @@
<template>
<view class="tm-steps" v-if="listLen>0">
<block v-for="(item,index) in list" :key="index">
<view class="tm-steps-item" :style="{
width:(100/listLen)+'%'
}">
<view class="tm-steps-item-text flex-center flex-col ">
<view :style="{
width:sizePx + 'px',
height:sizePx + 'px',
lineHeight:0
}" :class="[
index < value ? color_tmeme:'grey-lighten-4',
black_tmeme?'bk':'',
index < value?`shadow-${color_tmeme}-3`:''
]" @click.stop="itemclick(index)" class="tm-steps-item-text-icon rounded text-size-s flex-center ">
<block v-if="model=='number'">
<slot name="number"
:data="{icon:icon,activeIcon:activeIcon,index:index+1,active:index < value?true:false}">
<text :class="index < value ? '':'text-'+fontColor">{{index+1}}</text>
</slot>
</block>
<block v-if="model=='icon'">
<slot name="icon"
:data="{icon:icon,activeIcon:activeIcon,index:index+1,active:index < value?true:false}">
<tm-icons style="line-height: 0;" :color="fontColor" size="22" v-if="index >= value" :name="icon"></tm-icons>
<tm-icons style="line-height: 0;" color="white" size="22" v-if="index < value" :name="activeIcon"></tm-icons>
</slot>
</block>
</view>
<view :class="[
index < value ? `text-${activeFontColor_theme}`:`text-${fontColor}`,
]" class="tm-steps-item-text-icon-label text-size-s text-align-center px-10 py-16">
<slot name="default" :data="item">
{{item[rangeKey]?item[rangeKey]:item}}
</slot>
</view>
</view>
<view :style="{
width:` calc(100% - ${sizePx}px)`,
height:sizePx+'px',
top:`0`,
left: `calc(50% + ${sizePx/2}px)`,
}" v-if="index!==listLen-1" class="tm-steps-item-driver flex-center">
<!-- <tm-icons color="white" size="22" name="icon-angle-right"></tm-icons> -->
<view class="fulled flex-center"
:style="{borderTopStyle:lineStyle,borderTopWidth:lineWidth+'rpx',height:'1px'}"
:class="[index < value ? `border-${lineColor_theme}-t-1`:black_tmeme?'border-grey-darken-4-t-1':'border-grey-lighten-4-t-1']">
<view style="line-height: 0;">
<tm-icons v-if="lineInIcon" style="line-height: 0;" dense :color="index < value ? color_tmeme:'grey-lighten-4'" :black="black_tmeme" size="22" :name="lineInIcon"></tm-icons>
</view>
</view>
</view>
</view>
</block>
</view>
</template>
<script>
/**
* 步骤条
* @description 只能搭配tm-stepsItem组件使用。内部需要放入:tm-stepsItem组件。
* @property {Number} value = [] 默认:1,当前第几步同步需要valu.snyc 推荐使用v-model.
* @property {Boolean} black = [true|false] 默认:false,暗黑模式。
* @property {String} model = [number|icon] 默认:number,两种模式,number:数字,icon:图标模式。
* @property {String} color = [] 默认:primary,主题色。
* @property {String} range-key = [] 默认:text,数据为对象数组时,需要提供对应的文字key。
* @property {String} size = [] 默认:42,节点大小。
* @property {String} line-color = [] 默认:primary,激活时的线条主题色。
* @property {String} font-color = [] 默认:grey,文字主题色。
* @property {String} active-font-color = [] 默认:primary,激活后的文字主题色。
* @property {String} active-icon = [] 默认:icon-check,激活后的图标。
* @property {String} icon = [] 默认:icon-clock-fill,未激活的图标。
* @property {String} lineInIcon = [] 默认:'',节点间线中间内部的图标,填写后显示,不填写不显示。
* @property {String} lineStyle = [] 默认:solid,标准的css边线类型
* @property {String} lineWidth = [] 默认:1,连接线的厚度,单位rpx
* @property {Function} input 等同v-model参数
* @property {Function} change 步骤变化时触发。等同input,v-model参数
* @property {Function} click 点击步骤节点时触发,返回当前步骤位置。
* @example <tm-steps v-model="star"><block v-for="(item,index) in 4" :key="index"><tm-stepsItem :fail="index+1==3" model="icon" :step="index+1"></tm-stepsItem><tm-divider :color="index > star-1 ? '#eeeeee':'#1976d2'" v-if="index!==3" ></tm-divider></block></tm-steps>
*/
import tmIcons from "@/tm-vuetify/components/tm-icons/tm-icons.vue"
export default {
components: {
tmIcons
},
name: "tm-steps",
model: {
prop: "value",
event: "input"
},
props: {
value: {
type: Number,
default: 1
},
list: {
type: Array,
default: () => {
return []
}
},
rangeKey: {
type: String,
default: 'text'
},
// 主题色。
color: {
type: String,
default: 'primary'
},
// 激活时的颜色值,16进制。
lineColor: {
type: String,
default: 'primary'
},
lineStyle:{
type:String,
default:'solid',//solid,dashed
},
lineWidth:{
type:String|Number,
default:1,
},
//显示线上面的图标
lineInIcon:{
type:String,
default:'',
},
fontColor: {
type: String,
default: 'grey'
},
activeFontColor: {
type: String,
default: 'primary'
},
model: {
type: String,
default: 'icon'
},
black: {
type: Boolean | String,
default: null
},
size: {
type: Number,
default: 46
},
icon: {
type: String,
default: 'icon-clock-fill'
},
activeIcon: {
type: String,
default: 'icon-check'
},
// 跟随主题色的改变而改变。
fllowTheme: {
type: Boolean | String,
default: true
},
},
data() {
return {
};
},
computed: {
black_tmeme: function() {
if (this.black !== null) return this.black;
return this.$tm.vx.state().tmVuetify.black;
},
color_tmeme: function() {
if (this.$tm.vx.state().tmVuetify.color !== null && this.$tm.vx.state().tmVuetify.color && this
.fllowTheme) {
return this.$tm.vx.state().tmVuetify.color;
}
return this.color;
},
lineColor_theme: function() {
if (this.$tm.vx.state().tmVuetify.color !== null && this.$tm.vx.state().tmVuetify.color && this
.fllowTheme) {
return this.$tm.vx.state().tmVuetify.color;
}
return this.lineColor;
},
activeFontColor_theme: function() {
if (this.$tm.vx.state().tmVuetify.color !== null && this.$tm.vx.state().tmVuetify.color && this
.fllowTheme) {
return this.$tm.vx.state().tmVuetify.color;
}
return this.activeFontColor;
},
listLen: function() {
if (!Array.isArray(this.list)) return 0;
return this.list.length;
},
sizePx: function() {
return uni.upx2px(this.size);
}
},
watch: {
value: async function() {
let bz = this.value;
if (this.value > this.listLen || this.value < 0) {
bz = 0
}
if (this.value == this.listLen - 1) {
bz = this.listLen - 1
}
await this.setActive(bz);
}
},
async mounted() {
this.UpdateItem();
},
methods: {
//如果需要动态插入步骤,需要调用此函数更新。
async UpdateItem() {
await this.setActive(this.value)
},
async setActive(index) {
this.$emit("input", index)
this.$emit("change", index)
this.$emit("update:value", index)
},
itemclick(index) {
this.$emit('click', index + 1)
}
},
}
</script>
<style lang="scss" scoped>
.tm-steps {
display: flex;
justify-content: space-between;
align-items: flex-start;
// border-style: dashed;
.tm-steps-item {
position: relative;
.tm-steps-item-text {
.tm-steps-item-text-icon {}
}
.tm-steps-item-driver {
position: absolute;
}
}
}
</style>
@@ -0,0 +1,182 @@
<template>
<view class="tm-stepsItem ">
<view @click="onclick" class="px-10 flex-center flex-col tm-stepsItem-wk" >
<view class="tm-stepsItem-dian rounded flex-center"
:class="[
isSu&&!isFa&&model=='icon'?' su '+ color +' shadow-'+color+'-5':'',
isSu&&!isFa&&model=='number'?' su '+color +' shadow-'+color+'-5' :'',
isFa?'fa red' +' shadow-red-5':'',
isWa&&!isFa&&!isSu?(black_tmeme?'wa bk grey-lighten-4 text-grey':`wa grey-lighten-4 text text-grey`):'',
]"
>
<text v-if="model=='number'">{{setNum}}</text>
<view v-if="model=='icon'">
<tm-icons size="20" v-if="isSu&&!isFa" name="icon-check"></tm-icons>
<tm-icons size="20" v-if="isFa" name="icon-times"></tm-icons>
<view v-if="isWa&&!isFa&&!isSu" class="tm-stepsItem-dian-quan rounded " :class="[color]"></view>
</view>
</view>
<view class="tm-stepsItem-text text-size-xs py-10 "><slot></slot></view>
</view>
</view>
</template>
<script>
/**
* 步骤条子组件
* @description 只能在tm-steps中使用。默认插槽显示底部内容。
* @property {String} model = [number|icon] 默认:number,两种模式,number:数字,icon:图标模式。
* @property {String} color = [] 默认:primary,主题色。
* @property {Boolean} wait = [true|false] 默认:true,待流转。显示错误状态。优先级最低
* @property {Boolean} fail = [true|false] 默认:false,显示错误状态。优先级最高
* @property {Boolean} success = [true|false] 默认:false,显示错误状态。优先级高于wait.
* @property {Number|String} step = [] 默认:0model=number时,显示的数字文本。
* @property {Boolean} black = [true|false] 默认:false,暗黑模式。
* @property {Function} click 点击事件。
* @example <tm-stepsItem :fail="true" model="icon" ></tm-stepsItem>
*/
export default {
name:"tm-stepsItem",
props:{
black:{
type:Boolean|String,
default:null
},
step:{
type:Number|String,
default:0
},
success:{
type:Boolean,
default:false
},
fail:{
type:Boolean,
default:false
},
wait:{
type:Boolean,
default:true
},
color:{
type:String,
default:'primary'
},
// number|icon
model:{
type:String,
default:'number'
}
},
data() {
return {
width:0,
su_selft:false,
fa_selft:false,
wa_selft:false,
};
},
watch:{
success:function(newval){
this.isSu = newval;
},
fail:function(newval){
this.isFa = newval;
},
wait:function(newval){
this.isWa = newval;
},
},
computed:{
black_tmeme: function() {
if (this.black !== null) return this.black;
return this.$tm.vx.state().tmVuetify.black;
},
setNum:function(){
return this.step;
},
isSu:{
get:function(){
return this.su_selft;
},
set:function(val){
this.su_selft = val;
}
},
isFa:{
get:function(){
return this.fa_selft;
},
set:function(val){
this.fa_selft = val;
}
},
isWa:{
get:function(){
return this.wa_selft;
},
set:function(val){
this.wa_selft = val;
}
},
},
async mounted() {
this.isSu = this.success;
this.isFa = this.fail;
this.isWa = this.wait;
this.$nextTick(async function (){
let tbs = await this.$Querey(".tm-stepsItem");
this.width = tbs[0].width;
})
},
methods: {
setActive({success,fail,wait}) {
this.isSu = success;
if(typeof fail !=='undefined'){
this.isFa = fail;
}
this.isWa = wait;
},
onclick(e){
this.$emit('click',e)
},
},
}
</script>
<style lang="scss" scoped>
.tm-stepsItem{
flex-shrink: 0;
.tm-stepsItem-wk{
position: relative;
top: -18upx;
}
.tm-stepsItem-dian{
width: 36upx;
height: 36upx;
font-size: 22upx;
line-height: 12upx;
.tm-stepsItem-dian-quan{
width: 12upx;
height: 12upx;
}
}
.tm-stepsItem-text{
flex-shrink: 0;
width: 100upx;
text-align: center;
}
}
</style>
@@ -0,0 +1,99 @@
<template>
<view
class="tm-sticky fulled"
:class="[model_val]"
:style="{
top: model_val == 'top' || model_val == 'static' ? `${top_val}px` : 'auto',
bottom: model_val == 'bottom' ? `${top_val}px` : 'auto'
}"
>
<slot></slot>
</view>
</template>
<script>
/**
* 粘性布局
* @property {String|Number} top = [] 当悬浮时的的偏移量,当model=bottom时,为底部偏移量,单位px
* @property {String} model = [static|top|bottom] 固定的位置
* @example <tm-sticky ></tm-sticky>
*/
export default {
name: 'tm-sticky',
props: {
top: {
type: String | Number,
default: 0
},
model: {
type: String,
default: 'static' //static,top,bottom
}
},
destroyed() {
uni.$off('onPageScroll');
},
computed:{
top_val:function(){
return this.top;
},
model_val:function(){
let p = this.model;
// #ifndef H5
if(this.model=='static' && this.istrueStic==false){
return 'top';
}
// #endif
return p;
},
istrueStic:function(){
if(this.nowScrollTop <= this.eleTop){
return true;
}
return false;
}
},
mounted() {
let t= this;
uni.$on('onPageScroll', e => {
t.nowScrollTop = e.scrollTop;
});
this.$nextTick(async function(){
let p = await this.$Querey('.tm-sticky').catch(e=>{})
this.eleTop = p[0].top;
})
},
data() {
return {
nowScrollTop: 0,
eleTop:0
};
}
};
</script>
<style lang="scss" scoped>
.tm-sticky {
&.static {
position: sticky;
position: -webkit-sticky;
}
&.top {
position: fixed;
top: 0;
z-index: 301;
}
&.bottom {
position: fixed;
bottom: 0;
z-index: 301;
}
z-index: 300;
}
</style>
@@ -0,0 +1,464 @@
<template>
<view class="tm-swiper " :class="[inline ? 'd-inline-block' : '']">
<swiper
:previous-margin="`${ani3d}rpx`"
:next-margin="`${ani3d}rpx`"
:style="{
width: w_s + 'rpx',
height: h_s + 'rpx'
}"
:vertical="vertical"
:autoplay="autoplay && !isPlayVedio"
:circular="circular"
:interval="interval"
:duration="duration"
:indicator-active-color="color_tmeme"
:current="nowIndex"
@change="change"
>
<block v-for="(item, index) in dataList" :key="index">
<swiper-item
:style="{
width: w_s + 'rpx',
height: h_s + 'rpx'
}"
class="shadow-24"
:class="[round_num > 0 ? ` round-${round_num} overflow` : '']"
>
<view
:class="[
nowIndex !== index && ani3d > 0 ? 'a3d' : '',
round_num > 0 ? ` round-${round_num} overflow` : '',
nowIndex < index && ani3d > 0 ? 'a3dL' : '',
nowIndex > index && ani3d > 0 ? 'a3dR' : ''
]"
class="tm-swiper-ik fulled fulled-height"
:key="index"
>
<view class="actvieMarginDh">
<view class="actvieMarginDh-item" :class="[round_num > 0 ? ` round-${round_num} overflow` : '']">
<image
v-if="item.dtype != 'video'"
@click="itemClick(item)"
:style="{
height: h_s + 'rpx',
width: w_s - margin_px + 'rpx'
}"
@load="imgload($event, index)"
:src="item.url"
:class="[round_num > 0 ? ` round-${round_num} overflow` : '']"
></image>
<video
@play="isPlayVedio = true"
@pause="isPlayVedio = false"
@ended="isPlayVedio = false"
:autoplay="nowIndex == index ? true : false"
:style="{
height: h_s + 'rpx',
width: w_s - margin_px + 'rpx'
}"
v-if="item.dtype == 'video'"
:src="item.url"
></video>
<view
v-if="item.title"
:style="{
width: w_s - margin_px + 'rpx'
}"
class="flex-start relative "
>
<view
class="text-size-s px-16 absolute b-0 l-0 zIndex-14 flex-start text-overflow text-white"
:style="{
height: '66rpx',
width: w_s - margin_px - 32 + 'rpx',
lineHeight: '66rpx',
background: 'rgba(0,0,0,0.4)'
}"
>
{{ item.title }}
</view>
</view>
</view>
</view>
</view>
</swiper-item>
</block>
</swiper>
<view class="tm-swiper-dot" v-if="indicatorDots && nowshowDot" :class="[dotDirection]">
<block v-if="dotModel != 'round'">
<view
@click="nowIndex = index"
v-for="(item, index) in dataList"
:key="index"
class="tm-swiper-dot-item flex-center mx-8"
:class="[nowIndex == index ? color_tmeme : 'unactive', dotModel]"
>
<text v-if="dotModel === 'number'" class="text-size-xs">{{ index + 1 }}</text>
</view>
</block>
<block v-if="dotModel == 'round'">
<view
:class="[dotModel]"
class="text-size-xs balck text-white round-24 px-24 tm-swiper-dot-item flex-center flex-shrink"
:style="{ height: '40rpx', minWidth: '60rpx' }"
>
{{ nowIndex + 1 + '/' + dataList.length }}
</view>
</block>
</view>
</view>
</template>
<script>
/**
* 图片轮播
* @description 很参数与原官方相同。
* @property {Number} width = [] 默认:0,轮播宽度,0为自己计算。
* @property {Number} height = [] 默认:0,轮播高度,0为自己计算。
* @property {Array} list = [] 默认:[],图片列表,可以是string数组或者object数组。
* @property {String} rang-key = [] 默认:[],图片列表object数组时,需要提供图片地址 的键值。
* @property {Boolean} inline = [] 默认:false,是否内联,方便一排放多个轮播组件。
* @property {Boolean} previmage = [] 默认:true,是否预览图片,点击图片时,放大预览。
* @property {Boolean} vertical = [] 默认:false,是否竖向滚动。
* @property {Boolean} circular = [] 默认:false,是否采用衔接滑动,即播放到末尾后重新回到开头
* @property {Boolean} autoplay = [] 默认:false,是否自动切换
* @property {Number} interval = [] 默认:3000,自动切换时间间隔
* @property {Number} duration = [] 默认:500,滑动动画时长
* @property {Number} ani3d = [] 默认:0,开启3d缩放动画
* @property {Boolean} indicator-dots = [] 默认:true,是否显示指示
* @property {String} color = [] 默认:primary,指示点的主题色。
* @property {Number} current = [] 默认:0,如果需要双向绑定请使用current.sync.
* @property {String} dot-model = [dot|number|rect|round] 默认:dot,指示点的类型。
* @property {String} dot-direction = [left|center|right] 默认:center,指示点的位置。
* @property {Number} round = [] 默认:0,圆角,单位rpx
* @property {Number} margin = [] 默认:0,单位rpx
* @property {Function} change 轮播切换时触发。参数返回当前播放位置。
* @property {Function} click 轮播项目被点击时触发发,返回项目数据
* @example <tm-swiper :list="['https://picsum.photos/300?jv=3','https://picsum.photos/300?jv=3']" ></tm-swiper>
*/
export default {
name: 'tm-swiper',
props: {
width: {
type: Number | String,
default: 0
},
// 自定高度。
height: {
type: Number | String,
default: 0
},
list: {
type: Array,
default: () => {
return [];
}
},
rangKey: {
type: String,
default: 'src'
},
// 是否内联。
inline: {
type: Boolean,
default: false
},
// 是否开启预览模式,即点击图片可以预览。
previmage: {
type: Boolean | String,
default: true
},
vertical: false,
circular: false,
autoplay: false,
interval: {
type: Number,
default: 3000
},
duration: {
type: Number,
default: 500
},
// 是否显示指示
indicatorDots: false,
//指示点的主题色。
color: {
type: String,
default: 'primary'
},
// 当前位置。
current: {
type: Number,
default: 0
},
ani3d: {
type: Number,
default: 0
},
// 指示点的类型。
dotModel: {
type: String,
default: 'dot' //dot|number|rect
},
// 指示点的位置 。
dotDirection: {
type: String,
default: 'center' //left|center|right
},
round: {
type: Boolean | Number,
default: 0
},
margin: {
type: Number,
default: 0
},
// 跟随主题色的改变而改变。
fllowTheme: {
type: Boolean | String,
default: true
}
},
watch: {
current: function(newval) {
// if (newval >= this.dataList.length) {
// this.nowIndex = 0;
// return;
// }
// this.nowIndex = newval;
},
list: {
deep: true,
handler() {
this.chulidata();
}
}
},
computed: {
color_tmeme: function() {
if (this.$tm.vx.state().tmVuetify.color !== null && this.$tm.vx.state().tmVuetify.color && this.fllowTheme) {
return this.$tm.vx.state().tmVuetify.color;
}
return this.color;
},
round_num: function() {
if (typeof this.round === 'boolean') return 0;
return this.round;
},
nowIndex: {
get: function() {
return this.dotIndex;
},
set: function(val) {
this.dotIndex = val;
this.$emit('update:current', val);
this.$emit('change', val);
}
},
nowshowDot: function() {
return this.showdot();
},
w_s: {
get: function() {
return this.w_w;
},
set: function(val) {
this.w_w = val;
}
},
h_s: {
get: function() {
return this.h_h;
},
set: function(val) {
this.h_h = val;
}
},
margin_px: function() {
return this.margin * 2;
}
},
data() {
return {
w_w: 0,
h_h: 0,
dataList: [],
dotIndex: 0,
isPlayVedio: false
};
},
async mounted() {
// this.nowIndex = this.current;
let w = this.width;
if (String(w).indexOf('px') > -1 && typeof w == 'string') {
let p = parseInt(h);
let sy = 750 / uni.getSystemInfoSync().windowWidth;
w = p * sy;
}
if (typeof w == 'string') {
w = parseInt(w);
}
this.w_w = w;
let h = this.height;
if (String(h).indexOf('px') > -1 && typeof h == 'string') {
let p = parseInt(h);
let sy = 750 / uni.getSystemInfoSync().windowWidth;
h = p * sy;
}
if (typeof h == 'string') {
h = parseInt(h);
}
this.h_h = h;
this.$nextTick(async function() {
await this.chulidata();
});
},
methods: {
showdot() {
if (typeof this.dataList[this.nowIndex] == 'string') return true;
if (typeof this.dataList[this.nowIndex] == 'object') {
if (this.dataList[this.nowIndex].dtype == 'video') return false;
}
return true;
},
async chulidata() {
let t = this;
let tb = await this.$Querey('.tm-swiper', this).catch(ev => {});
//console.log(uni.getSystemInfoSync());
let sy = 750 / uni.getSystemInfoSync().windowWidth;
let upxw = tb[0].width * sy;
let sw = this.w_s > 0 ? this.w_s : upxw > 0 ? upxw : 100;
this.w_s = sw;
let sh = this.h_s > 0 ? this.h_s : tb[0].height > 0 ? tb[0].height : 300;
this.h_s = sh;
let d = [];
this.list.forEach(item => {
if (typeof item === 'string') {
d.push({
width: sw,
height: sh,
url: item,
title: '',
dtype: '',
data: item
});
} else if (typeof item === 'object') {
d.push({
width: sw,
height: sh,
url: item[t.rangKey],
title: item['title'] || '',
dtype: item['type'] || '',
data: item
});
}
});
this.dataList = d;
},
imgload(e, index) {},
change(event) {
this.nowIndex = event.detail.current;
},
itemClick(e) {
if (this.previmage) {
uni.previewImage({
current: e.url,
urls: [e.url]
});
}
this.$emit('click', e);
}
}
};
</script>
<style></style>
<style lang="scss" scoped>
.tm-swiper {
position: relative;
.tm-swiper-ik {
transition: all 1s;
}
.a3d {
transform: scale(0.76);
}
.a3dL {
transform: scale(0.76) translateX(-24rpx);
}
.a3dR {
transform: scale(0.76) translateX(24rpx);
}
.actvieMarginDh {
display: flex;
justify-content: center;
.actvieMarginDh-item {
overflow: hidden;
line-height: 0;
}
}
.tm-swiper-dot {
.tm-swiper-dot-item {
width: 16upx;
height: 16upx;
border-radius: 50%;
&.unactive {
background-color: rgba(255, 255, 255, 0.6);
}
&.number {
width: 34upx;
height: 34upx;
}
&.rect {
width: 24upx;
height: 6upx;
border-radius: 0;
}
&.round {
background-color: rgba(0, 0, 0, 0.4);
}
}
width: 100%;
position: absolute;
left: 0;
display: flex;
bottom: 20upx;
&.center {
display: flex;
justify-content: center;
}
&.left {
display: flex;
justify-content: flex-start;
padding-left: 20upx;
width: calc(100% - 20upx);
}
&.right {
display: flex;
justify-content: flex-end;
padding-right: 20upx;
width: calc(100% - 20upx);
}
}
}
</style>

Some files were not shown because too many files have changed in this diff Show More