优化代码,修复已知bug

This commit is contained in:
jaylen
2023-06-10 10:04:35 +08:00
parent fa70ad608a
commit 9707895a7c
60 changed files with 11677 additions and 1299 deletions
Vendored
BIN
View File
Binary file not shown.
-201
View File
@@ -1,201 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-192
View File
@@ -1,192 +0,0 @@
<p align="center">
<img alt="图鸟UI" src="https://tnuiimage.tnkjapp.com/gitee_introduce_file/top.jpg" width="60%" />
</p>
<h3 align="center" style="margin: 30px 0 30px;font-weight: bold;font-size:40px;">Tuniao UI</h3>
<h3 align="center">提供丰富组件和炫酷页面的UI框架</h3>
<p align="center">
<a href="https://gitee.com/TSpecific/tuniao-ui/stargazers" target="_blank">
<img src="https://svg.hamm.cn/gitee.svg?type=star&user=TSpecific&project=tuniao-ui"/>
</a>
<a href="https://gitee.com/TSpecific/tuniao-ui/members" target="_blank">
<img src="https://svg.hamm.cn/gitee.svg?type=fork&user=TSpecific&project=tuniao-ui"/>
</a>
<img src="https://svg.hamm.cn/badge.svg?key=Platform&value=uni-app"/>
</p>
## 说明
**图鸟UI**,是基于uni-app进行开发的UI框架,提供丰富的组件进行快速开发,已经支持`H5``微信小程序`,包含常用表单组件、信息展示组件等,并提供`丰富的酷炫页面模板`
### 开源版本中所使用到的图片均做了防盗链,只能在调试中使用,不保证在生产环境中可以使用。
### 图片资源以上传到[图鸟社区](https://www.yuque.com/tuniao)。
## 特点
- 包含基础常用的布局元素,flex、grid、浮动
- 完整一体的配色体系,包含4种色深模式,同时包含4套渐变配色
- 300+风格统一的图标icon,后续可继续增加
- 30+精选组件,让开发者可以快速进行开发
- 酷炫常用的页面模板,更有让你眼前一亮的界面效果
- 图片素材语雀便捷下载,图鸟社区共同成长
- 使用文档详尽说明,让你一文读懂图鸟UI
## 链接
- [图鸟社区](https://www.yuque.com/tuniao)
- [使用手册](https://tuniaoui.tuniaokj.com/components/introduce.html)
## 安装
下载地址 ------> [图鸟UI - DCloud 插件市场](https://ext.dcloud.net.cn/plugin?id=7088)
## 快速上手
#### 1.复制文件到项目的根目录
- ##### 必要文件夹
- 复制tuniao-ui文件夹
- 复制store文件夹
- ##### 如果使用了模板页面则需要复制以下文件夹
- 复制libs文件夹
- 复制static文件夹
#### 2.引入TuniaoUI主JS库
在项目根目录中的`main.js`中,引入并使用TuniaoUI的JS库,注意这两行配置代码要放在import Vue之后。
```javascript
// 引入全局TuniaoUI
import TuniaoUI from 'tuniao-ui'
Vue.use(TuniaoUI)
```
#### 3.引入TuniaoUI提供的vuex
在项目根目录的`main.js`中引入store
```js
// 引入store
import store from './store'
...
const app = new Vue({
store,
...App
})
```
在项目根目录中的`main.js`中,引入并使用TuniaoUI的vuex,注意这两行配置代码要放在import Vue之后。
```js
// 引入TuniaoUI提供的vuex简写方法
let vuexStore = require('@/store/$t.mixin.js')
Vue.mixin(vuexStore)
```
#### 4.引入TuniaoUI的全局SCSS主题文件
在项目根目录的`uni.scss`中引入此文件。
```scss
@import 'tuniao-ui/theme.scss';
```
#### 5.引入TuniaoUI基础样式和图标文件
::: danger 注意
`App.vue``style标签`首行的位置引入,注意给style标签加入lang="scss"属性
:::
```scss
<style lang="scss">
/* 注意要写在第一行同时给style标签加入lang="scss"属性 */
@import './tuniao-ui/index.scss';
@import './tuniao-ui/iconfont.css';
</style>
```
#### 6.配置easycom组件模式
此配置需要在根目录的`page.json`中进行。
::: tip 温馨提示
1. uni-app为了调试性能的原因,修改`easycom`规则不会实时生效,配置完后,您需要重启HBuilderX或者重新编译项目才能正常使用TuniaoUI的功能。
2. 请确保您的`pages.json`中只有一个`easycom`字段,否则请自行合并多个引入规则。
:::
```
// pages.json
{
"easycom": {
"^tn-(.*)": "@/tuniao-ui/components/tn-$1/tn-$1.vue"
},
"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
{
// ......
}
]
// ......
}
```
## UI截图
<p>
<img style="margin-top: 10px; margin-left: 10px;" alt="图鸟UI" src="https://tnuiimage.tnkjapp.com/gitee_introduce_file/1.jpg" width="300" />
<img style="margin-top: 10px; margin-left: 10px;" alt="图鸟UI" src="https://tnuiimage.tnkjapp.com/gitee_introduce_file/2.jpg" width="300" />
<img style="margin-top: 10px; margin-left: 10px;" alt="图鸟UI" src="https://tnuiimage.tnkjapp.com/gitee_introduce_file/3.jpg" width="300" />
<img style="margin-top: 10px; margin-left: 10px;" alt="图鸟UI" src="https://tnuiimage.tnkjapp.com/gitee_introduce_file/4.jpg" width="300" />
<img style="margin-top: 10px; margin-left: 10px;" alt="图鸟UI" src="https://tnuiimage.tnkjapp.com/gitee_introduce_file/5.jpg" width="300" />
<img style="margin-top: 10px; margin-left: 10px;" alt="图鸟UI" src="https://tnuiimage.tnkjapp.com/gitee_introduce_file/6.jpg" width="300" />
</p>
## 联系作者
<p>
<img alt="图鸟UI" src="https://tnuiimage.tnkjapp.com/gitee_introduce_file/bottom.jpg" width="380" />
</p>
### 微信群聊二维码
<p>
<img alt="图鸟UI" src="https://tnuiimage.tnkjapp.com/gitee_introduce_file/group_code.jpg" width="380" />
</p>
## 版权信息
`TuniaoUI开源版`遵循`Apache`协议,意味着您无需支付任何费用,也无需授权,即可将TuniaoUI开源版应用到您的产品中,但是需要保留TuniaoUI的信息。
+223 -161
View File
@@ -1,169 +1,231 @@
<template>
<view class="basic-icon">
<!-- 顶部自定义导航 -->
<tn-nav-bar fixed>图标</tn-nav-bar>
<!-- 页面内容 -->
<view :style="{paddingTop: vuex_custom_bar_height + 'px'}">
<tn-sticky :customNavHeight="vuex_custom_bar_height">
<view class="search-content">
<input class="search-content__input" placeholder-class="search-content__input-placeholder" placeholder="请输入图标名称吖" @input="saerchInput" />
<view class="basic-icon">
<!-- 顶部自定义导航 -->
<tn-nav-bar fixed>图标</tn-nav-bar>
<!-- 页面内容 -->
<view :style="{paddingTop: vuex_custom_bar_height + 'px'}">
<view class="search-fixed">
<view class="search-content">
<input class="search-content__input" placeholder-class="search-content__input-placeholder tn-color-blue"
placeholder="搜索 中文名/英文名" @input="saerchInput" />
</view>
</view>
<view class="" :style="{marginTop: vuex_custom_bar_height + 'px'}">
<block v-for="(item, index) in resultIconList" :key="index">
<!-- 分类标题-->
<view class="tn-text-center tn-text-xl tn-text-bold tn-margin-lg">
<text class="tn-icon-font"></text>
<text class="tn-padding-left-sm tn-padding-right-sm">{{ item.title }}</text>
<text class="tn-icon-font"></text>
</view>
<view class="icon__container tn-flex tn-flex-wrap tn-flex-row-left tn-flex-col-center tn-margin">
<view v-for="(icons_item, icons_index) in item.icons" :key="icons_index"
class="icon__item tn-flex tn-flex-direction-column tn-flex-row-center tn-flex-col-center icon-shadow"
:class="[{'icon__item--active': icons_index === currentIconIndex}]"
@click="clickIcon(icons_index, icons_item.name, icons_item.icon)">
<view class="icon__item--icon tn-flex tn-flex-row-center tn-flex-col-center tn-shadow-blur">
<view :class="[`tn-icon-${icons_item.icon}`]"></view>
</view>
<view class="icon__item--title tn-text-ellipsis tn-text-df tn-color-grey">{{ icons_item.icon }}</view>
<!-- <view class="icon__item--title tn-text-ellipsis tn-text-xs tn-color-grey">{{ icons_item.name }}</view> -->
</view>
</view>
</block>
</view>
<view class="tn-text-center tn-margin-bottom-xl">
<view>
icon目前700+支持
<text class="tn-color-orange tn-text-lg tn-padding-xs">中文英文</text>
搜索
</view>
</tn-sticky>
<view class="icon__container tn-flex tn-flex-wrap tn-flex-row-left tn-flex-col-center tn-margin">
<block v-for="(item, index) in resultIconList" :key="index">
<view
class="icon__item tn-flex tn-flex-direction-column tn-flex-row-center tn-flex-col-center icon-shadow"
:class="[{'icon__item--active': index === currentIconIndex}]"
@click="clickIcon(index, item.name)"
>
<view class="icon__item--icon tn-flex tn-flex-row-center tn-flex-col-center tn-shadow-blur">
<view :class="[`tn-icon-${item.name}`]"></view>
</view>
<view class="icon__item--title tn-text-ellipsis">{{ item.name }}</view>
</view>
</block>
</view>
<view class="tn-text-center tn-margin-bottom-xl">
<view>icon目前400+里面缺少你想要的吗</view>
<view>UI期待你的需求留言</view>
<view>后续图标多了加上分类</view>
</view>
<view class="tn-padding-bottom"></view>
</view>
</view>
<button class=" tn-button--clear-style" open-type="feedback">
<view class="tn-margin tn-text-center">
<text>里面缺少你想要的吗UI期待你的需求</text>
<text class="tn-color-blue">留言</text>
</view>
</button>
</view>
<view class="tn-padding-bottom"></view>
</view>
</view>
</template>
<script>
import iconData from './iconfont.js'
export default {
name: 'basicIcon',
data() {
return {
// 图标列表
iconList: iconData.data,
// 用户输入的内容
searchValue: '',
// 当前点击的图标序号
currentIconIndex: -1
}
},
computed: {
resultIconList() {
if (this.searchValue === '') return this.iconList
return this.iconList.filter((item) => {
return item.name.includes(this.searchValue)
})
}
},
methods: {
// input输入的内容
saerchInput(e) {
this.searchValue = e.target.value
},
// 点击图标
clickIcon(index, name) {
this.currentIconIndex = index
this.$tn.message.toast(name, false, null, 'none', 5000)
},
}
}
import iconData from './iconfont.js'
export default {
name: 'basicIcon',
data() {
return {
// 图标列表
iconList: iconData.data,
// 用户输入的内容
searchValue: '',
// 当前点击的图标序号
currentIconIndex: -1,
}
},
computed: {
resultIconList() {
//filter会改变原数组,故使用深拷贝
var newArr = JSON.parse(JSON.stringify(iconData.data))
if (!this.searchValue) return iconData.data
return newArr.filter((item1) => {
item1.icons = item1.icons.filter((item2) => {
if (item2.name.includes(this.searchValue) || item2.icon.includes(this.searchValue)) {
return item2
}
})
if (item1.icons.length > 0) {
return item1
}
})
}
},
methods: {
// input输入的内容
saerchInput(e) {
//重新设置为-1,避免选中之前选中的图标
this.currentIconIndex=-1;
this.searchValue = e.target.value;
this.resultIconList
},
// 点击图标
clickIcon(index, name ,icon) {
this.currentIconIndex = index
// this.$tn.message.toast(name, false, null, 'none', 5000)
//这里点击后直接复制图标名称。
uni.setClipboardData({
data: icon,
showToast: false,
success: () => {
console.log('success'); //复制成功
this.$tn.message.toast('已复制:' + icon, false, null, 'none', 5000)
}
});
// const save = function (e) {
// e.clipboardData.setData('text/plain', name)
// e.preventDefault() // 阻止默认行为
// }
// const once = {
// once: true
// }
// document.addEventListener('copy', save, once) // 添加一个copy事件,当触发时执行一次,执行完删除
// document.execCommand('copy') // 执行copy方法
},
}
}
</script>
<style lang="scss" scoped>
/* 搜索框 start */
.search-content {
padding-top: 16rpx;
margin: 40rpx 40rpx;
&__input {
caret-color: $tn-main-color;
width: 100%;
height: 70rpx;
line-height: 60rpx;
border-radius: 100rpx;
text-align: center;
margin: 0 auto;
background-color: #FFFFFF;
color: #080808;
box-shadow: 0rpx 0rpx 80rpx 0rpx rgba(0, 0, 0, 0.05);
}
&__input-placeholder {
font-size: 24rpx;
}
}
/* 搜索框 end */
/* 图标容器 start */
.icon-shadow{
box-shadow: 0rpx 0rpx 80rpx 0rpx rgba(0, 0, 0, 0.06);
}
.icon {
&__container {
margin-bottom: 30rpx;
}
&__item {
width: 30.4%;
background-color: #FFFFFF;
border-radius: 10rpx;
padding: 30rpx;
margin: 20rpx 10rpx;
margin-top: 0;
transform: scale(1);
transition: transform 0.2s linear;
transform-origin: center center;
&--active {
transform: scale(0.95);
box-shadow:
inset 10rpx 10rpx 18rpx rgba(0, 0, 120, 0.04),
inset -8rpx -8rpx 20rpx rgba(0, 0, 120, 0.03);
}
&--icon {
width: 80rpx;
height: 80rpx;
font-size: 60rpx;
border-radius: 50%;
margin-bottom: 18rpx;
position: relative;
z-index: 1;
&::after {
content: " ";
position: absolute;
z-index: -1;
width: 100%;
height: 100%;
left: 0;
bottom: 0;
border-radius: inherit;
opacity: 1;
transform: scale(1, 1);
background-size: 100% 100%;
background-image: url(https://tnuiimage.tnkjapp.com/cool_bg_image/icon_bg.png);
}
}
&--title {
width: 100%;
color: #78909C;
font-size: 28rpx;
text-align: center;
}
}
}
/* 图标容器 end */
</style>
.search-fixed {
position: fixed;
width: 100%;
transition: all 0.25s ease-out;
z-index: 1;
}
/* 搜索框 start */
.search-content {
padding-top: 16rpx;
margin: 40rpx 40rpx;
&__input {
caret-color: $tn-main-color;
width: 100%;
height: 70rpx;
line-height: 60rpx;
border-radius: 100rpx;
text-align: center;
margin: 0 auto;
background-color: #FFFFFF;
color: #080808;
box-shadow: 0rpx 0rpx 80rpx 0rpx rgba(0, 0, 0, 0.05);
}
&__input-placeholder {
font-size: 24rpx;
}
}
/* 搜索框 end */
/* 图标容器 start */
.icon-shadow {
box-shadow: 0rpx 0rpx 80rpx 0rpx rgba(0, 0, 0, 0.06);
}
.icon {
&__container {
margin-bottom: 30rpx;
}
&__item {
width: 30.4%;
background-color: #FFFFFF;
border-radius: 10rpx;
padding: 30rpx;
margin: 20rpx 10rpx;
margin-top: 0;
transform: scale(1);
transition: transform 0.2s linear;
transform-origin: center center;
&--active {
transform: scale(0.95);
box-shadow:
inset 10rpx 10rpx 18rpx rgba(0, 0, 120, 0.04),
inset -8rpx -8rpx 20rpx rgba(0, 0, 120, 0.03);
}
&--icon {
width: 80rpx;
height: 80rpx;
font-size: 60rpx;
border-radius: 50%;
margin-bottom: 18rpx;
position: relative;
z-index: 1;
&::after {
content: " ";
position: absolute;
z-index: -1;
width: 100%;
height: 100%;
left: 0;
bottom: 0;
border-radius: inherit;
opacity: 1;
transform: scale(1, 1);
background-size: 100% 100%;
background-image: url(https://tnuiimage.tnkjapp.com/cool_bg_image/icon_bg.png);
}
}
&--title {
width: 100%;
text-align: center;
}
}
}
/* 图标容器 end */
</style>
File diff suppressed because one or more lines are too long
+302 -309
View File
@@ -1,324 +1,317 @@
<template>
<view class="components-keyboard">
<view class="components-keyboard">
<!-- 顶部自定义导航 -->
<tn-nav-bar fixed>keyboard键盘</tn-nav-bar>
<!-- 顶部自定义导航 -->
<tn-nav-bar fixed>keyboard键盘</tn-nav-bar>
<!-- 页面内容 -->
<view :style="{paddingTop: vuex_custom_bar_height + 'px'}">
<dynamic-demo-template ref="demoTemplate" :tips="tips" :sectionList="sectionList" :full="true" @click="click">
<view v-if="mode === 'number' || mode === 'card'" class="number-value">
<tn-input v-model="inputValue" type="text" :disabled="true" :border="true" placeholder="键盘输入的内容" @click="showKeyboard"></tn-input>
<tn-button class="clear-btn" backgroundColor="tn-bg-gray" fontColor="white" width="120rpx" height="70rpx" @click="clearInputValue">清空</tn-button>
</view>
<view v-else class="car-value">
<block v-for="(item, index) in 8" :key="index">
<view
class="car-input"
:class="{'new-energy': index === 7 && !licensePlateValue[index]}"
@tap="chooseLicensePlateNumber(index)"
>
<block v-if="index === 7 && !licensePlateValue[index]">
<view class="new-energy-car">
<view class="icon tn-icon-add"></view>
<view class="text">新能源</view>
</view>
</block>
<block v-else>
{{ licensePlateValue[index] || '' }}
</block>
</view>
<view class="car-point" v-if="index === 1">
<view class="point"></view>
</view>
</block>
</view>
</dynamic-demo-template>
<!-- 页面内容 -->
<view :style="{paddingTop: vuex_custom_bar_height + 'px'}">
</view>
<!-- 键盘 -->
<tn-keyboard
v-model="value"
:mode="mode"
:dotEnabled="dotEnabled"
:randomEnabled="randomEnabled"
:switchEnMode="switchEnMode"
:tooltip="tooltip"
:mask="mask"
@change="onChange"
@cancel="onCancel"
@confirm="onConfirm"
@backspace="onBackspace"
></tn-keyboard>
<dynamic-demo-template ref="demoTemplate" :tips="tips" :sectionList="sectionList" :full="true"
@click="click">
<view v-if="mode === 'number' || mode === 'card'" class="number-value">
<tn-input v-model="inputValue" type="text" :disabled="true" :border="true" placeholder="键盘输入的内容"
@click="showKeyboard"></tn-input>
<tn-button class="clear-btn" backgroundColor="tn-bg-gray" fontColor="white" width="120rpx"
height="70rpx" @click="clearInputValue">清空</tn-button>
</view>
<view v-else class="car-value">
<block v-for="(item, index) in 8" :key="index">
<view class="car-input" :class="{'new-energy': index === 7 && !licensePlateValue[index]}"
@tap="chooseLicensePlateNumber(index)">
<block v-if="index === 7 && !licensePlateValue[index]">
<view class="new-energy-car">
<view class="icon tn-icon-add"></view>
<view class="text">新能源</view>
</view>
</block>
<block v-else>
<block v-if="licensePlateValue[index]===''">
</view>
</block>
<block v-else>
{{ licensePlateValue[index]}}
</block>
</block>
</view>
<view class="car-point" v-if="index === 1">
<view class="point"></view>
</view>
</block>
</view>
</dynamic-demo-template>
</view>
<!-- 键盘 -->
<tn-keyboard v-model="value" :mode="mode" :dotEnabled="dotEnabled" :randomEnabled="randomEnabled"
:switchEnMode="switchEnMode" :tooltip="tooltip" :mask="mask" @change="onChange" @cancel="onCancel"
@confirm="onConfirm" @backspace="onBackspace"></tn-keyboard>
</view>
</template>
<script>
import dynamicDemoTemplate from '@/libs/components/dynamic-demo-template.vue'
export default {
name: 'componentsKeyboard',
components: {dynamicDemoTemplate},
data() {
return {
value: false,
mode: 'number',
dotEnabled: true,
randomEnabled: false,
switchEnMode: false,
tooltip: true,
mask: true,
// 输入的值
inputValue: '',
// 输入的车牌
licensePlateValue: ['','','','','','','',''],
// 当前选择输入的车牌号码位置
currentLicensePlateIndex: 0,
tips: ['无需依赖额外的样式文件','使用tn-keyboard组件'],
sectionList: [
{
name: '参数切换',
section: [
{
title: '模式',
optional: ['数字','身份证','车牌'],
methods: 'modeChange'
},
{
title: '显示点',
optional: ['显示','隐藏'],
methods: 'dotEnabledChange'
},
{
title: '打乱顺序',
optional: ['是','否'],
methods: 'randomEnabledChange',
current: 1
},
{
title: '显示顶部工具栏',
optional: ['显示','隐藏'],
methods: 'tooltipChange'
},
{
title: '遮罩显示',
optional: ['是','否'],
methods: 'maskChange'
}
]
}
]
}
},
watch: {
mode(value) {
switch (value) {
case 'number':
this.$refs.demoTemplate.updateSectionBtnsValue(0, 0, 0)
break
case 'card':
this.$refs.demoTemplate.updateSectionBtnsValue(0, 0, 1)
break
case 'car':
this.$refs.demoTemplate.updateSectionBtnsValue(0, 0, 2)
break
}
},
currentLicensePlateIndex(value) {
if (value === 0) {
this.switchEnMode = false
} else {
this.switchEnMode = true
}
}
},
methods: {
click(event) {
this[event.methods] && this[event.methods](event)
},
// 弹出键盘
showKeyboard() {
this.value = true
},
// 切换模式
modeChange(event) {
switch (event.index) {
case 0:
this.mode = 'number'
this.dotEnabled = true
this.value = true
this.$refs.demoTemplate.updateSectionBtnsState(1, true)
break
case 1:
this.mode = 'card'
this.value = true
this.$refs.demoTemplate.updateSectionBtnsState(1, false)
break
case 2:
this.mode = 'car'
this.licensePlateValue = ['','','','','','','','']
this.$refs.demoTemplate.updateSectionBtnsState(1, false)
break
}
},
// 切换点显示
dotEnabledChange(event) {
this.dotEnabled = event.index === 0 ? true : false
this.value = true
},
// 切换乱序显示
randomEnabledChange(event) {
this.randomEnabled = event.index === 0 ? true : false
this.value = true
},
// 切换顶部工具栏显示
tooltipChange(event) {
this.tooltip = event.index === 0 ? true : false
this.value = true
},
// 切换遮罩状态
maskChange(event) {
this.mask = event.index === 0 ? true : false
this.value = true
},
// 键盘输入值改变
onChange(e) {
if (this.mode === 'number' || this.mode === 'card') {
this.inputValue += e
} else if (this.mode === 'car') {
// 判断输入的值是否正确
if (this.currentLicensePlateIndex === 0 && !this.$tn.test.chinese(e)) {
this.$tn.message.toast('车牌归属地选择错误')
return
} else if (this.currentLicensePlateIndex === 1 && !this.$tn.test.letter(e)) {
this.$tn.message.toast('车牌归属地字母选择错误')
return
}
if (this.currentLicensePlateIndex !== 0 && !this.$tn.test.enOrNum(e)) {
this.$tn.message.toast('车牌号码选择错误')
return
}
// this.licensePlateValue[this.currentLicensePlateIndex] = e
this.$set(this.licensePlateValue, this.currentLicensePlateIndex, e)
this.currentLicensePlateIndex++
// 判断车牌是否已经选择完成
if (this.currentLicensePlateIndex === 8) {
this.value = false
}
}
},
// 点击了取消按钮
onCancel() {
this.$tn.message.toast('点击了取消按钮')
},
// 点击了确认按钮
onConfirm() {
this.$tn.message.toast('点击了确认按钮')
this.value = false
},
// 点击了退格按钮
onBackspace() {
if (this.mode === 'number' || this.mode === 'card') {
if (!this.inputValue) {
return
}
this.inputValue = this.inputValue.substring(0, this.inputValue.length - 1)
} else if (this.mode === 'car') {
let licensePlateIndex = this.currentLicensePlateIndex > 7 ? 7 : this.currentLicensePlateIndex
if (this.licensePlateValue[licensePlateIndex] !== '') {
this.$set(this.licensePlateValue, licensePlateIndex, '')
} else {
licensePlateIndex = licensePlateIndex < 1 ? 0 : --licensePlateIndex
this.$set(this.licensePlateValue, licensePlateIndex, '')
}
this.currentLicensePlateIndex = licensePlateIndex
}
},
// 点击车牌对于位置进行选择输入
chooseLicensePlateNumber(index) {
this.currentLicensePlateIndex = index
this.mode = 'car'
this.value = true
},
// 清空内容
clearInputValue() {
this.inputValue = ''
}
import dynamicDemoTemplate from '@/libs/components/dynamic-demo-template.vue'
export default {
name: 'componentsKeyboard',
components: {
dynamicDemoTemplate
},
data() {
return {
value: false,
mode: 'number',
dotEnabled: true,
randomEnabled: false,
switchEnMode: false,
tooltip: true,
mask: true,
},
// 输入的值
inputValue: '',
// 输入的车牌
licensePlateValue: ['', '', '', '', '', '', '', ''],
// 当前选择输入的车牌号码位置
currentLicensePlateIndex: 0,
}
tips: ['无需依赖额外的样式文件', '使用tn-keyboard组件'],
sectionList: [{
name: '参数切换',
section: [{
title: '模式',
optional: ['数字', '身份证', '车牌'],
methods: 'modeChange'
},
{
title: '显示点',
optional: ['显示', '隐藏'],
methods: 'dotEnabledChange'
},
{
title: '打乱顺序',
optional: ['是', '否'],
methods: 'randomEnabledChange',
current: 1
},
{
title: '显示顶部工具栏',
optional: ['显示', '隐藏'],
methods: 'tooltipChange'
},
{
title: '遮罩显示',
optional: ['是', '否'],
methods: 'maskChange'
}
]
}]
}
},
watch: {
mode(value) {
switch (value) {
case 'number':
this.$refs.demoTemplate.updateSectionBtnsValue(0, 0, 0)
break
case 'card':
this.$refs.demoTemplate.updateSectionBtnsValue(0, 0, 1)
break
case 'car':
this.$refs.demoTemplate.updateSectionBtnsValue(0, 0, 2)
break
}
},
currentLicensePlateIndex(value) {
if (value === 0) {
this.switchEnMode = false
} else {
this.switchEnMode = true
}
}
},
methods: {
click(event) {
this[event.methods] && this[event.methods](event)
},
// 弹出键盘
showKeyboard() {
this.value = true
},
// 切换模式
modeChange(event) {
switch (event.index) {
case 0:
this.mode = 'number'
this.dotEnabled = true
this.value = true
this.$refs.demoTemplate.updateSectionBtnsState(1, true)
break
case 1:
this.mode = 'card'
this.value = true
this.$refs.demoTemplate.updateSectionBtnsState(1, false)
break
case 2:
this.mode = 'car'
this.licensePlateValue = ['', '', '', '', '', '', '', '']
this.$refs.demoTemplate.updateSectionBtnsState(1, false)
break
}
},
// 切换点显示
dotEnabledChange(event) {
this.dotEnabled = event.index === 0 ? true : false
this.value = true
},
// 切换乱序显示
randomEnabledChange(event) {
this.randomEnabled = event.index === 0 ? true : false
this.value = true
},
// 切换顶部工具栏显示
tooltipChange(event) {
this.tooltip = event.index === 0 ? true : false
this.value = true
},
// 切换遮罩状态
maskChange(event) {
this.mask = event.index === 0 ? true : false
this.value = true
},
// 键盘输入值改变
onChange(e) {
if (this.mode === 'number' || this.mode === 'card') {
this.inputValue += e
} else if (this.mode === 'car') {
// 判断输入的值是否正确
if (this.currentLicensePlateIndex === 0 && !this.$tn.test.chinese(e)) {
this.$tn.message.toast('车牌归属地选择错误')
return
} else if (this.currentLicensePlateIndex === 1 && !this.$tn.test.letter(e)) {
this.$tn.message.toast('车牌归属地字母选择错误')
return
}
if (this.currentLicensePlateIndex !== 0 && !this.$tn.test.enOrNum(e)) {
this.$tn.message.toast('车牌号码选择错误')
return
}
// this.licensePlateValue[this.currentLicensePlateIndex] = e
this.$set(this.licensePlateValue, this.currentLicensePlateIndex, e)
this.currentLicensePlateIndex++
// 判断车牌是否已经选择完成
if (this.currentLicensePlateIndex === 8) {
this.value = false
}
}
},
// 点击了取消按钮
onCancel() {
this.$tn.message.toast('点击了取消按钮')
},
// 点击了确认按钮
onConfirm() {
this.$tn.message.toast('点击了确认按钮')
this.value = false
},
// 点击了退格按钮
onBackspace() {
if (this.mode === 'number' || this.mode === 'card') {
if (!this.inputValue) {
return
}
this.inputValue = this.inputValue.substring(0, this.inputValue.length - 1)
} else if (this.mode === 'car') {
let licensePlateIndex = this.currentLicensePlateIndex > 7 ? 7 : this.currentLicensePlateIndex
if (this.licensePlateValue[licensePlateIndex] !== '') {
this.$set(this.licensePlateValue, licensePlateIndex, '')
} else {
licensePlateIndex = licensePlateIndex < 1 ? 0 : --licensePlateIndex
this.$set(this.licensePlateValue, licensePlateIndex, '')
}
this.currentLicensePlateIndex = licensePlateIndex
}
},
// 点击车牌对于位置进行选择输入
chooseLicensePlateNumber(index) {
this.currentLicensePlateIndex = index
this.mode = 'car'
this.value = true
},
// 清空内容
clearInputValue() {
this.inputValue = ''
}
},
}
</script>
<style lang="scss" scoped>
.number-value {
display: flex;
align-items: center;
.clear-btn {
margin-left: 10rpx;
}
}
.car-value {
display: flex;
.car-input {
position: relative;
display: flex;
align-items: center;
justify-content: center;
height: 74rpx;
width: 64rpx;
border: 1px solid $tn-border-solid-color;
border-radius: 18rpx;
text-align: center;
font-size: 38rpx;
line-height: 1;
margin-left: 10rpx;
background-color: #FFFFFF;
&.new-energy {
background: transparent;
border-style: dashed;
}
}
.car-point {
display: flex;
align-items: center;
justify-content: center;
height: 74rpx;
width: 20rpx;
margin-left: 10rpx;
.point {
width: 20rpx;
height: 20rpx;
background-color: $tn-font-holder-color;
border-radius: 50%;
}
}
.new-energy-car {
display: flex;
flex-direction: column;
font-size: 16rpx;
color: $tn-font-sub-color;
.icon {
margin-bottom: 8rpx;
}
}
}
</style>
.number-value {
display: flex;
align-items: center;
.clear-btn {
margin-left: 10rpx;
}
}
.car-value {
display: flex;
.car-input {
position: relative;
display: flex;
align-items: center;
justify-content: center;
height: 74rpx;
width: 64rpx;
border: 1px solid $tn-border-solid-color;
border-radius: 18rpx;
text-align: center;
font-size: 38rpx;
line-height: 1;
margin-left: 10rpx;
background-color: #FFFFFF;
&.new-energy {
background: transparent;
border-style: dashed;
}
}
.car-point {
display: flex;
align-items: center;
justify-content: center;
height: 74rpx;
width: 20rpx;
margin-left: 10rpx;
.point {
width: 20rpx;
height: 20rpx;
background-color: $tn-font-holder-color;
border-radius: 50%;
}
}
.new-energy-car {
display: flex;
flex-direction: column;
font-size: 16rpx;
color: $tn-font-sub-color;
.icon {
margin-bottom: 8rpx;
}
}
}
</style>
+5 -1
View File
@@ -64,7 +64,11 @@
{
value: 5,
label: '胖虎'
}
},
{
value: 6,
label: '大星'
},
],
maskCloseable: true,
+3
View File
@@ -22,4 +22,7 @@ const app = new Vue({
...App
})
// 引入请求封装
require('./util/request/index')(app)
app.$mount()
+3 -2
View File
@@ -85,7 +85,7 @@
"quickapp" : {},
/* */
"mp-weixin" : {
"appid" : "wx97458d91caa76a6a",
"appid" : "wxf3d81a452b88ff4b",
"setting" : {
"urlCheck" : false,
"es6" : true,
@@ -119,7 +119,8 @@
"title" : "Tuniao UI",
"template" : "template.h5.html",
"router" : {
"mode" : "hash"
"mode" : "hash",
"base" : "./"
},
"sdkConfigs" : {
"maps" : {
+3 -3
View File
@@ -237,9 +237,9 @@ export default {
},
{
icon: 'rocket',
title: '期待你的加入',
author: '期待你的加入',
url: '/templatePage/life/candle/candle'
title: '微信红包封面',
author: '微信红包封面',
url: '/templatePage/life/cover/cover'
}
]
}
+299
View File
@@ -0,0 +1,299 @@
/**
* 页面展示列表数据
*/
export default {
data: [{
title: '圈子博客',
backgroundColor: 'tn-cool-bg-color-1',
list: [{
icon: 'order',
title: '操作指引',
author: '图鸟科技',
url: '/vipPage/life/guide/guide'
},{
icon: 'order',
title: '首次指引',
author: '图鸟科技',
url: '/vipPage/life/start/start'
},
{
icon: 'order',
title: '圈子首页',
author: '图鸟科技',
url: '/vipPage/blog/blog/blog'
},
{
icon: 'order',
title: '社交圈子',
author: '图鸟科技',
url: '/vipPage/blog/socialize/socialize'
},
{
icon: 'order',
title: '简约圈子(旧)',
author: '图鸟科技',
url: '/vipPage/blog/circle/circle'
},
{
icon: 'order',
title: '圈子个人',
author: '图鸟科技',
url: '/vipPage/blog/myblog/myblog'
},
{
icon: 'order',
title: '消息通知',
author: '图鸟科技',
url: '/vipPage/blog/message/message'
},
{
icon: 'order',
title: '商品优选',
author: '图鸟科技',
url: '/vipPage/blog/prefer/prefer'
},
{
icon: 'order',
title: '优选详情',
author: '图鸟科技',
url: '/vipPage/blog/product/product'
},
{
icon: 'order',
title: '博客博主',
author: '图鸟科技',
url: '/vipPage/blog/blogger/blogger'
},
{
icon: 'order',
title: '酷炫功能',
author: '图鸟科技',
url: '/vipPage/home/cool/cool'
},
{
icon: 'order',
title: '友情链接',
author: '图鸟科技',
url: '/vipPage/home/link/link'
},
{
icon: 'order',
title: '祝福页面',
author: '图鸟科技',
url: '/vipPage/life/bless/bless'
}
]
},
{
title: '酷炫首页',
backgroundColor: 'tn-cool-bg-color-1',
list: [{
icon: 'order',
title: '图鸟首页',
author: '图鸟科技',
url: '/vipPage/home/tuniao/tuniao'
},
{
icon: 'order',
title: '奶茶首页',
author: '图鸟科技',
url: '/vipPage/home/tea/tea'
},
{
icon: 'order',
title: '阅读首页',
author: '图鸟科技',
url: '/vipPage/home/read/read'
},
{
icon: 'order',
title: '月亮首页',
author: '图鸟科技',
url: '/vipPage/home/moon/moon'
},
{
icon: 'order',
title: '计划首页',
author: '图鸟科技',
url: '/vipPage/home/plan/plan'
},
{
icon: 'order',
title: '新年首页',
author: '图鸟科技',
url: '/vipPage/home/year/year'
},
{
icon: 'order',
title: '电影首页',
author: '图鸟科技',
url: '/vipPage/home/movie/movie'
},
{
icon: 'order',
title: '食物首页',
author: '图鸟科技',
url: '/vipPage/home/food/food'
},
{
icon: 'order',
title: '拟态首页',
author: '图鸟科技',
url: '/vipPage/home/mimicry/mimicry'
},
{
icon: 'order',
title: '充电首页',
author: '图鸟科技',
url: '/vipPage/life/power/power'
},
{
icon: 'order',
title: '卡片首页',
author: '图鸟科技',
url: '/vipPage/home/card/card'
},
{
icon: 'order',
title: '健康首页',
author: '图鸟科技',
url: '/vipPage/home/health/health'
},
{
icon: 'order',
title: '全景首页',
author: '图鸟科技',
url: '/vipPage/home/panoramic/panoramic'
},
{
icon: 'order',
title: 'uCharts首页',
author: '图鸟科技',
url: '/vipPage/home/ucharts/ucharts'
},
]
},
{
title: '商城店铺',
backgroundColor: 'tn-cool-bg-color-1',
list: [{
icon: 'order',
title: '店铺商品',
author: '图鸟科技',
url: '/vipPage/shop/store/store'
},
{
icon: 'order',
title: '商品订单',
author: '图鸟科技',
url: '/vipPage/shop/order/order'
},
{
icon: 'order',
title: '商品分类',
author: '图鸟科技',
url: '/vipPage/shop/classify/classify'
},
{
icon: 'order',
title: '积分活动',
author: '图鸟科技',
url: '/vipPage/shop/money/money'
}
]
},
{
title: '会员需求',
backgroundColor: 'tn-cool-bg-color-1',
list: [{
icon: 'order',
title: 'Drag长按拖拽随机固定',
author: '图鸟科技',
url: '/vipPage/components/drag/basic-drag/basic-drag'
},
{
icon: 'order',
title: '图片上传长按拖拽',
author: '图鸟科技',
url: '/vipPage/components/drag/upload-image-drag/upload-image-drag'
},
{
icon: 'order',
title: 'Cropper图片裁剪',
author: '图鸟科技',
url: '/vipPage/components/cropper/cropper'
},
{
icon: 'order',
title: 'StackSwiper堆叠轮播',
author: '图鸟科技',
url: '/vipPage/components/stack-swiper/stack-swiper'
},
{
icon: 'order',
title: '重力效果',
author: '图鸟科技',
url: '/vipPage/home/gravity/page1/page1'
},
{
icon: 'order',
title: '下拉刷新',
author: '图鸟科技',
url: '/vipPage/components/scroll-view/index'
},
{
icon: 'order',
title: '级联选择',
author: '图鸟科技',
url: '/vipPage/components/cascade-selection/cascade-selection'
},
{
icon: 'order',
title: '瀑布流',
author: '图鸟科技',
url: '/vipPage/components/waterfall/waterfall'
},
{
icon: 'order',
title: '树形菜单',
author: '图鸟科技',
url: '/vipPage/components/tree-view/tree-view'
},
{
icon: 'order',
title: '表格',
author: '图鸟科技',
url: '/vipPage/components/table/index'
},
{
icon: 'order',
title: '图鸟轮播(实验)',
author: '图鸟科技',
url: '/vipPage/components/custom-swiper/index'
}
]
},
{
title: '生态支持',
backgroundColor: 'tn-cool-bg-color-1',
list: [{
icon: 'order',
title: '短视频',
author: '图鸟 & 第三方',
url: '/thirdPage/short-video/short-video'
},{
icon: 'order',
title: '外卖模板',
author: '图鸟 & 第三方',
url: '/takeOutPage/walking-route/walking-route'
},
{
icon: 'order',
title: '期待你的加入',
author: '图鸟 & 第三方',
url: '/templatePage/life/candle/candle'
}
]
}
]
}
+18 -1
View File
@@ -7,6 +7,7 @@
"path": "pages/index/index",
"style": {
"mp-weixin": {
"disableScroll": true
},
"app-plus": {
"bounce": "none"
@@ -510,7 +511,8 @@
}, {
"path": "life/share/share",
"style": {
"navigationBarTitleText": "会员分享",
"navigationBarTitleText": "",
// 图鸟公众号
"enablePullDownRefresh": false
}
}, {
@@ -519,6 +521,12 @@
"navigationBarTitleText": "敬请期待",
"enablePullDownRefresh": false
}
}, {
"path": "life/cover/cover",
"style": {
"navigationBarTitleText": "红包封面",
"enablePullDownRefresh": false
}
}, {
"path": "time/clock/clock",
"style": {
@@ -616,6 +624,15 @@
"enablePullDownRefresh": false
}
}]
}, {
"root": "vipPage",
"pages": []
}, {
"root": "thirdPage",
"pages": []
},{
"root":"takeOutPage",
"pages": []
}],
"preloadRule": {
"pages/index/index": {
+41 -11
View File
@@ -1,9 +1,31 @@
<template>
<view class="index">
<Basic v-if="tabberPageLoadFlag[0]" :style="{display: currentIndex === 0 ? '' : 'none'}" ref="basic"></Basic>
<components v-if="tabberPageLoadFlag[1]" :style="{display: currentIndex === 1 ? '' : 'none'}" ref="components"></components>
<templatePage v-if="tabberPageLoadFlag[2]" :style="{display: currentIndex === 2 ? '' : 'none'}" ref="template"></templatePage>
<tuniao v-if="tabberPageLoadFlag[3]" :style="{display: currentIndex === 3 ? '' : 'none'}" ref="about"></tuniao>
<view v-if="tabberPageLoadFlag[0]" :style="{display: currentIndex === 0 ? '' : 'none'}">
<scroll-view class="custom-tabbar-page" scroll-y enable-back-to-top @scrolltolower="tabbarPageScrollLower">
<Basic ref="basic"></Basic>
</scroll-view>
</view>
<view v-if="tabberPageLoadFlag[1]" :style="{display: currentIndex === 1 ? '' : 'none'}">
<scroll-view class="custom-tabbar-page" scroll-y enable-back-to-top @scrolltolower="tabbarPageScrollLower">
<components ref="components"></components>
</scroll-view>
</view>
<view v-if="tabberPageLoadFlag[2]" :style="{display: currentIndex === 2 ? '' : 'none'}">
<scroll-view class="custom-tabbar-page" scroll-y enable-back-to-top @scrolltolower="tabbarPageScrollLower">
<vip ref="vip"></vip>
</scroll-view>
</view>
<view v-if="tabberPageLoadFlag[3]" :style="{display: currentIndex === 3 ? '' : 'none'}">
<scroll-view class="custom-tabbar-page" scroll-y enable-back-to-top @scrolltolower="tabbarPageScrollLower">
<templatePage ref="template"></templatePage>
</scroll-view>
</view>
<view v-if="tabberPageLoadFlag[4]" :style="{display: currentIndex === 4 ? '' : 'none'}">
<scroll-view class="custom-tabbar-page" scroll-y enable-back-to-top @scrolltolower="tabbarPageScrollLower">
<tuniao ref="about"></tuniao>
</scroll-view>
</view>
<tn-tabbar
v-model="currentIndex"
@@ -23,6 +45,7 @@
import Basic from '../basic/basic.vue'
import Components from '../components/components.vue'
import TemplatePage from '../template/template.vue'
import Vip from '../vip/vip.vue'
import Tuniao from '../tuniao/tuniao.vue'
export default {
@@ -30,6 +53,7 @@
Basic,
Components,
TemplatePage,
Vip,
Tuniao
},
data() {
@@ -47,9 +71,19 @@
inactiveIcon: 'honor'
},
{
title: '页面',
activeIcon: 'discover',
inactiveIcon: 'discover'
title: '会员',
activeIcon: 'vip-fill',
inactiveIcon: 'vip',
activeIconColor: '#FFFFFF',
inactiveIconColor: '#FFFFFF',
iconSize: 50,
out: true
},
{
title: '发现',
activeIcon: 'discover-fill',
inactiveIcon: 'discover',
count: 100
},
{
title: '图鸟',
@@ -72,10 +106,6 @@
})
this.switchTabbar(index)
},
onPageScroll(e) {
},
onReachBottom() {
},
methods: {
// 切换导航
switchTabbar(index) {
+508
View File
@@ -0,0 +1,508 @@
<template>
<view class="vip tn-safe-area-inset-bottom">
<view class="top-backgroup">
<image src='https://tnuiimage.tnkjapp.com/index_bg/tools_new.jpg' mode='widthFix' class='backgroud-image'></image>
</view>
<swiper class="card-swiper" :circular="true"
:autoplay="false" duration="500" interval="180000" previous-margin="170rpx" next-margin="170rpx" @change="cardSwiper" style="margin-top: -280rpx;">
<swiper-item v-for="(item,index) in swiperList" :key="index" :class="cardCur==index?'cur':''">
<!-- <view class="swiper-item image-banner">
<image :src="item.url" mode="widthFix" v-if="item.type=='image'"></image>
</view> -->
<!-- <view class="swiper-item image-banner" :style="'background-image:url('+ item.url + ');width: 230rpx;height: 100%;background-size:100% 100%;'">
</view> -->
<view class="tnphone-white-min swiper-item wow fadeInLeft2" @click="navTuniaoMoban">
<view class="skin wow fadeInRight2">
<view class="screen wow fadeInUp2">
<!-- <view class="head">
<text>{{item.name}}</text>
</view> -->
<view class="peak wow">
<view class="sound"></view>
<view class="lens"></view>
</view>
<!-- <view class="area-l">
<view class="">
<text class="tn-icon-all"></text>
<text class="tn-icon-wifi tn-padding-left-xs"></text>
</view>
</view>
<view class="area-r">
<view class="">
<text class="tn-icon-light"></text>
<text class="tn-icon-time tn-padding-left-xs"></text>
</view>
</view> -->
<!-- <view class="talk"></view> -->
<view class="image-banner">
<image :src="item.url" mode="aspectFill" v-if="item.type=='image'"></image>
</view>
</view>
</view>
</view>
</swiper-item>
</swiper>
<!-- 方式4 start-->
<view class="tn-flex tn-padding-bottom-sm">
<view class="tn-flex-1 tn-padding-sm tn-radius" @click="navTuniaoVUE3">
<view class="tn-flex tn-flex-direction-column tn-flex-row-center tn-flex-col-center">
<view class="icon4__item--icon tn-flex tn-flex-row-center tn-flex-col-center tn-shadow-blur">
<view class="tn-icon-level-fill tn-cool-color-icon4 tn-cool-bg-color-5"></view>
</view>
<view class="tn-color-gray--dark tn-text-center">
<text class="tn-text-ellipsis">图鸟VUE3</text>
</view>
</view>
</view>
<view class="tn-flex-1 tn-padding-sm tn-radius" @click="navTuniaoMoban">
<view class="tn-flex tn-flex-direction-column tn-flex-row-center tn-flex-col-center">
<view class="icon4__item--icon tn-flex tn-flex-row-center tn-flex-col-center tn-shadow-blur">
<view class="tn-icon-discover-planet-fill tn-cool-color-icon4 tn-cool-bg-color-8"></view>
</view>
<view class="tn-color-gray--dark tn-text-center">
<text class="tn-text-ellipsis">圈子博客</text>
</view>
</view>
</view>
<view class="tn-flex-1 tn-padding-sm tn-radius" @click="navBusiness">
<view class="tn-flex tn-flex-direction-column tn-flex-row-center tn-flex-col-center">
<view class="icon4__item--icon tn-flex tn-flex-row-center tn-flex-col-center tn-shadow-blur">
<view class="tn-icon-identity-fill tn-cool-color-icon4 tn-cool-bg-color-15"></view>
</view>
<view class="tn-color-gray--dark tn-text-center">
<text class="tn-text-ellipsis">简约商圈</text>
</view>
</view>
</view>
<view class="tn-flex-1 tn-padding-sm tn-radius" @click="navXiongJie">
<view class="tn-flex tn-flex-direction-column tn-flex-row-center tn-flex-col-center">
<view class="icon4__item--icon tn-flex tn-flex-row-center tn-flex-col-center tn-shadow-blur">
<view class="tn-icon-image-fill tn-cool-color-icon4 tn-cool-bg-color-3"></view>
</view>
<view class="tn-color-gray--dark tn-text-center">
<text class="tn-text-ellipsis">凶姐壁纸</text>
</view>
</view>
</view>
</view>
<!-- 方式4 end-->
<!-- banner start-->
<view class="tn-flex tn-flex-wrap tn-padding-xs" @click="navTuniaoMoban">
<view class="" style="width: 100%;">
<view class="image-piccapsule tn-shadow-blur" style="background-image:url('https://tnuiimage.tnkjapp.com/capsule-banner/banner-circle2.png');">
<view class="image-capsule">
</view>
</view>
</view>
</view>
<!-- banner end-->
<block v-for="(item, index) in navList" :key="index">
<view class="nav_title--wrap tn-margin-bottom-sm">
<view class="nav_title tn-cool-bg-color-15">{{ item.title | titleFilter}}</view>
</view>
<view class='nav-list'>
<block v-for="(content_item, content_index) in item.list" :key="content_index">
<navigator
open-type="navigate"
hover-class='none'
:url="content_item.url"
class="nav-list-item tn-shadow-blur tn-cool-bg-image"
:class="[
getRandomCoolBg(content_index)
]"
>
<view class="nav-link">
<view class='title'>{{ content_item.title }}</view>
<view class='author'> <text class="tn-icon-code tn-padding-right-xs"></text> {{ content_item.author }}</view>
</view>
<!-- <view class="icon">
<view :class="['tn-icon-' + content_item.icon]"></view>
</view> -->
</navigator>
</block>
</view>
</block>
<view class="tn-padding-xl tn-text-center tn-color-gray">
<view class="tn-padding-bottom-sm" @click="navPlus">
关于图鸟会员
<text class="tn-icon-rocket tn-padding-left-xs"></text>
</view>
</view>
</view>
</template>
<script>
import vipListData from '@/mock/vip_page.js'
export default {
name: 'Vip',
filters: {
titleFilter(value) {
if (value.length === 0) {
return ''
}
let newString = ''
for (let i = 0; i < value.length; i++) {
if (i !== 0) {
newString += ' / '
}
newString += value[i]
}
return newString
}
},
data() {
return {
cardCur: 0,
swiperList: [{
id: 0,
type: 'image',
url: 'https://tnuiimage.tnkjapp.com/index_bg/circle1.jpg',
}, {
id: 1,
type: 'image',
url: 'https://tnuiimage.tnkjapp.com/index_bg/circle2.jpg',
}, {
id: 2,
type: 'image',
url: 'https://tnuiimage.tnkjapp.com/index_bg/circle3.jpg',
}, {
id: 3,
type: 'image',
url: 'https://tnuiimage.tnkjapp.com/index_bg/circle4.jpg',
},{
id: 4,
type: 'image',
url: 'https://tnuiimage.tnkjapp.com/index_bg/circle5.jpg',
},{
id: 5,
type: 'image',
url: 'https://tnuiimage.tnkjapp.com/index_bg/circle6.jpg',
},{
id: 6,
type: 'image',
url: 'https://tnuiimage.tnkjapp.com/index_bg/circle7.jpg',
}
],
// nav菜单列表
navList: vipListData.data
}
},
methods: {
// cardSwiper
cardSwiper(e) {
this.cardCur = e.detail.current
},
getRandomCoolBg() {
return this.$tn.color.getRandomCoolBgClass()
},
// 跳转到图鸟VUE3
navTuniaoVUE3(e) {
wx.vibrateLong();
uni.navigateToMiniProgram({
appId: 'wxd4296f570b8b39c9'
})
},
// 跳转到图鸟模板
navTuniaoMoban(e) {
wx.vibrateLong();
uni.navigateToMiniProgram({
appId: 'wx13c0ed55c12d2afb'
})
},
// 跳转到关于作者
navSaitolia(e) {
wx.vibrateLong();
uni.navigateTo({
url: '/templatePage/life/plus/plus'
})
},
// 跳转到图鸟模板2
navBusiness(e) {
wx.vibrateLong();
uni.navigateToMiniProgram({
appId: 'wx9056aabd1c0c6a8a'
})
},
// 跳转到图鸟模板3
navXiongJie(e) {
wx.vibrateLong();
uni.navigateToMiniProgram({
appId: 'wx76bb942a2810e8e5'
})
},
// 跳转到会员协议
navPlus() {
uni.navigateTo({
url: '/templatePage/life/plus/plus'
})
},
}
}
</script>
<style lang="scss" scoped>
/* 顶部背景图 start */
.top-backgroup {
height: 350rpx;
z-index: -1;
.backgroud-image {
width: 100%;
height: 667rpx;
}
}
/* 顶部背景图 end */
/* 轮播 start */
/* .tnphone-white-min 细边框*/
.tnphone-white-min {width: 380rpx; height: 800rpx; border-radius: 40rpx; background: #E9E5F3; padding: 7rpx; display: table; color: #333;
box-sizing: border-box; box-shadow: 0rpx 10rpx 50rpx 0rpx rgba(0,0,0,0.15); margin: 70rpx auto; cursor: default; position: relative}
.tnphone-white-min .skin {width: 100%; height: 100%; border-radius: 40rpx; background: #E9E5F3; padding: 10rpx;}
.tnphone-white-min .screen {width: 100%; height: 100%; border-radius: 30rpx; background: #E9E5F3; position: relative; overflow: hidden}
.tnphone-white-min .head {width: 100%; height: 90rpx; text-align: center; position: absolute; padding: 45rpx 15rpx 10rpx 15rpx;}
.tnphone-white-min .peak {left: 22%;width: 56%; height: 27rpx; margin: -2rpx auto 0rpx; border-radius: 0 0 20rpx 20rpx; background: #E9E5F3; position: absolute}
.tnphone-white-min .sound {width: 48rpx; height: 6rpx; border-radius: 15rpx; background: #555; position: absolute; left: 50%; top: 50%; margin-left: -24rpx; margin-top: -10rpx;
box-shadow: 0rpx 4rpx 4rpx 0rpx #444 inset}
.tnphone-white-min .lens {width: 6rpx; height: 6rpx; border-radius: 50%; background: #2c5487; position: absolute; left: 50%; top: 50%; margin-left: 34rpx; margin-top: -10rpx}
.tnphone-white-min .talk {width: 50%; height: 6rpx; border-radius: 15rpx; background: rgba(0,0,0,.3); position: absolute; bottom: 8rpx; left: 50%; margin-left: -25%}
.tnphone-white-min .area-l,.tnphone-white-min .area-r {width: 70rpx; height: 16rpx; position: absolute; top: 6rpx}
.tnphone-white-min .area-l {left: 0; text-align: center; font-size: 12rpx; line-height: 22rpx; text-indent: 10rpx; font-weight: 600; padding-left: 20rpx;}
.tnphone-white-min .area-r {right: 0; text-align: center; font-size: 12rpx; line-height: 22rpx; text-indent: 10rpx; font-weight: 600; padding-right: 20rpx;}
.tnphone-white-min .fa-feed {float: left; font-size: 12rpx!important; transform:rotate(-45deg); margin-top: 4rpx; margin-right: 8rpx}
.tnphone-white-min .fa-battery-full {float: left; font-size: 12rpx!important; margin-top: 6rpx}
.tnphone-white-min .fa-chevron-left {float: left; margin-top: 4rpx}
.tnphone-white-min .fa-cog {float: right; margin-top: 4rpx}
.tnphone-white-min .btn01 {width: 3rpx; height: 28rpx; border-radius: 3rpx 0 0 3rpx; background: #222; position: absolute; top: 105rpx; left: -3rpx}
.tnphone-white-min .btn02 {width: 3rpx; height: 54rpx; border-radius: 3rpx 0 0 3rpx; background: #222; position: absolute; top: 160rpx; left: -3rpx}
.tnphone-white-min .btn03 {width: 3rpx; height: 54rpx; border-radius: 3rpx 0 0 3rpx; background: #222; position: absolute; top: 230rpx; left: -3rpx}
.tnphone-white-min .btn04 {width: 3rpx; height: 86rpx; border-radius: 0 3rpx 3rpx 0; background: #222; position: absolute; top: 180rpx; right: -3rpx}
/* 轮播样机样式 start*/
.card-swiper {
height: 830rpx !important;
}
.card-swiper swiper-item {
width: 260rpx !important;
// left: 170rpx;
// width: 380rpx !important;
box-sizing: border-box;
padding: 0rpx 15rpx 90rpx 15rpx;
overflow: initial;
}
.card-swiper swiper-item .swiper-item {
display: block;
transform: scale(0.45);
transition: all 0.2s ease-in 0s;
overflow: hidden;
}
.card-swiper swiper-item.cur .swiper-item {
transform: scale(0.65);
transition: all 0.2s ease-in 0s;
}
.image-banner{
display: flex;
align-items: center;
justify-content: center;
}
.image-banner image{
width: 100%;
height: 770rpx;
// border: 1rpx solid red;
}
/* 轮播指示点 start*/
.indication{
z-index: 9999;
width: 100%;
height: 36rpx;
position: absolute;
display:flex;
flex-direction:row;
align-items:center;
justify-content:center;
}
.spot{
background-color: #000;
opacity: 0;
width: 10rpx;
height: 10rpx;
border-radius: 20rpx;
margin: 0 8rpx !important;
top: -80rpx;
position: relative;
}
.spot.active{
opacity: 0;
width: 30rpx;
background-color: #000;
}
/* 轮播 end */
/* 图标容器4 start */
.tn-cool-color-icon4{
// background-image: -webkit-linear-gradient(135deg, #ED1C24, #FECE12); 16
// background-image: linear-gradient(135deg, #ED1C24, #FECE12);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
text-fill-color: transparent;
}
.icon4 {
&__item {
width: 30%;
background-color: #FFFFFF;
border-radius: 10rpx;
padding: 30rpx;
margin: 20rpx 10rpx;
transform: scale(1);
transition: transform 0.3s linear;
transform-origin: center center;
&--icon {
width: 110rpx;
height: 110rpx;
font-size: 55rpx;
border-radius: 50%;
margin-bottom: 18rpx;
position: relative;
z-index: 1;
box-shadow: 0px 10px 30px rgba(70,23,129, 0.12),
0px -8px 40px rgba(255, 255, 255, 1),
inset 0px -10px 10px rgba(70,23,129, 0.05),
inset 0px 10px 20px rgba(255, 255, 255, 1);
}
}
}
/* 标题start */
.nav_title {
-webkit-background-clip: text;
color: transparent;
&--wrap {
position: relative;
display: flex;
height: 120rpx;
font-size: 46rpx;
align-items: center;
justify-content: center;
font-weight: bold;
background-image: url(https://tnuiimage.tnkjapp.com/title_bg/title44.png);
background-size: cover;
}
}
/* 标题end */
/* 组件导航列表 start*/
.nav-list {
display: flex;
flex-wrap: wrap;
padding: 0rpx 12rpx 0rpx;
justify-content: space-between;
/* 列表元素 start */
.nav-list-item {
padding: 35rpx 30rpx 5rpx 30rpx;
border-radius: 12rpx;
width: 45%;
margin: 0 18rpx 40rpx;
background-size: cover;
background-position: center;
position: relative;
z-index: 99;
/* 元素标题 start */
.nav-link {
font-size: 32rpx;
text-transform: capitalize;
padding: 0 0 10rpx 0;
position: relative;
.title {
font-weight: bold;
font-size: 36rpx;
color: #FFFFFF;
margin-top: 30rpx;
text-align: center;
}
.author {
font-size: 25rpx;
color: #FFFFFF;
margin-top: 50rpx;
margin-left: -10rpx;
text-align: center;
}
}
/* 元素标题 end */
/* 元素图标 start */
.icon {
font-variant: small-caps;
position: absolute;
top: 20rpx;
right: 50rpx;
left: 37%;
width: 90rpx;
height: 90rpx;
line-height: 90rpx;
margin: 0;
padding: 0;
display: inline-flex;
text-align: center;
justify-content: center;
vertical-align: middle;
font-size: 50rpx;
color: #FFFFFF;
white-space: nowrap;
opacity: 0.9;
background-color: rgba(0, 0, 0, 0.05);
background-size: cover;
background-position: 50%;
border-radius: 5000rpx;
}
/* 元素图标 end */
}
/* 列表元素 end */
}
/* 组件导航列表 end*/
/* 胶囊banner*/
.image-capsule{
padding: 100rpx 0rpx;
font-size: 40rpx;
font-weight: 300;
position: relative;
}
.image-piccapsule{
background-size: cover;
background-repeat:no-repeat;
// background-attachment:fixed;
background-position:top;
border-radius: 20rpx 20rpx 0 0;
}
</style>
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

+226
View File
@@ -0,0 +1,226 @@
<template>
<view class="take-out__walking-route">
<!-- 顶部自定义导航 -->
<tn-nav-bar fixed>路线轨迹</tn-nav-bar>
<view :style="{paddingTop: vuex_custom_bar_height + 'px'}">
<map
class="take-out__map"
:longitude="location.longitude"
:latitude="location.latitude"
:scale="18"
:polyline="mapPolyline"
:markers="mapMarkers"
></map>
</view>
<tn-modal
v-model="openSettingFlag"
:custom="true"
:showCloseBtn="true"
width="auto"
padding="40rpx 80rpx"
>
<view class="tn-flex tn-flex-direction-column tn-flex-row-center tn-flex-col-center">
<view class="tn-text-bold tn-text-lg">授权使用位置信息</view>
<view class="tn-margin-top-xs tn-color-gray">获取定位失败, 请进行设置</view>
<view class="tn-margin-top">
<tn-button backgroundColor="#01BEFF" fontColor="#FFFFFF" @click="openSetting">打开设置</tn-button>
</view>
</view>
</tn-modal>
</view>
</template>
<script>
export default {
name: 'takeOutWalkingRoute',
data() {
return {
openSettingFlag: false,
location: {
latitude: 22.970675,
longitude: 113.334259
},
mapPolyline: [{
points: [
{latitude: 22.969238, longitude: 113.333890},
{latitude: 22.969309, longitude: 113.333878},
{latitude: 22.969334, longitude: 113.334132},
{latitude: 22.969649, longitude: 113.334130},
{latitude: 22.970044, longitude: 113.334130},
{latitude: 22.970578, longitude: 113.334130},
{latitude: 22.970606, longitude: 113.334255},
{latitude: 22.970667, longitude: 113.334345},
{latitude: 22.970673, longitude: 113.334458},
{latitude: 22.970798, longitude: 113.334452},
{latitude: 22.970962, longitude: 113.334530},
{latitude: 22.971108, longitude: 113.334564},
{latitude: 22.971111, longitude: 113.334631}
],
color: '#01BEFFFF',
width: 3,
arrowLine: true
}],
mapMarkers: [{
id: 0,
latitude: 22.971093,
longitude: 113.334740,
iconPath: '/takeOutPage/static/images/location.png',
width: 20,
height: 30,
callout: {
content: '您所在位置',
color: '#080808',
bgColor: 'transparent',
display: 'ALWAYS'
}
},{
id: 1,
latitude: 22.969238,
longitude: 113.333890,
iconPath: '/takeOutPage/static/images/take_out_car.png',
width: 30,
height: 30,
rotate: 270
}],
// 当前外卖小车的坐标位置Index
takeOutCarIndex: 0
}
},
onLoad() {
// #ifdef MP-WEIXIN || MP-ALIPAY || MP-BAIDU || MP-TOUTIAO || MP-QQ || MP-KUAISHOU || MP-LARK
// this.getUserAuthorize()
// #endif
// #ifdef APP-VUE || H5
// this.getUserLocation()
// #endif
this.runIntervalTimer = setInterval(() => {
let rotate = 0
const pointsLength = this.mapPolyline[0].points.length
if (this.takeOutCarIndex >= pointsLength) {
this.takeOutCarIndex = 0
}
if (this.takeOutCarIndex < pointsLength - 2) {
const currentPoint = this.mapPolyline[0].points[this.takeOutCarIndex]
const nextPoint = this.mapPolyline[0].points[this.takeOutCarIndex+1]
rotate = this.getAngle(currentPoint, nextPoint)
}
this.mapMarkers[1].latitude = this.mapPolyline[0].points[this.takeOutCarIndex].latitude
this.mapMarkers[1].longitude = this.mapPolyline[0].points[this.takeOutCarIndex].longitude
this.mapMarkers[1].rotate = rotate === 0 ? 0 : rotate - 90
this.takeOutCarIndex++
}, 500)
},
onUnload() {
if (this.runIntervalTimer) {
clearInterval(this.runIntervalTimer)
}
},
methods: {
// 获取用户是否已经授权获取位置信息
getUserAuthorize() {
uni.authorize({
scope: 'scope.userLocation',
success: (res) => {
this.getUserLocation()
},
fail: (err) => {
// 获取权限失败,判断用户是否禁止了获取用户信息
// #ifdef MP-WEIXIN || MP-ALIPAY || MP-BAIDU || MP-TOUTIAO || MP-QQ || MP-KUAISHOU || MP-LARK
uni.getSetting({
success: (settingRes) => {
if (!settingRes.authSetting.hasOwnProperty('scope.userLocation') || !settingRes.authSetting['scope.userLocation']) {
this.openSettingFlag = true
}
},
fail: (settingErr) => {}
})
// #endif
// #ifdef APP-VUE || H5
this.$tn.message.toast('获取定位失败, 请重试')
// #endif
}
})
},
// 获取用户的当前位置信息
getUserLocation() {
uni.getLocation({
type: 'gcj02',
success: (res) => {
this.location.latitude = res.latitude
this.location.longitude = res.longitude
},
fail: (err) => {
this.$tn.message.toast('获取定位失败, 请重试')
}
})
},
// 打开设置页面
openSetting() {
uni.openSetting({
success: (res) => {
if (res.authSetting.hasOwnProperty('scope.userLocation') && res.authSetting['scope.userLocation'] === true) {
this.openSettingFlag = false
this.getUserLocation()
} else {
this.$tn.message.toast('设置获取位置信息失败')
}
},
fail: (err) => {}
})
},
// 获取两点之间的角度
getAngle(current, next) {
let ret = 0
let w1 = current.latitude/180 * Math.PI
let j1 = current.longitude/180 * Math.PI
let w2 = next.latitude/180 * Math.PI
let j2 = next.longitude/180 * Math.PI
ret = 4 * Math.pow(Math.sin((w1 - w2) / 2), 2) - Math.pow(Math.sin((j1 - j2) / 2) * (Math.cos(w1) - Math.cos(w2)), 2)
ret = Math.sqrt(ret)
// var temp = Math.sin(Math.abs(j1 - j2) / 2) * (Math.cos(w1) + Math.cos(w2));
let temp = Math.sin((j1 - j2) / 2) * (Math.cos(w1) + Math.cos(w2))
ret = ret/temp
ret = Math.atan(ret) / Math.PI * 180
ret += 90
// 这里用如此臃肿的if..else是为了判定追踪单个点的具体情况,从而调整ret的值
if(j1-j2 < 0){
if(w1-w2 < 0){
ret = ret
}else{
ret = -ret+180
}
}else{
if(w1-w2 < 0){
ret = 180+ret
}else{
ret = -ret
}
}
return ret
}
}
}
</script>
<style lang="scss" scoped>
.take-out {
&__map {
width: 100%;
height: calc(100vh - 300px);
}
}
</style>
+22
View File
@@ -0,0 +1,22 @@
<template>
<view class="">
<web-view src="https://support.weixin.qq.com/cgi-bin/mmsupport-bin/showredpacket?receiveuri=dfvH6iYFZGS&check_type=2#wechat_redirect"></web-view>
</view>
</template>
<script>
export default {
data() {
return {
}
},
methods: {
}
}
</script>
<style scoped>
</style>
@@ -0,0 +1,654 @@
<template>
<view class="tn-cascade-selection tn-cascade-selection-class">
<scroll-view
class="selection__scroll-view"
:class="[{'tn-border-solid-bottom': headerLine}]"
:style="[scrollViewStyle]"
scroll-x
scroll-with-animation
:scroll-into-view="scrollViewId"
>
<view class="selection__header" :class="[backgroundColorClass]" :style="[headerStyle]">
<view
v-for="(item, index) in selectedArr"
:key="index"
:id="`select__${index}`"
class="selection__header__item"
:class="[headerItemClass(index)]"
:style="[headerItemStyle(index)]"
@tap.stop="clickNav(index)"
>
{{ item.text }}
<view
v-if="index===currentTab && showActiveLine"
class="selection__header__line"
:style="{backgroundColor: activeLineColor}"
></view>
</view>
</view>
</scroll-view>
<swiper
class="selection__list"
:class="[backgroundColorClass]"
:style="[listStyle]"
:current="currentTab"
:duration="300"
@change="switchTab"
>
<swiper-item
v-for="(item, index) in selectedArr"
:key="index"
>
<scroll-view
class="selection__list__item"
:style="{height: selectionContainerHeight + 'rpx'}"
scroll-y
:scroll-into-view="item.scrollViewId"
>
<view class="selection__list__item--first"></view>
<view
v-for="(subItem, subIndex) in item.list"
:key="subIndex"
:id="`select__${subIndex}`"
class="selection__list__item__cell"
:style="[itemStyle]"
@tap="change(index, subIndex, subItem)"
>
<view
v-if="item.index === subIndex"
class="selection__list__item__icon tn-icon-success"
:style="[itemIconStyle]"
></view>
<image
v-if="subItem.src"
class="selection__list__item__image"
:style="[itemImageStyle]"
:src="subItem.src"
></image>
<view
class="selection__list__item__title"
:class="[{'tn-text-bold': item.index === subIndex && itemActiveBold}]"
:style="[itemTitleStyle(index, subIndex)]"
>
{{ subItem.text }}
</view>
<view
v-if="subItem.subText"
class="selection__list__item__title--sub"
:style="[itemSubTitleStyle]"
>
{{ subItem.subText }}
</view>
</view>
</scroll-view>
</swiper-item>
</swiper>
</view>
</template>
<script>
import componentsColorMixin from '../../libs/mixin/components_color.js'
export default {
name: 'tn-cascade-selection',
mixins: [ componentsColorMixin ],
props: {
// 如果下一级是请求返回,则为第一级数据,否则为所有数据
/* {
text: '', // 标题
subText: '', // 子标题
src: '', // 图片地址
value: 0, // 选中的值
children: [
{
text: '',
subText: '',
value: 0,
children: []
}
]
} */
list: {
type: Array,
default() {
return []
}
},
// 默认选中值
// ['value1','value2','value3']
defaultValue: {
type: Array,
default() {
return []
}
},
// 子集数据通过请求来获取
request: {
type: Boolean,
default: false
},
// request为true时生效, 获取到的子集数据
receiveData: {
type: Array,
default() {
return []
}
},
// 显示header底部细线
headerLine: {
type: Boolean,
default: true
},
// header背景颜色
headerBgColor: {
type: String,
default: ''
},
// 顶部标签栏高度,单位rpx
tabsHeight: {
type: Number,
default: 88
},
// 默认显示文字
text: {
type: String,
default: '请选择'
},
// 选中的颜色
activeColor: {
type: String,
default: '#01BEFF'
},
// 选中后加粗
activeBold: {
type: Boolean,
default: true
},
// 选中显示底部线条
showActiveLine: {
type: Boolean,
default: true
},
// 线条颜色
activeLineColor: {
type: String,
default: '#01BEFF'
},
// icon大小,单位rpx
activeIconSize: {
type: Number,
default: 0
},
// icon颜色
activeIconColor: {
type: String,
default: '#01BEFF'
},
// item图片宽度, 单位rpx
itemImgWidth: {
type: Number,
default: 0
},
// item图片高度, 单位rpx
itemImgHeight: {
type: Number,
default: 0
},
// item图片圆角
itemImgRadius: {
type: String,
default: '50%'
},
// item text颜色
itemTextColor: {
type: String,
default: ''
},
// item text选中颜色
itemActiveTextColor: {
type: String,
default: ''
},
// item text选中加粗
itemActiveBold: {
type: Boolean,
default: true
},
// item text文字大小, 单位rpx
itemTextSize: {
type: Number,
default: 0
},
// item subText颜色
itemSubTextColor: {
type: String,
default: ''
},
// item subText字体大小, 单位rpx
itemSubTextSize: {
type: Number,
default: 0
},
// item样式
itemStyle: {
type: Object,
default() {
return {}
}
},
// selection选项容器高度, 单位rpx
selectionContainerHeight: {
type: Number,
default: 300
}
},
computed: {
scrollViewStyle() {
let style = {}
if (this.headerBgColor) {
style.backgroundColor = this.headerBgColor
}
return style
},
headerStyle() {
let style = {}
style.height = `${this.tabsHeight}rpx`
if (this.backgroundColorStyle) {
style.backgroundColor = this.backgroundColorStyle
}
return style
},
headerItemClass() {
return (index) => {
let clazz = ''
if (index !== this.currentTab) {
clazz += ` ${this.fontColorClass}`
} else {
if (this.activeBold) {
clazz += ' tn-text-bold'
}
}
return clazz
}
},
headerItemStyle() {
return (index) => {
let style = {}
style.color = index === this.currentTab ? this.activeColor : (this.fontColorStyle ? this.fontColorStyle : '')
if (this.fontSizeStyle) {
style.fontSize = this.fontSizeStyle
}
return style
}
},
listStyle() {
let style = {}
style.height = `${this.selectionContainerHeight}rpx`
if (this.backgroundColorStyle) {
style.color = this.backgroundColorStyle
}
return style
},
itemIconStyle() {
let style = {}
if (this.activeIconColor) {
style.color = this.activeIconColor
}
if (this.activeIconSize) {
style.fontSize = this.activeIconSize + 'rpx'
}
return style
},
itemImageStyle() {
let style = {}
if (this.itemImgWidth) {
style.width = this.itemImgWidth + 'rpx'
}
if (this.itemImgHeight) {
style.height = this.itemImgHeight + 'rpx'
}
if (this.itemImgRadius) {
style.borderRadius = this.itemImgRadius
}
return style
},
itemTitleStyle() {
return (index, subIndex) => {
let style = {}
if (index === subIndex) {
if (this.itemActiveTextColor) {
style.color = this.itemActiveTextColor
}
} else {
if (this.itemTextColor) {
style.color = this.itemTextColor
}
}
if (this.itemTextSize) {
style.fontSize = this.itemTextSize + 'rpx'
}
return style
}
},
itemSubTitleStyle() {
let style = {}
if (this.itemSubTextColor) {
style.color = this.itemSubTextColor
}
if (this.itemSubTextSize) {
style.fontSize = this.itemSubTextSize + 'rpx'
}
return {}
}
},
watch: {
list(val) {
this.initData(val, -1)
},
defaultValue(val) {
this.setDefaultValue(val)
},
receiveData(val) {
this.addSubData(val, this.currentTab)
},
},
data() {
return {
// 当前选中的子集
currentTab: 0,
// tabs栏scrollView滚动的位置
scrollViewId: 'select__0',
// 选项数组
selectedArr: []
}
},
created() {
this.setDefaultValue(this.defaultValue)
},
methods: {
// 初始化数据
initData(data, index) {
if (!data || data.length === 0) return
if (this.request) {
// 第一级数据
this.addSubData(data, index)
} else {
this.addSubData(this.getItemList(index, -1), index)
}
},
// 重置数据
reset() {
this.initData(this.list, -1)
},
// 滚动切换
switchTab(e) {
this.currentTab = e.detail.current
this.checkSelectPosition()
},
// 点击标题切换
clickNav(index) {
if (this.currentTab !== index) {
this.currentTab = index
}
},
// 列表数据发生改变
change(index, subIndex, subItem) {
let item = this.selectedArr[index]
if (item.index === subIndex) return
item.index = subIndex
item.text = subItem.text
item.subText = subItem.subText || ''
item.value = subItem.value
item.src = subItem.src || ''
this.$emit('change', {
index: index,
subIndex: subIndex,
...subItem
})
// 如果不是异步加载,则取出对应的数据
if (!this.request) {
let data = this.getItemList(index, subIndex)
this.addSubData(data, index)
}
},
// 设置默认的数据
setDefaultValue(val) {
let defaultValues = val || []
if (defaultValues.length > 0) {
this.selectedArr = this.getItemListWithValues(JSON.parse(JSON.stringify(this.list)), defaultValues)
if (!this.selectedArr) return
this.currentTab = this.selectedArr.length - 1
this.$nextTick(() => {
this.checkSelectPosition()
})
// defaultItemList.map((item) => {
// item.scrollViewId = `select__${item.index}`
// })
// this.selectedArr = defaultItemList
// this.currentTab = defaultItemList.length - 1
// this.$nextTick(() => {
// this.checkSelectPosition()
// })
} else {
this.initData(this.list, -1)
}
},
// 获取对应选项的item数据
getItemList(index, subIndex) {
let list = []
let arr = JSON.parse(JSON.stringify(this.list))
// 初始化数据
if (index === -1) {
list = this.removeChildren(arr)
} else {
// 判断第一项是否已经选择
let value = this.selectedArr[0].index
value = value === -1 ? subIndex : value
list = arr[value].children || []
if (index > 0) {
for (let i = 1; i < index + 1; i++) {
// 获取当前数据选中的序号
let val = index === i ? subIndex : this.selectedArr[i].index
list = list[val].children || []
if (list.length === 0) break
}
}
list = this.removeChildren(list)
}
return list
},
// 根据数组中的值获取对应的item数据
getItemListWithValues(data, values) {
const defaultValues = JSON.parse(JSON.stringify(values))
if (!defaultValues || defaultValues.length === 0) return
// 取出第一个值所对应的item
const itemIndex = data.findIndex((item) => {
return item.value === defaultValues[0]
})
if (itemIndex === -1) return
const item = data[itemIndex]
item.index = itemIndex
item.scrollViewId = `select__${itemIndex}`
item.list = this.removeChildren(JSON.parse(JSON.stringify(data)))
// 判断是否只有1个值
if (defaultValues.length === 1 || (!item.hasOwnProperty('children') || item.children.length === 0)) {
return this.removeChildren([item])
} else {
let selectItemList = []
const children = item.children
selectItemList.push(item)
// 移除已经获取的值
defaultValues.splice(0, 1)
const childrenValue = this.getItemListWithValues(children, defaultValues)
selectItemList = selectItemList.concat(childrenValue)
return this.removeChildren(selectItemList)
}
},
// 删除子元素
removeChildren(data) {
let list = data.map((item) => {
if (item.hasOwnProperty('children')) {
delete item['children']
}
return item
})
return list
},
// 新增子集数据时处理
addSubData(data, index) {
// 判断是否已经完成选择数据或者为初始化数据
if (!data || data.length === 0) {
if (index == -1) return
// 完成选择
let arr = this.selectedArr
// 如果当前选中项的序号比已选数据的长度小,则表示当前重新选择了数据
if (index < arr.length - 1) {
let newArr = arr.slice(0, index + 1)
this.selectedArr = newArr
}
let result = JSON.parse(JSON.stringify(this.selectedArr))
let lastItem = result[result.length - 1] || {}
let text = ''
result.map(item => {
text += item.text
delete item['list']
delete item['scrollViewId']
return item
})
this.$emit('complete', {
result: result,
value: lastItem.value,
text: text,
subText: lastItem.subText,
src: lastItem.src
})
} else {
// 重置数据
let item = [{
text: this.text,
subText: '',
value: '',
src: '',
index: -1,
scrollViewId: 'select__0',
list: data
}]
// 初始化数据
if (index === -1) {
this.selectedArr = item
} else {
// 拼接新旧数据并且判断是否为重新选择了数据(如果为重新选择了数据则重置之后的选项数据)
let retainArr = this.selectedArr.slice(0, index + 1)
this.selectedArr = retainArr.concat(item)
}
this.$nextTick(() => {
this.currentTab = this.selectedArr.length - 1
})
}
},
// 检查当前选中项,并将选项设置位置信息
checkSelectPosition() {
let item = this.selectedArr[this.currentTab]
item.scrollViewId = 'select__0'
this.$nextTick(() => {
setTimeout(() => {
// 设置当前数据滚动到的位置
let val = item.index < 2 ? 0 : Number(item.index - 2)
item.scrollViewId = `select__${val}`
}, 10)
})
// 设置选项滚动到所在的位置
if (this.currentTab > 1) {
this.scrollViewId = `select__${this.currentTab - 1}`
} else {
this.scrollViewId = `select__0`
}
}
}
}
</script>
<style lang="scss" scoped>
.tn-cascade-selection {
width: 100%;
}
.selection {
&__scroll-view {
background-color: #FFFFFF;
}
&__header {
width: 100%;
display: flex;
align-items: center;
position: relative;
&__item {
max-width: 240rpx;
padding: 15rpx 30rpx;
flex-shrink: 0;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
position: relative;
}
&__line {
width: 60rpx;
height: 6rpx;
border-radius: 4rpx;
position: absolute;
bottom: 0;
right: 0;
left: 50%;
transform: translateX(-50%);
}
}
&__list {
background-color: #FFFFFF;
&__item {
&--first {
width: 100%;
height: 20rpx;
}
&__cell {
width: 100%;
display: flex;
align-items: center;
padding: 20rpx 30rpx;
}
&__icon {
margin-right: 12rpx;
font-size: 24rpx;
}
&__image {
width: 40rpx;
height: 40rpx;
margin-right: 12rpx;
flex-shrink: 0;
}
&__title {
word-break: break-all;
color: #333333;
font-size: 28rpx;
&--sub {
margin-left: 20rpx;
word-break: break-all;
color: $tn-font-sub-color;
font-size: 24rpx;
}
}
}
}
}
</style>
+332
View File
@@ -0,0 +1,332 @@
var cropper = {
// 画布x轴起点
cutX: 0,
// 画布y轴起点
cutY: 0,
// 触摸点信息(手指与图片中心点的相对位置)
touchRelactive: [{
x: 0,
y: 0
}],
// 双指触摸时斜边的长度
hypotenuseLength:0,
// 是否结束触摸
touchEndFlag: false,
// 画布宽高
canvasWidth: 0,
canvasHeight: 0,
// 图片宽高
imgWidth: 0,
imgHeight: 0,
// 图片缩放比例
scale: 1,
// 图片旋转角度
angle: 0,
// 图片上边距
imgTop: 0,
// 图片左边距
imgLeft: 0,
// 窗口宽高
windowWidth: 0,
windowHeight: 0,
init: true
}
function bool(str) {
return str === 'true' || str === true
}
function propChange(prop, oldProp, ownerInstance, instance) {
if (prop && prop !== 'null') {
var params = prop.split(',')
var type = +params[0]
var dataset = instance.getDataset()
if (cropper.init || type == 4) {
cropper.canvasWidth = +dataset.width
cropper.canvasHeight = +dataset.height
cropper.imgTop = +dataset.windowheight / 2
cropper.imgLeft = +dataset.windowwidth / 2
cropper.imgWidth = +dataset.imgwidth
cropper.imgHeight = +dataset.imgheight
cropper.windowHeight = +dataset.windowheight
cropper.windowWidth = +dataset.windowwidth
cropper.init = false
} else if (type == 2 || type == 3) {
cropper.imgWidth = +dataset.imgwidth
cropper.imgHeight = +dataset.imgheight
}
cropper.angle = +dataset.angle
if (type == 3) {
imgTransform(ownerInstance)
}
switch(type) {
case 1:
setCutCenter(ownerInstance)
// // 设置裁剪框大小
computeCutSize(ownerInstance)
// // 检查裁剪框是否在范围内
cutDetectionPosition(ownerInstance)
break
case 2:
setCutCenter(ownerInstance)
break
case 3:
imgMarginDetectionScale(ownerInstance)
break
case 4:
imageReset(ownerInstance)
break
case 5:
setCutCenter(ownerInstance)
break
default:
break
}
}
}
function touchStart(event, ownerInstance) {
var touch = event.touches || event.changedTouches
cropper.touchEndFlag = false
if (touch.length === 1) {
cropper.touchRelactive[0] = {
x: touch[0].pageX - cropper.imgLeft,
y: touch[0].pageY - cropper.imgTop
}
} else {
var width = Math.abs(touch[0].pageX - touch[1].pageX)
var height = Math.abs(touch[0].pageY - touch[1].pageY)
cropper.touchRelactive = [{
x: touch[0].pageX - cropper.imgLeft,
y: touch[0].pageY - cropper.imgTop
},{
x: touch[1].pageX - cropper.imgLeft,
y: touch[1].pageY - cropper.imgTop
}]
cropper.hypotenuseLength = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2))
}
}
function touchMove(event, ownerInstance) {
var touch = event.touches || event.changedTouches
if (cropper.touchEndFlag) return
moveDuring(ownerInstance)
if (event.touches.length === 1) {
var left = touch[0].pageX - cropper.touchRelactive[0].x,
top = touch[0].pageY - cropper.touchRelactive[0].y;
cropper.imgLeft = left
cropper.imgTop = top
imgTransform(ownerInstance)
imgMarginDetectionPosition(ownerInstance)
} else {
var dataset = event.instance.getDataset()
var minScale = +dataset.minscale
var maxScale = +dataset.maxscale
var width = Math.abs(touch[0].pageX - touch[1].pageX),
height = Math.abs(touch[0].pageY - touch[1].pageY),
hypotenuse = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2)),
scale = cropper.scale * (hypotenuse / cropper.hypotenuseLength),
current_deg = 0;
scale = scale <= minScale ? minScale : scale
scale = scale >= maxScale ? maxScale : scale
cropper.scale = scale
imgMarginDetectionScale(ownerInstance, true)
var touchRelative = [{
x: touch[0].pageX - cropper.imgLeft,
y: touch[0].pageY - cropper.imgTop
}, {
x: touch[1].pageX - cropper.imgLeft,
y: touch[1].pageY - cropper.imgTop
}]
cropper.touchRelactive = touchRelative
cropper.hypotenuseLength = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2))
// 更新视图
cropper.angle = cropper.angle + current_deg
imgTransform(ownerInstance)
}
}
function touchEnd(event, ownerInstance) {
cropper.touchEndFlag = true
moveStop(ownerInstance)
updateData(ownerInstance)
}
function moveDuring(ownerInstance) {
if (!ownerInstance) return
ownerInstance.callMethod('moveDuring')
}
function moveStop(ownerInstance) {
if (!ownerInstance) return
ownerInstance.callMethod('moveStop')
}
function setCutCenter(ownerInstance) {
var cutX = (cropper.windowWidth - cropper.canvasWidth) * 0.5
var cutY = (cropper.windowHeight - cropper.canvasHeight) * 0.5
cropper.imgTop = cropper.imgTop - cropper.cutY + cutY
cropper.cutY = cutY
cropper.imgLeft = cropper.imgLeft - cropper.cutX + cutX
cropper.cutX = cutX
cutDetectionPosition(ownerInstance)
imgTransform(ownerInstance)
updateData(ownerInstance)
}
// 检测剪裁框位置是否在允许的范围内(屏幕内)
function cutDetectionPosition(ownerInstance) {
var windowHeight = cropper.windowHeight,
windowWidth = cropper.windowWidth;
// 检测上边距是否在范围内
var cutDetectionPositionTop = function() {
if (cropper.cutY < 0) {
cropper.cutY = 0
}
if (cropper.cutY > windowHeight - cropper.canvasHeight) {
cropper.cutY = windowHeight - cropper.canvasHeight
}
}
// 检测左边距是否在范围内
var cutDetectionPositionLeft = function() {
if (cropper.cutX < 0) {
cropper.cutX = 0
}
if (cropper.cutX > windowWidth - cropper.canvasWidth) {
cropper.cutX = windowWidth - cropper.canvasWidth
}
}
// 裁剪框坐标处理(如果只写一个参数则另一个默认为0,都不写默认为居中)
if (cropper.cutX === null && cropper.cutY === null) {
var cutX = (windowWidth - cropper.canvasWidth) * 0.5,
cutY = (windowHeight - cropper.canvasHeight) * 0.5;
cropper.cutX = cutX
cropper.cutY = cutY
} else if (cropper.cutX !== null && cropper.cutX !== null) {
cutDetectionPositionTop()
cutDetectionPositionLeft()
} else if (cropper.cutX !== null && cropper.cutY === null) {
cutDetectionPositionLeft()
cropper.cutY = (windowHeight - cropper.canvasHeight) / 2
} else if (cropper.cutX === null && cropper.cutY !== null) {
cutDetectionPositionTop()
cropper.cutX = (windowWidth - cropper.canvasWidth) / 2
}
}
// 图片边缘检测-缩放
function imgMarginDetectionScale(ownerInstance, delay) {
var scale = cropper.scale,
imgWidth = cropper.imgWidth,
imgHeight = cropper.imgHeight;
if ((cropper.angle / 90) % 2) {
imgWidth = cropper.imgHeight
imgHeight = cropper.imgWidth
}
if (imgWidth * scale < cropper.canvasWidth) {
scale = cropper.canvasWidth / imgWidth
}
if (imgHeight * scale < cropper.canvasHeight) {
scale = Math.max(scale, cropper.canvasHeight / imgHeight)
}
imgMarginDetectionPosition(ownerInstance, scale, delay)
}
// 图片边缘检测-位置
function imgMarginDetectionPosition(ownerInstance, scale, delay) {
var left = cropper.imgLeft,
top = cropper.imgTop,
imgWidth = cropper.imgWidth,
imgHeight = cropper.imgHeight;
scale = scale || cropper.scale
if ((cropper.angle / 90) % 2) {
imgWidth = cropper.imgHeight
imgHeight = cropper.imgWidth
}
left = cropper.cutX + (imgWidth * scale) / 2 >= left ? left : cropper.cutX + (imgWidth * scale) / 2
left = cropper.cutX + cropper.canvasWidth - (imgWidth * scale) / 2 <= left ? left : cropper.cutX + cropper.canvasWidth - (imgWidth * scale) / 2
top = cropper.cutY + (imgHeight * scale) / 2 >= top ? top : cropper.cutY + (imgHeight * scale) / 2
top = cropper.cutY + cropper.canvasHeight - (imgHeight * scale) / 2 <= top ? top : cropper.cutY + cropper.canvasHeight - (imgHeight * scale) / 2
cropper.imgLeft = left
cropper.imgTop = top
cropper.scale = scale
if (!delay || delay === 'null') {
imgTransform(ownerInstance)
}
}
// 改变截取值大小
function computeCutSize(ownerInstance) {
if (cropper.canvasWidth > cropper.windowWidth) {
cropper.canvasWidth = cropper.windowWidth
} else if (cropper.canvasWidth + cropper.cutX > cropper.windowWidth) {
cropper.cutX = cropper.windowWidth - cropper.cutX
}
if (cropper.canvasHeight > cropper.windowHeight) {
cropper.canvasHeight = cropper.windowHeight
} else if (cropper.canvasHeight + cropper.cutY > cropper.windowHeight) {
cropper.cutY = cropper.windowHeight - cropper.cutY
}
}
// 图片动画
function imgTransform(ownerInstance) {
try {
var image = ownerInstance.selectComponent('.tn-cropper__image')
if (!image) return
var x = cropper.imgLeft - cropper.imgWidth / 2,
y = cropper.imgTop - cropper.imgHeight / 2;
image.setStyle({
'transform': 'translate3d('+ x + 'px,' + y + 'px,0) scale(' + cropper.scale +') rotate(' + cropper.angle + 'deg)'
})
} catch(e) {
}
}
// 图片重置
function imageReset(ownerInstance) {
cropper.scale = 1
cropper.angle = 0
imgTransform(ownerInstance)
}
// 高度变化
function canvasHeight(ownerInstance) {
if (!ownerInstance) return
computeCutSize(ownerInstance)
}
// 宽度变化
function canvasWidth(ownerInstance) {
if (!ownerInstance) return
computeCutSize(ownerInstance)
}
// 更新数据
function updateData(ownerInstance) {
if (!ownerInstance) return
ownerInstance.callMethod('change', {
cutX: cropper.cutX,
cutY: cropper.cutY,
imgWidth: cropper.imgWidth,
imgHeight: cropper.imgHeight,
scale: cropper.scale,
angle: cropper.angle,
imgTop: cropper.imgTop,
imgLeft: cropper.imgLeft
})
}
module.exports = {
touchStart: touchStart,
touchMove: touchMove,
touchEnd: touchEnd,
propChange: propChange
}
@@ -0,0 +1,574 @@
<template>
<view class="tn-cropper-class tn-cropper" @touchmove.stop.prevent="stop">
<image
v-if="imageUrl"
:src="imageUrl"
class="tn-cropper__image"
:style="{
width: (imgWidth ? imgWidth : width) + 'px',
height: (imgHeight ? imgHeight : height) + 'px',
transitionDuration: (animation ? 0.3 : 0) + 's'
}"
mode="widthFix"
:data-minScale="minScale"
:data-maxScale="maxScale"
@load="imageLoad"
@error="imageLoad"
@touchstart="wxs.touchStart"
@touchmove="wxs.touchMove"
@touchend="wxs.touchEnd"
></image>
<view
class="tn-cropper__wrapper"
:style="{
width: width + 'px',
height: height + 'px',
borderRadius: isRound ? '50%' : '0'
}"
>
<view
class="tn-cropper__border"
:style="{
border: borderStyle,
borderRadius: isRound ? '50%' : '0',
}"
:prop="prop"
:change:prop="wxs.propChange"
:data-width="width"
:data-height="height"
:data-windowHeight="systemInfo.windowHeight || 600"
:data-windowWidth="systemInfo.windowWidth || 400"
:data-imgTop="imgTop"
:data-imgWidth="imgWidth"
:data-imgHeight="imgHeight"
:data-angle="angle"
></view>
</view>
<canvas
class="tn-cropper__canvas"
:style="{
width: width * scaleRatio + 'px',
height: height * scaleRatio + 'px'
}"
:canvas-id="CANVAS_ID"
:id="CANVAS_ID"
:disable-scroll="true"
></canvas>
<view
v-if="!custom"
class="tn-cropper__tabbar"
>
<view class="tn-cropper__tabbar__btn tn-cropper__tabber__cancel" @tap.stop="back">取消</view>
<view class="tn-cropper__tabbar__rotate" :class="[`tn-icon-${rotateIcon}`]" @tap.stop="setAngle"></view>
<view class="tn-cropper__tabbar__btn tn-cropper__tabber__confirm" @tap.stop="getCutImage">完成</view>
</view>
</view>
</template>
<script src="./index.wxs" lang="wxs" module="wxs"></script>
<script>
export default {
name: 'tn-cropper',
props: {
// 图片路径
imageUrl: {
type: String,
default: ''
},
// 裁剪框高度 px
height: {
type: Number,
default: 280
},
// 裁剪框的宽度 px
width: {
type: Number,
default: 280
},
// 是否为圆形裁剪框
isRound: {
type: Boolean,
default: false
},
// 裁剪框边框样式
borderStyle: {
type: String,
default: '1rpx solid #FFF'
},
// 生成的图片尺寸相对于裁剪框的比例
scaleRatio: {
type: Number,
default: 1
},
// 裁剪后的图片质量
// 取值范围为:(0, 1]
quality: {
type: Number,
default: 0.8
},
// 是否返回base64(H5默认为base64)
returnBase64: {
type: Boolean,
default: false
},
// 图片旋转角度
rotateAngle: {
type: Number,
default: 0
},
// 图片最小缩放比
minScale: {
type: Number,
default: 0.5
},
// 图片最大缩放比
maxScale: {
type: Number,
default: 2
},
// 自定义操作栏(设置后会隐藏默认的底部操作栏)
custom: {
type: Boolean,
default: false
},
// 是否在值发生改变的时候开始裁剪
// custom为true时生效
startCutting: {
type: Boolean,
default: false
},
// 裁剪时是否显示loading
loading: {
type: Boolean,
default: true
},
// 旋转图片图标
rotateIcon: {
type: String,
default: 'circle-arrow'
}
},
data() {
return {
// canvas容器id
CANVAS_ID: 'tn-cropper-canvas',
// 移动裁剪超时时间定时器
TIME_CUT_CENTER: null,
// canvas容器
ctx: null,
// 画布x轴起点
cutX: 0,
// 画布y轴起点
cutY: 0,
// 图片宽度
imgWidth: 0,
// 图片高度
imgHeight: 0,
// 图片底部位置
imgTop: 0,
// 图片左边位置
imgLeft: 0,
// 图片缩放比
scale: 1,
// 图片旋转角度
angle: 0,
// 开启动画过渡效果
animation: false,
// 动画定时器
animationTime: null,
// 系统信息
systemInfo: {},
// 传递的参数
prop: '',
// 标记是否发生改变
sizeChange: 0,
angleChange: 0,
resetChange: 0,
centerChange: 0
}
},
watch: {
imageUrl(val) {
this.imageReset()
this.showLoading()
uni.getImageInfo({
src: val,
success: (res) => {
// 计算图片尺寸
this.imgComputeSize(res.width, res.height)
this.angleChange++
this.prop = `3,${this.angleChange}`
},
fail: (err) => {
console.log(err);
this.imgComputeSize()
this.angleChange++
this.prop = `3,${this.angleChange}`
}
})
},
isRound(val) {
if (val) {
this.$nextTick(() => {
this.imageReset()
})
}
},
rotateAngle(val) {
this.animation = true
this.angle = val
this.angleChanged(val)
},
animation(val) {
clearTimeout(this.animationTime)
if (val) {
this.animationTime = setTimeout(() => {
this.animation = false
}, 200)
}
},
startCutting(val) {
if (this.custom && val) {
this.getCutImage()
}
}
},
mounted() {
this.systemInfo = uni.getSystemInfoSync()
this.imgTop = this.systemInfo.windowHeight / 2
this.imgLeft = this.systemInfo.windowWidth / 2
this.ctx = uni.createCanvasContext(this.CANVAS_ID, this)
// 初始化
this.$nextTick(() => {
this.prop = '1,1'
})
setTimeout(() => {
this.$emit('ready', {})
}, 200)
},
methods: {
// 将网络图片转换为本地图片【同步执行】
async getLocalImage(url) {
return await new Promise((resolve, reject) => {
uni.downloadFile({
url: url,
success: (res) => {
resolve(res.tempFilePath)
},
fail: (err) => {
reject(false)
}
})
})
},
// 返回裁剪后的图片信息
getCutImage() {
if (!this.imageUrl) {
uni.showToast({
title: '请选择图片',
icon: 'none'
})
return
}
this.loading && this.showLoading()
const draw = async () => {
// 图片实际大小
let imgWidth = this.imgWidth * this.scale * this.scaleRatio
let imgHeight = this.imgHeight * this.scale * this.scaleRatio
// canvas和图片的相对距离
let xpos = this.imgLeft - this.cutX
let ypos = this.imgTop - this.cutY
let imgUrl = this.imageUrl
// #ifdef APP-PLUS || MP-WEIXIN
if (~this.imageUrl.indexOf('https:')) {
imgUrl = await this.getLocalImage(this.imageUrl)
}
// #endif
// 旋转画布
this.ctx.translate(xpos * this.scaleRatio, ypos * this.scaleRatio)
// 如果时圆形则截取圆形
if (this.isRound) {
const r = this.width > this.height ? Math.floor(this.height / 2) : Math.floor(this.width / 2)
let translateX = Math.floor(this.width / 2)
let translateY = Math.floor(this.height / 2)
this.ctx.beginPath()
this.ctx.arc(translateX - (xpos * this.scaleRatio), translateY - (ypos * this.scaleRatio), r, 0, (360 * Math.PI) / 180)
this.ctx.closePath()
this.ctx.stroke()
this.ctx.clip()
}
this.ctx.rotate((this.angle * Math.PI) / 180)
this.ctx.drawImage(imgUrl, -imgWidth / 2, -imgHeight / 2, imgWidth, imgHeight)
// 清空后再继续绘制
this.ctx.draw(false, () => {
let params = {
width: this.width * this.scaleRatio,
height: Math.round(this.height * this.scaleRatio),
destWidth: this.width * this.scaleRatio,
destHeight: Math.round(this.height) * this.scaleRatio,
fileType: 'png',
quality: this.quality
}
let data = {
url: '',
base64: '',
width: this.width * this.scaleRatio,
height: this.height * this.scaleRatio
}
// #ifdef MP-ALIPAY
if (this.returnBase64) {
this.ctx.toDataURL(params).then((urlData) => {
data.base64 = urlData
this.loading && uni.hideLoading()
this.$emit('cropper', data)
})
} else {
this.ctx.toTempFilePath({
...params,
success: (res) => {
data.url = res.apFilePath
this.loading && uni.hideLoading()
this.$emit('cropper', data)
}
})
}
// #endif
let base64Flag = this.returnBase64
// #ifndef MP-ALIPAY
// #ifdef MP-BAIDU || MP-TOUTIAO || H5
base64Flag = false
// #endif
if (base64Flag) {
uni.canvasGetImageData({
canvasId: this.CANVAS_ID,
x: 0,
y: 0,
width: this.width * this.scaleRatio,
height: Math.round(this.height * this.scaleRatio),
success: (res) => {
const arrayBuffer = new Uint8Array(res.data)
const base64 = uni.arrayBufferToBase64(arrayBuffer)
data.base64 = base64
this.loading && uni.hideLoading()
this.$emit('cropper', data)
}
}, this)
} else {
uni.canvasToTempFilePath({
...params,
canvasId: this.CANVAS_ID,
success: (res) => {
data.url = res.tempFilePath
// #ifdef H5
data.base64 = res.tempFilePath
// #endif
this.loading && uni.hideLoading()
this.$emit('cropper', data)
}
}, this)
}
// #endif
})
}
draw()
},
// 修改图片后触发的函数
change(e) {
this.cutX = e.cutX || 0
this.cutY = e.cutY || 0
this.imgWidth = e.imgWidth || this.imgWidth
this.imgHeight = e.imgHeight || this.imgHeight
this.scale = e.scale || 1
this.angle = e.angle || 0
this.imgTop = e.imgTop || 0
this.imgLeft = e.imgLeft || 0
},
// 重置图片
imageReset() {
this.scale = 1
this.angle = 0
let systemInfo = this.systemInfo.windowHeight ? this.systemInfo : uni.getSystemInfoSync()
this.imgTop = systemInfo.windowHeight / 2
this.imgLeft = systemInfo.windowWidth / 2
this.resetChange++
this.prop = `4,${this.resetChange}`
// 初始旋转角度
this.$emit('initAngle', {})
},
// 图片的生成的尺寸
imgComputeSize(width, height) {
// 默认按图片的最小边 = 对应的裁剪框尺寸
let imgWidth = width,
imgHeight = height;
if (imgWidth && imgHeight) {
if (imgWidth / imgHeight > this.width / this.height) {
imgHeight = this.height
imgWidth = (width / height) * imgHeight
} else {
imgWidth = this.width
imgHeight = (height / width) * imgWidth
}
} else {
let systemInfo = this.systemInfo.windowHeight ? this.systemInfo : uni.getSystemInfoSync()
imgWidth = systemInfo.windowWidth
imgHeight = 0
}
this.imgWidth = imgWidth
this.imgHeight = imgHeight
this.sizeChange++
this.prop = `2,${this.sizeChange}`
},
// 图片加载完毕
imageLoad(e) {
this.imageReset()
uni.hideLoading()
this.$emit('imageLoad', {})
},
// 移动结束
moveStop() {
clearTimeout(this.TIME_CUT_CENTER)
this.TIME_CUT_CENTER = setTimeout(() => {
this.centerChange++
this.prop = `5,${this.centerChange}`
}, 688)
},
// 移动中
moveDuring() {
clearTimeout(this.TIME_CUT_CENTER)
},
// 显示加载框
showLoading() {
uni.showLoading({
title: '请稍等......',
mask: true
})
},
// 停止
stop() {},
// 取消/返回
back() {
uni.navigateBack()
},
// 角度改变
angleChanged(val) {
this.moveStop()
if (val % 90) {
this.angle = Math.round(val / 90) * 90
}
this.angleChange++
this.prop = `3,${this.angleChange}`
},
// 设置角度
setAngle() {
this.animation = true
this.angle = this.angle + 90
this.angleChanged(this.angle)
}
}
}
</script>
<style lang="scss" scoped>
.tn-cropper {
width: 100vw;
height: 100vh;
background-color: rgba(0, 0, 0, 0.7);
position: fixed;
top: 0;
left: 0;
z-index: 1;
&__image {
width: 100%;
border-style: none;
position: absolute;
top: 0;
left: 0;
z-index: 2;
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
transform-origin: center;
}
&__canvas {
position: fixed;
z-index: 10;
left: -2000px;
top: -2000px;
pointer-events: none;
}
&__wrapper {
position: fixed;
z-index: 4;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
border: 3000px solid rgba(0, 0, 0, 0.55);
pointer-events: none;
box-sizing: content-box;
}
&__border {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
box-sizing: border-box;
pointer-events: none;
}
&__tabbar {
width: 100%;
height: 120rpx;
padding: 0 40rpx;
box-sizing: border-box;
position: fixed;
left: 0;
bottom: 0;
z-index: 99;
display: flex;
align-items: center;
justify-content: space-between;
color: #FFFFFF;
font-size: 32rpx;
&::after {
content: '';
position: absolute;
top: 0;
right: 0;
left: 0;
border-top: 1rpx solid rgba(255, 255, 255, 0.2);
-webkit-transform: scaleY(0.5) translateZ(0);
transform: scaleY(0.5) translateZ(0);
transform-origin: 0 100%;
}
&__btn {
height: 80rpx;
display: flex;
align-items: center;
}
&__rotate {
width: 44rpx;
height: 44rpx;
font-size: 40rpx;
text-align: center;
}
}
}
</style>
@@ -0,0 +1,288 @@
function setTimeout(instance, cb, time) {
if (time > 0) {
var s = getDate().getTime()
var fn = function () {
if (getDate().getTime() - s > time) {
cb && cb()
} else
instance.requestAnimationFrame(fn)
}
fn()
}
else
cb && cb()
}
// 判断触摸的移动方向
function decideSwiperDirection(startTouches, currentTouches, vertical) {
// 震动偏移容差
var toleranceShake = 150
// 移动容差
var toleranceTranslate = 10
if (!vertical) {
// 水平方向移动
if (Math.abs(currentTouches.y - startTouches.y) <= toleranceShake) {
// console.log(currentTouches.x, startTouches.x);
if (Math.abs(currentTouches.x - startTouches.x) > toleranceTranslate) {
if (currentTouches.x - startTouches.x > 0) {
return 'right'
} else if (currentTouches.x - startTouches.x < 0) {
return 'left'
}
}
}
} else {
// 垂直方向移动
if (Math.abs(currentTouches.x - startTouches.x) <= toleranceShake) {
// console.log(currentTouches.x, startTouches.x);
if (Math.abs(currentTouches.y - startTouches.y) > toleranceTranslate) {
if (currentTouches.y - startTouches.y > 0) {
return 'down'
} else if (currentTouches.y - startTouches.y < 0) {
return 'up'
}
}
}
}
return ''
}
// swiperItem参数数据更新
var itemDataObserver = function(newVal, oldVal, ownerInstance, instance) {
if (!newVal || newVal === 'undefined') return
var state = ownerInstance.getState()
state.itemData = newVal
}
// swiperIndex数据更新
var currentIndexObserver = function(newVal, oldVal, ownerInstance, instance) {
if ((!newVal && newVal != 0) || newVal === 'undefined') return
var state = ownerInstance.getState()
state.currentIndex = newVal
}
// containerData数据更新
var containerDataObserver = function(newVal, oldVal, ownerInstance, instance) {
if (!newVal || newVal === 'undefined') return
var state = ownerInstance.getState()
state.containerData = newVal
}
// 开始触摸
var touchStart = function(event, ownerInstance) {
console.log('touchStart');
var instance = event.instance
var dataset = instance.getDataset()
var state = ownerInstance.getState()
var itemData = state.itemData
var containerData = state.containerData
// 由于当前SwiperIndex初始为0,可能会导致swiperIndex数据没有更新
if (!state.currentIndex || state.currentIndex === 'undefined') {
state.currentIndex = 0
}
if (!containerData || containerData.circular === 'undefined') {
containerData.circular = false
}
state.containerData = containerData
// 如果当前切换动画还没执行结束,再次触摸会重新加载对应的swiperContainer的信息
// console.log(containerData.animationFinish);
if (!containerData.animationFinish) {
ownerInstance.callMethod('changeParentSwiperContainerStyleStatus',{
status: 'reload'
})
}
// 判断是否为为当前显示的SwiperItem
if (itemData.index != state.currentIndex) return
var touches = event.changedTouches[0]
if (!touches) return
// 标记滑动开始时间
state.touchStartTime = getDate().getTime()
// 记录当前滑动开始的x,y坐标
state.touchRelactive = {
x: touches.pageX,
y: touches.pageY
}
// 记录触摸id,用于处理多指的情况
state.touchId = touches.identifier
// 标记开始触摸
state.touching = true
ownerInstance.callMethod('updateTouchingStatus', {
status: true
})
}
// 正在移动
var touchMove = function(event, ownerInstance) {
console.log('touchMove');
var instance = event.instance
var dataset = instance.getDataset()
var state = ownerInstance.getState()
var itemData = state.itemData
var containerData = state.containerData
// 判断是否为为当前显示的SwiperItem
if (itemData.index != state.currentIndex) return
// 判断是否开始触摸
if (!state.touching) return
var touches = event.changedTouches[0]
if (!touches) return
// 判断是否为同一个触摸点
if (state.touchId != touches.identifier) return
var currentTouchRelactive = {
x: touches.pageX,
y: touches.pageY
}
// 计算相对位移比例
if (containerData.vertical) {
var touchDistance = currentTouchRelactive.y - state.touchRelactive.y
var itemHeight = itemData.itemHeight
var distanceRate = touchDistance / itemHeight
// console.log(currentTouchRelactive.y, touchDistance, itemHeight, distanceRate);
// 判断是否为衔接轮播,如果不是衔接轮播,如果当前为第一个swiperItem并且向下滑、当前为最后一个swiperItem并且向上滑时不进行操作
if (!containerData.circular &&
((state.currentIndex === 0 && touchDistance > 0) || (state.currentIndex === containerData.swiperItemLength - 1 && touchDistance < 0))
) {
return
}
// 如果超出了距离则不进行操作
if((Math.abs(touchDistance) > (itemData.itemTop + itemData.itemHeight))) {
ownerInstance.callMethod('updateParentSwiperContainerStyle', {
value: distanceRate < 0 ? -1 : 1
})
return
}
} else {
var touchDistance = currentTouchRelactive.x - state.touchRelactive.x
var itemWidth = itemData.itemWidth
var distanceRate = touchDistance / itemWidth
// console.log(currentTouchRelactive.x, touchDistance, itemWidth, distanceRate);
// 判断是否为衔接轮播,如果不是衔接轮播,如果当前为第一个swiperItem并且向右滑、当前为最后一个swiperItem并且向左滑时不进行操作
if (!containerData.circular &&
((state.currentIndex === 0 && touchDistance > 0) || (state.currentIndex === containerData.swiperItemLength - 1 && touchDistance < 0))
) {
return
}
// 如果超出了距离则不进行操作
if((Math.abs(touchDistance) > (itemData.itemLeft + itemData.itemWidth))) {
ownerInstance.callMethod('updateParentSwiperContainerStyle', {
value: distanceRate < 0 ? -1 : 1
})
return
}
}
ownerInstance.callMethod('updateParentSwiperContainerStyle', {
value: distanceRate
})
}
// 移动结束
var touchEnd = function(event, ownerInstance) {
console.log('touchEnd');
var instance = event.instance
var dataset = instance.getDataset()
var state = ownerInstance.getState()
var itemData = state.itemData
var containerData = state.containerData
// 判断是否为为当前显示的SwiperItem
if (itemData.index != state.currentIndex) return
// 判断是否开始触摸
if (!state.touching) return
var touches = event.changedTouches[0]
if (!touches) return
// 判断是否为同一个触摸点
if (state.touchId != touches.identifier) return
var currentTime = getDate().getTime()
var currentTouchRelactive = {
x: touches.pageX,
y: touches.pageY
}
if (containerData.vertical) {
// 判断触摸移动方向
var direction = decideSwiperDirection(state.touchRelactive, currentTouchRelactive, true)
// 判断是否为衔接轮播,如果不是衔接轮播,如果当前为第一个swiperItem并且向下滑、当前为最后一个swiperItem并且向上滑时不进行操作
if (containerData.circular ||
!((state.currentIndex === 0 && direction === 'down') || (state.currentIndex === containerData.swiperItemLength - 1 && direction === 'up'))
) {
// 判断触摸的时间和移动的距离是否超过了当前itemHeight的一半,如果是则执行切换操作
// console.log(currentTime - state.touchStartTime, Math.abs(currentTouchRelactive.y - state.touchRelactive.y));
if ((currentTime - state.touchStartTime) > 200 && Math.abs(currentTouchRelactive.y - state.touchRelactive.y) < itemData.itemHeight / 2) {
ownerInstance.callMethod('changeParentSwiperContainerStyleStatus',{
status: 'reset'
})
} else {
// console.log(direction, state.touchRelactive.y, currentTouchRelactive.y);
ownerInstance.callMethod('updateParentSwiperContainerStyleWithDirection', {
direction: direction
})
}
}
} else {
// 判断触摸移动方向
var direction = decideSwiperDirection(state.touchRelactive, currentTouchRelactive, false)
// 判断是否为衔接轮播,如果不是衔接轮播,如果当前为第一个swiperItem并且向右滑、当前为最后一个swiperItem并且向左滑时不进行操作
if (containerData.circular ||
!((state.currentIndex === 0 && direction === 'right') || (state.currentIndex === containerData.swiperItemLength - 1 && direction === 'left'))
) {
// 判断触摸的时间和移动的距离是否超过了当前itemWidth的一半,如果是则执行切换操作
// console.log(currentTime - state.touchStartTime, Math.abs(currentTouchRelactive.x - state.touchRelactive.x));
if ((currentTime - state.touchStartTime) > 200 && Math.abs(currentTouchRelactive.x - state.touchRelactive.x) < itemData.itemWidth / 2) {
ownerInstance.callMethod('changeParentSwiperContainerStyleStatus',{
status: 'reset'
})
} else {
// console.log(direction, state.touchRelactive.x, currentTouchRelactive.x);
ownerInstance.callMethod('updateParentSwiperContainerStyleWithDirection', {
direction: direction
})
}
}
}
// 清除标记
state.touchId = null
state.touchRelactive = null
state.touchStartTime = 0
// 标记停止触摸
state.touching = true
ownerInstance.callMethod('updateTouchingStatus', {
status: false
})
}
module.exports = {
itemDataObserver: itemDataObserver,
currentIndexObserver: currentIndexObserver,
containerDataObserver: containerDataObserver,
touchStart: touchStart,
touchMove: touchMove,
touchEnd: touchEnd
}
@@ -0,0 +1,277 @@
<template>
<!-- #ifdef MP-WEIXIN -->
<view
class="tn-c-swiper-item"
:style="[swiperStyle]"
:itemData="itemData"
:currentIndex="currentIndex"
:containerData="containerData"
:change:itemData="wxs.itemDataObserver"
:change:currentIndex="wxs.currentIndexObserver"
:change:containerData="wxs.containerDataObserver"
@touchstart="wxs.touchStart"
:catch:touchmove="touching?wxs.touchMove:''"
:catch:touchend="touching?wxs.touchEnd:''"
>
<view class="item__container tn-c-swiper-item__container" :style="[containerStyle]">
<slot></slot>
</view>
</view>
<!-- #endif -->
<!-- #ifndef MP-WEIXIN -->
<view
class="tn-c-swiper-item"
:style="[swiperStyle]"
:itemData="itemData"
:currentIndex="currentIndex"
:containerData="containerData"
:change:itemData="wxs.itemDataObserver"
:change:currentIndex="wxs.currentIndexObserver"
:change:containerData="wxs.containerDataObserver"
@touchstart="wxs.touchStart"
@touchmove="wxs.touchMove"
@touchend="wxs.touchEnd"
>
<view class="item__container tn-c-swiper-item__container" :style="[containerStyle]">
<slot></slot>
</view>
</view>
<!-- #endif -->
</template>
<script src="./index.wxs" lang="wxs" module="wxs"></script>
<script>
export default {
name: 'tn-custom-swiper-item',
props: {
},
computed: {
// swiperItem
itemData() {
return {
index: this.index,
itemWidth: this.itemWidth,
itemHeight: this.itemHeight,
itemTop: this.itemTop,
itemLeft: this.itemLeft
}
},
currentIndex() {
return this.parentData.currentIndex
},
containerData() {
return {
duration: this.parentData.duration,
animationFinish: this.parentData.swiperContainerAnimationFinish,
circular: this.parentData.circular,
swiperItemLength: this.swiperItemLength,
vertical: this.parentData.vertical
}
},
swiperStyle() {
let style = {}
style.transform = `translate3d(${this.translateX}%, ${this.translateY}%, 0px)`
return style
},
containerStyle() {
let style = {}
if (this.parentData.customSwiperStyle && Object.keys(this.parentData.customSwiperStyle).length > 0) {
style = this.parentData.customSwiperStyle
}
if ((this.currentIndex === 0 && this.index === this.swiperItemLength - 1) || (this.index === this.currentIndex - 1) &&
(this.parentData.prevSwiperStyle && Object.keys(this.parentData.prevSwiperStyle).length > 0)
) {
// swiperItem
const copyStyle = JSON.parse(JSON.stringify(style))
style = Object.assign(copyStyle, this.parentData.prevSwiperStyle)
}
if ((this.currentIndex === this.swiperItemLength - 1 && this.index === 0) || (this.index === this.currentIndex + 1) &&
(this.parentData.nextSwiperStyle && Object.keys(this.parentData.nextSwiperStyle).length > 0)
) {
// swiperItem
const copyStyle = JSON.parse(JSON.stringify(style))
style = Object.assign(copyStyle, this.parentData.nextSwiperStyle)
}
return style
}
},
data() {
return {
//
parentData: {
duration: 500,
currentIndex: 0,
swiperContainerAnimationFinish: false,
circular: false,
vertical: false,
prevSwiperStyle: {},
customSwiperStyle: {},
nextSwiperStyle: {}
},
//
touching: true,
// swiperItem
translateX: 0,
translateY: 0,
// swiperItem
itemWidth: 0,
itemHeight: 0,
// swiperItem
itemTop: 0,
itemLeft: 0,
// swiperItem prev current next
status: 'current',
// swiperItemindex
index: 0,
// swiperItem
swiperItemLength: 0
}
},
created() {
this.parent = false
this.updateParentData()
// childrenswiperItem
this.index = this.parent.children.length
this.parent && this.parent.children.push(this)
},
mounted() {
this.$nextTick(() => {
this.initSwiperItem()
})
},
methods: {
// swiperItem
initSwiperItem() {
this.getSwiperItemRect(() => {
this.parent.updateAllSwiperItemStyle()
this.parentData.swiperContainerAnimationFinish = true
})
},
// swiperItem
async getSwiperItemRect(callback) {
const swiperItemRes = await this._tGetRect('.tn-c-swiper-item')
if (!swiperItemRes.height || !swiperItemRes.width) {
setTimeout(() => {
this.getSwiperItemRect()
}, 30)
return
}
this.itemWidth = swiperItemRes.width
this.itemHeight = swiperItemRes.height
this.itemTop = swiperItemRes.top
this.itemLeft = swiperItemRes.left
callback && callback()
},
// swiperItem
updateSwiperItemStyle(swiperItemLength, currentIndex = undefined) {
currentIndex = currentIndex != undefined ? currentIndex : this.parentData.currentIndex
this.swiperItemLength = swiperItemLength
// swiperItem
// swiperItemswiperItem
if (currentIndex === 0 && this.index === swiperItemLength - 1) {
if (this.parentData.vertical) {
this.translateX = 0
this.translateY = -100
} else {
this.translateX = -100
this.translateY = 0
}
}
// swiperItemswiperItem
else if (currentIndex === swiperItemLength - 1 && this.index === 0) {
if (this.parentData.vertical) {
this.translateX = 0
this.translateY = swiperItemLength * 100
} else {
this.translateX = swiperItemLength * 100
this.translateY = 0
}
}
//
else {
if (this.parentData.vertical) {
this.translateX = 0
this.translateY = this.index * 100
} else {
this.translateX = this.index * 100
this.translateY = 0
}
}
},
//
updateParentSwiperContainerStyle(e) {
this.parent.updateSwiperContainerStyleWithValue(e.value)
},
//
updateParentSwiperContainerStyleWithDirection(e) {
this.parent.updateSwiperContainerStyleWithDirection(e.direction)
},
//
changeParentSwiperContainerStyleStatus(e) {
// reset -> reload ->
this.parent.updateSwiperContainerStyleWithDirection(e.status)
},
//
updateParentData() {
this.getParentData('tn-custom-swiper')
},
//
updateTouchingStatus(e) {
this.touching = e.status
if (e.status) {
this.parent.stopAutoPlay()
} else {
this.parent.startAutoPlay()
}
},
//
extractCustomStyle(customStyle) {
let data = {
transform: {},
style: {}
}
if (!customStyle) return data
// transform
const allowTransformProps = ['scale','scaleX','scaleY','scaleZ','rotate','rotateX','rotateY','rotateZ']
for (let prop in customStyle) {
if (prop.startsWith('transformProp')) {
// transform
let transformProp = prop.substring('transformProp'.length)
const index = allowTransformProps.findIndex((item) => {
return item.toLowerCase() === transformProp.toLowerCase()
})
if (index !== -1) {
transformProp = allowTransformProps[index]
data.transform[transformProp] = customStyle[prop]
}
} else {
//
data.style[prop] = customStyle[prop]
}
}
return data
}
}
}
</script>
<style lang="scss" scoped>
.tn-c-swiper-item {
width: 100%;
height: 100%;
position: absolute;
display: block;
will-change: transform;
cursor: none;
transform: translate3d(0px, 0px, 0px);
.item__container {
width: 100%;
height: 100%;
display: block;
position: absolute;
}
}
</style>
@@ -0,0 +1,535 @@
<template>
<view
class="tn-c-swiper-class tn-c-swiper"
>
<!-- 轮播item容器-->
<view class="tn-swiper__container" :style="[swiperContainerStyle]" :animation="containerAnimation">
<slot></slot>
</view>
<!-- 轮播指示器-->
<view v-if="indicator" class="tn-swiper__indicator" :class="[`tn-swiper__indicator--${vertical ? 'vertical' : 'horizontal'}`]" :style="[indicatorStyle]">
<!-- 方形 -->
<block v-if="indicatorType === 'rect'">
<view
v-for="(item, index) in children.length"
:key="index"
class="tn-swiper__indicator__rect"
:class="[
`tn-swiper__indicator__rect--${vertical ? 'vertical' : 'horizontal'}`,
currentIndex === index ? `tn-swiper__indicator__rect--active tn-swiper__indicator__rect--active--${vertical ? 'vertical' : 'horizontal'}` : ''
]"
:style="[indicatorPointStyle(index)]"
></view>
</block>
<!-- -->
<block v-if="indicatorType === 'dot'">
<view
v-for="(item, index) in children.length"
:key="index"
class="tn-swiper__indicator__dot"
:class="[
`tn-swiper__indicator__dot--${vertical ? 'vertical' : 'horizontal'}`,
currentIndex === index ? `tn-swiper__indicator__dot--active tn-swiper__indicator__dot--active--${vertical ? 'vertical' : 'horizontal'}` : ''
]"
:style="[indicatorPointStyle(index)]"
></view>
</block>
<!-- 圆角方形 -->
<block v-if="indicatorType === 'round'">
<view
v-for="(item, index) in children.length"
:key="index"
class="tn-swiper__indicator__round"
:class="[
`tn-swiper__indicator__round--${vertical ? 'vertical' : 'horizontal'}`,
currentIndex === index ? `tn-swiper__indicator__round--active tn-swiper__indicator__round--active--${vertical ? 'vertical' : 'horizontal'}` : ''
]"
:style="[indicatorPointStyle(index)]"
></view>
</block>
<!-- 序号 -->
<block v-if="indicatorType === 'number' && !vertical">
<view class="tn-swiper__indicator__number">{{ currentIndex + 1 }}/{{ children.length }}</view>
</block>
</view>
</view>
</template>
<script>
export default {
name: 'tn-custom-swiper',
props: {
//
current: {
type: Number,
default: 0
},
//
autoplay: {
type: Boolean,
default: false
},
//
interval: {
type: Number,
default: 5000
},
//
duration: {
type: Number,
default: 500
},
//
circular: {
type: Boolean,
default: false
},
//
vertical: {
type: Boolean,
default: false
},
//
indicator: {
type: Boolean,
default: false
},
//
// rect -> round -> dot -> number ->
indicatorType: {
type: String,
default: 'dot'
},
//
// topLeft \ topCenter \ topRight \ bottomLeft \ bottomCenter \ bottomRight
indicatorPosition: {
type: String,
default: 'bottomCenter'
},
//
indicatorActiveColor: {
type: String,
default: ''
},
//
indicatorInactiveColor: {
type: String,
default: ''
},
//
prevSwiperStyle: {
type: Object,
default() {
return {}
}
},
//
customSwiperStyle: {
type: Object,
default() {
return {}
}
},
//
nextSwiperStyle: {
type: Object,
default() {
return {}
}
}
},
computed: {
parentData() {
return [
this.duration,
this.currentIndex,
this.swiperContainerAnimationFinish,
this.circular,
this.vertical,
this.prevSwiperStyle,
this.customSwiperStyle,
this.nextSwiperStyle
]
},
indicatorStyle() {
let style = {}
if (this.vertical) {
if (this.indicatorPosition === 'topLeft' || this.indicatorPosition === 'bottomLeft') style.justifyContent = 'flex-start'
if (this.indicatorPosition === 'topCenter' || this.indicatorPosition === 'bottomCenter') style.justifyContent = 'center'
if (this.indicatorPosition === 'topRight' || this.indicatorPosition === 'bottomRight') style.justifyContent = 'flex-end'
if (['topLeft','topCenter','topRight'].indexOf(this.indicatorPosition) >= 0) {
if (this.vertical) {
style.right = '12rpx'
style.left = 'auto'
} else {
style.top = '12rpx'
style.bottom = 'auto'
}
} else {
if (this.vertical) {
style.right = 'auto'
style.left = '12rpx'
} else {
style.top = 'auto'
style.bottom = '12rpx'
}
}
} else {
if (this.indicatorPosition === 'topLeft' || this.indicatorPosition === 'bottomLeft') style.justifyContent = 'flex-start'
if (this.indicatorPosition === 'topCenter' || this.indicatorPosition === 'bottomCenter') style.justifyContent = 'center'
if (this.indicatorPosition === 'topRight' || this.indicatorPosition === 'bottomRight') style.justifyContent = 'flex-end'
if (['topLeft','topCenter','topRight'].indexOf(this.indicatorPosition) >= 0) {
style.top = '12rpx'
style.bottom = 'auto'
} else {
style.top = 'auto'
style.bottom = '12rpx'
}
}
return style
},
indicatorPointStyle() {
return (index) => {
let style = {}
if (index === this.currentIndex && this.indicatorActiveColor !== '') {
style.backgroundColor = this.indicatorActiveColor
} else if (this.indicatorInactiveColor !== '') {
style.backgroundColor = this.indicatorInactiveColor
}
return style
}
}
},
watch: {
parentData() {
if (this.children.length) {
this.children.forEach((item) => {
// updateParentData()
typeof(item.updateParentData) === 'function' && item.updateParentData()
})
}
},
current(nVal, oVal) {
if (this.currentIndex === nVal) return
this.currentIndex = nVal > this.children.length ? this.children.length - 1 : nVal
this.swiperContainerAnimationFinish = false
//
this.swiperContainerStyle.transitionDuration = `${this.duration + 90}ms`
this.updateSwiperContainerItem(oVal)
}
},
data() {
return {
//
clearAnimationTimer: null,
//
convergeTimer: null,
// Timer
autoPlayTimer: null,
//
currentIndex: this.current,
// swiperContainer
swiperContainerStyle: {
transform: 'translate3d(0px, 0px, 0px)',
transitionDuration: '0ms'
},
// swiperContainer
containerAnimation: {},
//
swiperContainerAnimationFinish: false
}
},
created() {
this.children = []
},
mounted() {
this.$nextTick(() => {
const index = this.currentIndex > this.children.length ? this.children.length - 1 : this.currentIndex
this.updateSwiperContainerStyle(index)
this.startAutoPlay()
})
},
methods: {
// swiperItem
updateAllSwiperItemStyle() {
this.children.forEach((item, index) => {
typeof(item.updateSwiperItemStyle) === 'function' && item.updateSwiperItemStyle(this.children.length)
})
},
// swiperIndexswiperItemContainer
updateSwiperContainerStyle(index) {
if (this.vertical) {
this.swiperContainerStyle.transform = `translate3d(0px, ${-index * 100}%, 0px)`
} else {
this.swiperContainerStyle.transform = `translate3d(${-index * 100}%, 0px, 0px)`
}
},
// swiperItemContainer
updateSwiperContainerStyleWithValue(value) {
if (this.vertical) {
this.swiperContainerStyle.transform = `translate3d(0px, ${(-this.currentIndex * 100) + value * 100}%, 0px)`
} else {
this.swiperContainerStyle.transform = `translate3d(${(-this.currentIndex * 100) + value * 100}%, 0px, 0px)`
}
},
// swiperItemContainer
updateSwiperContainerStyleWithDirection(direction) {
const oldCurrent = this.currentIndex
const childrenLength = this.children.length
const lastSwiperItemIndex = childrenLength - 1
this.swiperContainerAnimationFinish = false
// SwiperItem
if (direction === 'reset') {
//
this.swiperContainerStyle.transitionDuration = `${this.duration}ms`
this.updateSwiperContainerStyle(this.currentIndex)
this.clearAnimationTimer = setTimeout(() => {
this.clearSwiperContainerAnimation()
}, this.duration)
} else if (direction === 'reload') {
this.clearConvergeSwiperItemTimer()
this.clearSwiperContainerAnimation()
this.updateSwiperItemStyle(0)
this.updateSwiperItemStyle(lastSwiperItemIndex)
} else {
if (direction === 'left' || direction === 'up') {
if (oldCurrent === childrenLength - 1 && !this.circular) {
this.clearSwiperContainerAnimation()
this.clearConvergeSwiperItemTimer()
return
}
this.currentIndex = oldCurrent + 1 >= childrenLength ? 0 : oldCurrent + 1
} else if (direction === 'right' || direction === 'down') {
if (oldCurrent === 0 && !this.circular) {
this.clearSwiperContainerAnimation()
this.clearConvergeSwiperItemTimer()
return
}
this.currentIndex = oldCurrent - 1 < 0 ? childrenLength - 1 : oldCurrent - 1
}
//
this.swiperContainerStyle.transitionDuration = `${this.duration + 90}ms`
// this.updateSwiperItemContainerRect(this.currentIndex)
}
// console.log(direction, oldCurrent, this.currentIndex);
this.updateSwiperContainerItem(oldCurrent)
//
this.$emit('change', {
current: this.currentIndex
})
},
//
startAutoPlay() {
if (this.autoplay && !this.autoPlayTimer && this.circular) {
this.autoPlayTimer = setInterval(() => {
this.updateSwiperContainerStyleWithDirection('left')
}, this.interval)
}
},
//
stopAutoPlay() {
if (this.autoPlayTimer) {
clearInterval(this.autoPlayTimer)
this.autoPlayTimer = null
}
},
// swiperContainerswiperItem
updateSwiperContainerItem(oldCurrent) {
const childrenLength = this.children.length
const lastSwiperItemIndex = childrenLength - 1
// SwiperItem
// swiperItemContainer
if (oldCurrent === 0 && this.currentIndex === lastSwiperItemIndex) {
//
// this.swiperContainerStyle.transform = `translate3d(100%, 0px, 0px)`
this.updateSwiperContainerStyle(-1)
this.clearSwiperContainerAnimationTimer()
this.clearAnimationTimer = setTimeout(() => {
this.convergeSwiperItem()
}, this.duration)
} else if (oldCurrent === lastSwiperItemIndex && this.currentIndex === 0) {
//
// this.swiperContainerStyle.transform = `translate3d(${-childrenLength * 100}%, 0px, 0px)`
this.updateSwiperContainerStyle(childrenLength)
this.clearSwiperContainerAnimationTimer()
this.clearAnimationTimer = setTimeout(() => {
this.convergeSwiperItem()
}, this.duration)
} else {
this.updateSwiperContainerStyle(this.currentIndex)
this.updateSwiperItemStyle(0)
this.updateSwiperItemStyle(lastSwiperItemIndex)
this.clearAnimationTimer = setTimeout(() => {
this.clearSwiperContainerAnimation()
}, this.duration)
}
},
// swiperItem
updateSwiperItemStyle(index) {
const childrenLength = this.children.length
if (index < 0) index = 0
if (index > childrenLength - 1) index = childrenLength - 1
typeof(this.children[index].updateSwiperItemStyle) === 'function' && this.children[index].updateSwiperItemStyle(childrenLength, this.currentIndex)
},
// swiperItem
updateSwiperItemContainerRect(index) {
const childrenLength = this.children.length
if (index < 0) index = 0
if (index > childrenLength - 1) index = childrenLength - 1
typeof(this.children[index].getSwiperItemRect) === 'function' && this.children[index].getSwiperItemRect()
},
//
convergeSwiperItem() {
const lastSwiperItemIndex = this.children.length - 1
this.clearSwiperContainerAnimation()
this.clearConvergeSwiperItemTimer()
this.convergeTimer = setTimeout(() => {
this.updateSwiperItemStyle(0)
this.updateSwiperItemStyle(lastSwiperItemIndex)
this.updateSwiperContainerStyle(this.currentIndex)
this.clearConvergeSwiperItemTimer()
}, 30)
},
// /
clearSwiperContainerAnimation() {
this.swiperContainerStyle.transitionDuration = `0ms`
this.swiperContainerAnimationFinish = true
this.clearSwiperContainerAnimationTimer()
},
// /
clearConvergeSwiperItemTimer() {
if (this.convergeTimer) {
clearTimeout(this.convergeTimer)
this.convergeTimer = null
}
},
// /
clearSwiperContainerAnimationTimer() {
if (this.clearAnimationTimer) {
clearTimeout(this.clearAnimationTimer)
this.clearAnimationTimer = null
}
}
}
}
</script>
<style lang="scss" scoped>
.tn-c-swiper {
position: relative;
overflow: hidden;
width: 100%;
height: 100%;
.tn-swiper {
&__container {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
will-change: transform;
transition-property: all;
transition-timing-function: ease-out;
}
&__indicator {
position: absolute;
display: flex;
z-index: 1;
&--horizontal {
padding: 0 24rpx;
flex-direction: row;
width: 100%;
}
&--vertical {
padding: 24rpx 0;
flex-direction: column;
height: 100%;
}
&__rect {
background-color: rgba(0, 0, 0, 0.3);
transition: all 0.5s;
&--horizontal {
width: 26rpx;
height: 8rpx;
}
&--vertical {
width: 8rpx;
height: 26rpx;
}
&--active {
background-color: rgba(255, 255, 255, 0.8);
}
}
&__dot {
width: 14rpx;
height: 14rpx;
border-radius: 20rpx;
background-color: rgba(0, 0, 0, 0.3);
transition: all 0.5s;
&--horizontal {
margin: 0 6rpx;
}
&--vertical {
margin: 6rpx 0;
}
&--active {
background-color: rgba(255, 255, 255, 0.8);
}
}
&__round {
width: 14rpx;
height: 14rpx;
border-radius: 20rpx;
background-color: rgba(0, 0, 0, 0.3);
transition: all 0.5s;
&--horizontal {
margin: 0 6rpx;
}
&--vertical {
margin: 6rpx 0;
}
&--active {
background-color: rgba(255, 255, 255, 0.8);
&--horizontal {
width: 34rpx;
}
&--vertical {
height: 34rpx;
}
}
}
&__number {
padding: 6rpx 16rpx;
line-height: 1;
background-color: rgba(0, 0, 0, 0.3);
color: rgba(255, 255, 255, 0.8);
border-radius: 100rpx;
font-size: 26rpx;
}
}
}
}
</style>
+265
View File
@@ -0,0 +1,265 @@
// 判断是否出界
var isOutRange = function(x1, y1, x2, y2, x3, y3) {
return x1 < 0 || x1 >= y1 || x2 < 0 || x2 >= y2 || x3 < 0 || x3 >= y3
}
var edit = false
function bool(str) {
return str === 'true' || str === true
}
/**
* 排序核心
* @param {Object} startKey 开始时位置
* @param {Object} endKey 结束时位置
* @param {Object} instance wxs内的局部变量快照
*/
var sortCore = function(startKey, endKey, state) {
var basedata = state.basedata
var excludeFix = function(sortKey, type) {
// fixed 元素位置不会变化, 这里直接用 sortKey 获取,更加便捷
if (state.list[sortKey].fixed) {
var _sortKey = type ? --sortKey : ++sortKey
return excludeFix(sortKey, type)
}
return sortKey
}
// 先获取到 endKey 对应的 realKey, 防止下面排序过程中该 realKey 被修改
var endRealKey = -1
state.list.forEach(function(item) {
if (item.sortKey === endKey) endRealKey = item.realKey
})
return state.list.map(function(item) {
if (item.fixed) return item
var sortKey = item.sortKey
var realKey = item.realKey
if (startKey < endKey) {
// 正序拖动
if (sortKey > startKey && sortKey <= endKey) {
--realKey
sortKey = excludeFix(--sortKey, true)
} else if (sortKey === startKey) {
realKey = endRealKey
sortKey = endKey
}
} else if (startKey > endKey) {
// 倒序拖动
if (sortKey >= endKey && sortKey < startKey) {
++realKey
sortKey = excludeFix(++sortKey, false)
} else if (sortKey === startKey) {
realKey = endRealKey
sortKey = endKey
}
}
if (item.sortKey != sortKey) {
item.translateX = (sortKey % basedata.columns) * 100 + '%'
item.translateY = Math.floor(sortKey / basedata.columns) * 100 + '%'
item.sortKey = sortKey
item.realKey = realKey
}
return item
})
}
var triggerCustomEvent = function(list, type, instance) {
if (!instance) return
var _list = [],
listData = [];
list.forEach(function(item) {
_list[item.sortKey] = item
})
_list.forEach(function(item) {
listData.push(item.data)
})
// 编译到小程序 funcName作为参数传递导致事件不执行
switch(type) {
case 'change':
instance.callMethod('change', {data: listData})
break
case 'sortEnd':
instance.callMethod('sortEnd', {data: listData})
break
}
}
var listObserver = function(newVal, oldVal, ownerInstance, instance) {
var state = ownerInstance.getState()
state.itemsInstance = ownerInstance.selectAllComponents('.tn-drag__item')
state.list = newVal || []
state.list.forEach(function(item, index) {
var itemInstance = state.itemsInstance[index]
if (item && itemInstance) {
itemInstance.setStyle({
'transform': 'translate3d('+ item.translateX + ',' + item.translateY +', 0)'
})
if (item.fixed) itemInstance.addClass('tn-drag__fixed')
}
})
}
var baseDataObserver = function(newVal, oldVal, ownerInstance, instance) {
var state = ownerInstance.getState()
state.basedata = newVal
}
var longPress = function(event, ownerInstance) {
var instance = event.instance
var dataset = instance.getDataset()
var state = ownerInstance.getState()
edit = bool(dataset.edit)
if (!edit) return
if (!state.basedata || state.basedata === 'undefined') {
state.basedata = JSON.parse(dataset.basedata)
}
var basedata = state.basedata
var touches = event.changedTouches[0]
if (!touches) return
state.current = +dataset.index
// 初始项是固定项则返回
var item = state.list[state.current]
if (item && item.fixed) return
// 如果已经在 drag 中则返回, 防止多指触发 drag 动作, touchstart 事件中有效果
if (state.dragging) return
ownerInstance.callMethod("drag", {
dragging: true
})
// 计算X, Y轴初始位移,使item中心移动到点击处,单列的时候X轴初始不做位移
state.translateX = basedata.columns === 1 ? 0 : touches.pageX - (basedata.itemWidth / 2 + basedata.left)
state.translateY = touches.pageY - (basedata.itemHeight / 2 + basedata.top)
state.touchId = touches.identifier
instance.setStyle({
'transform': 'translate3d(' + state.translateX + 'px,' + state.translateY +'px, 0)'
})
state.itemsInstance.forEach(function(item, index) {
item.removeClass("tn-drag__transition").removeClass("tn-drag__current")
item.addClass(index === state.current ? "tn-drag__current" : "tn-drag__transition")
})
ownerInstance.callMethod("vibrate")
state.dragging = true
}
var touchStart = function(event, ownerInstance) {
var instance = event.instance
var dataset = instance.getDataset()
edit = bool(dataset.edit)
}
var touchMove = function(event, ownerInstance) {
var instance = event.instance
var dataset = instance.getDataset()
var state = ownerInstance.getState()
var basedata = state.basedata
if (!state.dragging || !edit) return
var touches = event.changedTouches[0]
if (!touches) return
// 如果不是同一个触发点则返回
if (state.touchId !== touches.identifier) return
// 计算X,Y轴位移, 单列时候X轴初始不做位移
var translateX = basedata.columns === 1 ? 0 : touches.pageX - (basedata.itemWidth / 2 + basedata.left)
var translateY = touches.pageY - (basedata.itemHeight / 2 + basedata.top)
// 到顶到低自动滑动
if (touches.clientY > basedata.windowHeight - basedata.itemHeight - basedata.realBottomSize) {
// 当前触摸点pageY + item高度 - (屏幕高度 - 底部固定区域高度)
ownerInstance.callMethod('pageScroll', {
scrollTop: touches.pageY + basedata.itemHeight - (basedata.windowHeight - basedata.realBottomSize)
})
} else if (touches.clientY < basedata.itemHeight + basedata.realTopSize) {
// 当前触摸点pageY - item高度 - 顶部固定区域高
ownerInstance.callMethod('pageScroll', {
scrollTop: touches.pageY - basedata.itemHeight - basedata.realTopSize
})
}
// 设置当前激活元素的偏移量
instance.setStyle({
'transform': 'translate3d('+ translateX + 'px,' + translateY + 'px, 0)'
})
var startKey = state.list[state.current].sortKey
var currentX = Math.round(translateX / basedata.itemWidth)
var currentY = Math.round(translateY / basedata.itemHeight)
var endKey = currentX + basedata.columns * currentY
// 目标项时固定项则返回
var item = state.list[endKey]
if (item && item.fixed) return
// X轴或者Y轴超出范围则返回
if (isOutRange(currentX, basedata.columns, currentY, basedata.rows, endKey, state.list.length)) return
// 防止拖拽过程中发生乱序问题
if (startKey === endKey || startKey === state.preStartKey) return
state.preStartKey = startKey
var list = sortCore(startKey, endKey, state)
state.itemsInstance.forEach(function(itemInstance, index) {
var item = list[index]
if (index !== state.current) {
itemInstance.setStyle({
'transform': 'translate3d('+ item.translateX + ',' + item.translateY +', 0)'
})
}
})
// ownerInstance.callMethod('vibrate')
ownerInstance.callMethod('listDataChange', {
data: list
})
triggerCustomEvent(list, "change", ownerInstance)
}
var touchEnd = function(event, ownerInstance) {
var instance = event.instance
var dataset = instance.getDataset()
var state = ownerInstance.getState()
var basedata = state.basedata
if (!state.dragging || !edit) return
triggerCustomEvent(state.list, "sortEnd", ownerInstance)
instance.addClass('tn-drag__transition')
instance.setStyle({
'transform': 'translate3d('+ state.list[state.current].translateX + ',' + state.list[state.current].translateY + ', 0)'
})
state.itemsInstance.forEach(function(item, index) {
item.removeClass('tn-drag__transition')
})
state.preStartKey = -1
state.dragging = false
ownerInstance.callMethod('drag', {
dragging: false
})
state.current = -1
state.translateX = 0
state.translateY = 0
}
module.exports = {
longPress: longPress,
touchStart: touchStart,
touchMove: touchMove,
touchEnd: touchEnd,
baseDataObserver: baseDataObserver,
listObserver: listObserver
}
+278
View File
@@ -0,0 +1,278 @@
<template>
<view
class="tn-drag-class tn-drag"
:style="{
height: wrapHeight + 'rpx'
}"
:list="listData"
:basedata="baseData"
:change:list="wxs.listObserver"
:change:basedata="wxs.baseDataObserver"
>
<!-- #ifdef MP-WEIXIN -->
<view
v-for="(item, index) in listData"
:key="item.id"
class="tn-drag__item"
:style="{
width: 100 / columns + '%',
height: itemHeight + 'rpx'
}"
:data-index="index"
:data-basedata="baseData"
:data-edit="edit"
@longpress="wxs.longPress"
@touchstart="wxs.touchStart"
:catch:touchmove="dragging?wxs.touchMove:''"
:catch:touchend="dragging?wxs.touchEnd:''"
>
<slot :entity="item.data" :fixed="item.fixed" :index="index" :height="itemHeight" :isEdit="edit"></slot>
</view>
<!-- #endif -->
<!-- #ifndef MP-WEIXIN -->
<view
v-for="(item, index) in listData"
:key="item.id"
class="tn-drag__item"
:style="{
width: 100 / columns + '%',
height: itemHeight + 'rpx'
}"
@longpress="wxs.longPress"
:data-index="index"
:data-basedata="baseData"
:data-edit="edit"
@touchstart="wxs.touchStart"
@touchmove="wxs.touchMove"
@touchend="wxs.touchEnd"
>
<slot :entity="item.data" :fixed="item.fixed" :index="index" :height="itemHeight" :isEdit="edit"></slot>
</view>
<!-- #endif -->
</view>
</template>
<script src="./index.wxs" lang="wxs" module="wxs"></script>
<script>
export default {
name: 'tn-drag',
props: {
//
// fixed
list: {
type: Array,
default () {
return []
}
},
//
edit: {
type: Boolean,
default: true
},
//
columns: {
type: Number,
default: 3
},
// item, rpx
itemHeight: {
type: Number,
default: 0
},
//
scrollTop: {
type: Number,
default: 0
}
},
computed: {
wrapHeight() {
return this.rows * this.itemHeight
}
},
data() {
return {
//
baseData: {},
//
dragData: [],
//
rows: 0,
//
listData: [],
//
dragging: false
}
},
watch: {
list(val) {
this.listData = []
this.$nextTick(() => {
this.init()
})
},
columns(val) {
this.listData = []
this.$nextTick(() => {
this.init()
})
}
},
mounted() {
this.$nextTick(() => {
this.init()
})
},
methods: {
//
init() {
this.dragging = true
const initDragItem = item => {
const obj = {
...item
}
const fixed = obj?.fixed || false
delete obj["fixed"]
return {
id: this.unique(),
fixed,
data: {
...obj
}
}
}
let i = 0
const listData = (this.list || []).map((item, index) => {
let listItem = initDragItem(item)
//
listItem.realKey = i++
//
listItem.sortKey = index
listItem.translateX = `${(listItem.sortKey % this.columns) * 100}%`
listItem.translateY = `${Math.floor(listItem.sortKey / this.columns) * 100}%`
return listItem
})
this.rows = Math.ceil(listData.length / this.columns)
this.listData = listData
this.dragData = listData
if (listData.length === 0) return
// console.log(listData);
// dom
this.$nextTick(() => {
this.initRect()
})
},
// dom
initRect() {
const {
windowWidth,
windowHeight
} = uni.getSystemInfoSync()
let baseData = {}
baseData.windowHeight = windowHeight
baseData.realTopSize = 0
baseData.realBottomSize = 0
baseData.columns = this.columns
baseData.rows = this.rows
const query = uni.createSelectorQuery().in(this)
query.select('.tn-drag').boundingClientRect()
query.select('.tn-drag__item').boundingClientRect()
query.exec(res => {
if (!res) {
setTimeout(() => {
this.initRect()
}, 10)
return
}
baseData.itemWidth = res[1].width
baseData.itemHeight = res[1].height
baseData.left = res[0].left
baseData.top = res[0].top + this.scrollTop
this.dragging = false
this.baseData = baseData
})
},
//
vibrate() {
uni.vibrateShort()
},
//
pageScroll(e) {
uni.pageScrollTo({
scrollTop: e.scrollTop,
duration: 0
})
},
//
drag(e) {
this.dragging = e.dragging
},
//
listDataChange(e) {
this.dragData = e.data
},
// item
itemClick(index) {
const item = this.dragData[index]
this.$emit('click', {
key: item.realKey,
data: item.data
})
},
//
sortEnd(e) {
this.$emit('end', {
data: e.data
})
},
//
change(e) {
this.$emit('change', {
data: e.data
})
},
// id
unique(n = 6) {
let id = ''
for (let i = 0; i < n; i++) id += Math.floor(Math.random() * 10)
return 'tn_' + new Date().getTime() + id
}
}
}
</script>
<style lang="scss" scoped>
.tn-drag {
position: relative;
&__item {
position: absolute;
z-index: 2;
top: 0;
left: 0;
}
&__transition {
transition: transform 0.25s !important;
}
&__current {
z-index: 10 !important;
}
&__fixed {
z-index: 1 !important;
}
}
</style>
@@ -0,0 +1,995 @@
<template>
<view v-if="!disabled" class="tn-image-upload-class tn-image-upload">
<movable-area
class="tn-image-upload__movable-area"
:style="{
height: movableAreaHeight
}"
@mouseenter="mouseEnterArea"
@mouseleave="mouseLeaveArea"
>
<block
v-for="(item, index) in lists"
:key="item.id"
>
<movable-view
class="tn-image-upload__movable-view"
:style="{
width: $tn.string.getLengthUnitValue(width),
height: $tn.string.getLengthUnitValue(height),
zIndex: item.zIndex,
opacity: item.opacity,
}"
:x="item.x"
:y="item.y"
direction="all"
:damping="40"
:disabled="item.disabled"
@change="movableChange($event, item)"
@touchstart="movableStart(item)"
@mousedown="movableStart(item)"
@touchend="movableEnd(item)"
@mouseup="movableEnd(item)"
@longpress="movableLongPress(item)"
>
<view
class="tn-image-upload__item tn-image-upload__item-preview"
:style="{
width: $tn.string.getLengthUnitValue(itemWidth, 'px'),
height: $tn.string.getLengthUnitValue(itemHeight, 'px'),
transform: `scale(${item.scale})`
}"
>
<!-- 删除按钮 -->
<view
v-if="deleteable"
class="tn-image-upload__item-preview__delete"
@tap.stop="deleteItem(index)"
:style="{
borderTopColor: deleteBackgroundColor
}"
>
<view
class="tn-image-upload__item-preview__delete--icon"
:class="[`tn-icon-${deleteIcon}`]"
:style="{
color: deleteColor
}"
></view>
</view>
<!-- 进度条 -->
<tn-line-progress
v-if="showProgress && item.data.progress > 0 && !item.data.error"
class="tn-image-upload__item-preview__progress"
:percent="item.data.progress"
:showPercent="false"
:round="false"
:height="8"
></tn-line-progress>
<!-- 重试按钮 -->
<view v-if="item.data.error" class="tn-image-upload__item-preview__error-btn" @tap.stop="retry(index)">点击重试</view>
<!-- 图片信息 -->
<image
class="tn-image-upload__item-preview__image"
:src="item.data.url || item.data.path"
:mode="imageMode"
@tap.stop="doPreviewImage(item.data.url || item.data.path, index)"
></image>
</view>
</movable-view>
</block>
<!-- 添加按钮 -->
<view
v-if="maxCount > lists.length"
class="tn-image-upload__add"
:style="{
top: addBtn.y + 'px',
left: addBtn.x + 'px',
width: $tn.string.getLengthUnitValue(itemWidth, 'px'),
height: $tn.string.getLengthUnitValue(itemHeight, 'px')
}"
@tap="selectFile"
>
<!-- 添加按钮 -->
<view
class="tn-image-upload__item tn-image-upload__item-add"
hover-class="tn-hover-class"
hover-stay-time="150"
:style="{
width: $tn.string.getLengthUnitValue(itemWidth, 'px'),
height: $tn.string.getLengthUnitValue(itemHeight, 'px')
}"
>
<view class="tn-image-upload__item-add--icon tn-icon-add"></view>
<view class="tn-image-upload__item-add__tips">{{ uploadText }}</view>
</view>
</view>
</movable-area>
</view>
</template>
<script>
export default {
name: 'tn-image-upload-drag',
props: {
//
fileList: {
type: Array,
default() {
return []
}
},
//
action: {
type: String,
default: ''
},
//
name: {
type: String,
default: 'file'
},
//
header: {
type: Object,
default() {
return {}
}
},
//
formData: {
type: Object,
default() {
return {}
}
},
//
disabled: {
type: Boolean,
default: false
},
//
autoUpload: {
type: Boolean,
default: true
},
//
maxCount: {
type: Number,
default: 9
},
//
imageMode: {
type: String,
default: 'aspectFill'
},
//
previewFullImage: {
type: Boolean,
default: true
},
//
showProgress: {
type: Boolean,
default: true
},
//
deleteable: {
type: Boolean,
default: true
},
//
deleteIcon: {
type: String,
default: 'close'
},
//
deleteBackgroundColor: {
type: String,
default: ''
},
//
deleteColor: {
type: String,
default: ''
},
//
uploadText: {
type: String,
default: '选择图片'
},
// toast
showTips: {
type: Boolean,
default: true
},
//
width: {
type: Number,
default: 200
},
//
height: {
type: Number,
default: 200
},
//
// https://uniapp.dcloud.io/api/media/image
sizeType: {
type: Array,
default() {
return ['original', 'compressed']
}
},
//
sourceType: {
type: Array,
default() {
return ['album', 'camera']
}
},
//
multiple: {
type: Boolean,
default: true
},
// (byte)
maxSize: {
type: Number,
default: 10 * 1024 * 1024
},
//
limitType: {
type: Array,
default() {
return ['png','jpg','jpeg','webp','gif','image']
}
},
// json
toJson: {
type: Boolean,
default: true
},
//
beforeUpload: {
type: Function,
default: null
},
//
beforeRemove: {
type: Function,
default: null
},
index: {
type: [Number, String],
default: ''
}
},
computed: {
movableAreaHeight() {
if (this.lists.length < this.maxCount) {
return Math.ceil((this.lists.length + 1) / this.baseData.columns) * uni.upx2px(this.height) + 'px'
} else {
return Math.ceil(this.lists.length / this.baseData.columns) * uni.upx2px(this.height) + 'px'
}
},
itemWidth() {
return uni.upx2px(this.width) - (uni.upx2px(10) * 2)
},
itemHeight() {
return uni.upx2px(this.height) - (uni.upx2px(10) * 2)
}
},
data() {
return {
lists: [],
uploading: false,
baseData: {
windowWidth: 0,
columns: 0,
dragItem: null,
widthPx: 0,
heightPx: 0
},
addBtn: {
x: 0,
y: 0
},
timer: null,
dragging: false
}
},
watch: {
// fileList: {
// handler(val) {
// val.map(value => {
// // fileList()fileList
// // watchthis.lists
// // sometrueeverytrue
// let tmp = this.lists.some(listVal => {
// return listVal.url === value.url
// })
// //
// !tmp && this.lists.push({ url: value.url, error: false, progress: 100 })
// })
// },
// immediate: true
// },
lists(val) {
this.$emit('on-list-change', this.sortList(), this.index)
}
},
created() {
this.baseData.windowWidth = uni.getSystemInfoSync().windowWidth
},
mounted() {
this.$nextTick(() => {
this.updateDragInfo()
})
},
methods: {
//
clear() {
this.lists = []
this.updateAddBtnPositioin()
},
//
reUpload() {
this.uploadFile()
},
//
selectFile() {
if (this.disabled) return
const {
name = '',
maxCount,
multiple,
maxSize,
sizeType,
lists,
camera,
compressed,
sourceType
} = this
let chooseFile = null
const newMaxCount = maxCount - lists.length
// 使 chooseImage
chooseFile = new Promise((resolve, reject) => {
uni.chooseImage({
count: multiple ? (newMaxCount > 9 ? 9 : newMaxCount) : 1,
sourceType,
sizeType,
success: resolve,
fail: reject
})
})
chooseFile.then(res => {
let file = null
let listOldLength = lists.length
res.tempFiles.map((val, index) => {
if (!this.checkFileExt(val)) return
//
if (!multiple && index >= 1) return
if (val.size > maxSize) {
this.$emit('on-oversize', val, this.sortList(), this.index)
this.showToast('超出可允许文件大小')
} else {
if (maxCount <= lists.length) {
this.$emit('on-exceed', val, this.sortList(), this.index)
this.showToast('超出最大允许的文件数')
return
}
lists.push(this.handleDragListItem({
url: val.path,
progress: 0,
error: false,
file: val
}))
this.updateAddBtnPositioin()
}
})
this.$emit('on-choose-complete', this.sortList(), this.index)
if (this.autoUpload) this.uploadFile(listOldLength)
}).catch(err => {
this.$emit('on-choose-fail', err)
})
},
//
showToast(message, force = false) {
if (this.showTips || force) {
this.$tn.message.toast(message)
}
},
// ref
upload() {
this.uploadFile()
},
//
retry(index) {
this.lists[index].data.progress = 0
this.lists[index].data.error = false
this.lists[index].data.response = null
this.$tn.message.loading('重新上传')
this.uploadFile(index)
},
//
async uploadFile(index = 0) {
if (this.disabled) return
if (this.uploading) return
//
if (index >= this.lists.length) {
this.$emit('on-uploaded', this.sortList(), this.index)
return
}
//
if (this.lists[index].data.progress === 100) {
this.lists[index].data.uploadTask = null
if (this.autoUpload) this.uploadFile(index + 1)
return
}
// before-upload
if (this.beforeUpload && typeof(this.beforeUpload) === 'function') {
// (H5)thisthis
// bind()thisthisthis
// uploadtn-formthis.$parenttn-formthis
// this$parentthis.$u.$parent.call(this)
let beforeResponse = this.beforeUpload.bind(this.$tn.$parent.call(this))(index, this.lists)
// Promise
if (!!beforeResponse && typeof beforeResponse.then === 'function') {
await beforeResponse.then(res => {
// promise
}).catch(err => {
// catch
return this.uploadFile(index + 1)
})
} else if (beforeResponse === false) {
// flase
return this.uploadFile(index + 1)
} else {
// true
}
}
//
if (!this.action) {
this.showToast('请配置上传地址', true)
return
}
this.lists[index].data.error = false
this.uploading = true
//
const task = uni.uploadFile({
url: this.action,
filePath: this.lists[index].data.url,
name: this.name,
formData: this.formData,
header: this.header,
success: res => {
// jsonjson
let data = this.toJson && this.$tn.test.jsonString(res.data) ? JSON.parse(res.data) : res.data
if (![200, 201, 204].includes(res.statusCode)) {
this.uploadError(index, data)
} else {
this.lists[index].data.response = data
this.lists[index].data.progress = 100
this.lists[index].data.error = false
this.$emit('on-success', data, index, this.sortList(), this.index)
}
},
fail: err => {
this.uploadError(index, err)
},
complete: res => {
this.$tn.message.closeLoading()
this.uploading = false
this.uploadFile(index + 1)
this.$emit('on-change', res, index, this.sortList(), this.index)
}
})
this.lists[index].uploadTask = task
task.onProgressUpdate(res => {
if (res.progress > 0) {
this.lists[index].data.progress = res.progress
this.$emit('on-progress', res, index, this.sortList(), this.index)
}
})
},
//
uploadError(index, err) {
this.lists[index].data.progress = 0
this.lists[index].data.error = true
this.lists[index].data.response = null
this.showToast('上传失败,请重试')
this.$emit('on-error', err, index, this.sortList(), this.index)
},
//
deleteItem(index) {
if (!this.deleteable) return
this.$tn.message.modal(
'提示',
'您确定要删除吗?',
async () => {
// before-remove
// before-remove
if (this.beforeRemove && typeof(this.beforeRemove) === 'function') {
let beforeResponse = this.beforeRemove.bind(this.$tn.$parent.call(this))(index, this.lists)
// promise
if (!!beforeResponse && typeof beforeResponse.then === 'function') {
await beforeResponse.then(res => {
// promise
this.handlerDeleteItem(index)
}).catch(err => {
this.showToast('删除操作被中断')
})
} else if (beforeResponse === false) {
this.showToast('删除操作被中断')
} else {
this.handlerDeleteItem(index)
}
} else {
this.handlerDeleteItem(index)
}
}, true)
},
//
handlerDeleteItem(index) {
//
if (this.lists[index].data.progress < 100 && this.lists[index].data.progress > 0) {
typeof this.lists[index].data.uploadTask !== 'undefined' && this.lists[index].data.uploadTask.abort()
}
this.remove(index)
this.$forceUpdate()
this.$emit('on-remove', index, this.sortList(), this.index)
this.showToast('删除成功')
},
// ref
remove(index) {
if (!this.deleteable) return
//
if (index >= 0 && index < this.lists.length) {
let currentItemIndex = this.lists[index].index
this.lists.splice(index, 1)
//
for (let item of this.lists) {
if (item.index > currentItemIndex) {
item.index -= 1
item.x = item.positionX * this.baseData.widthPx
item.y = item.positionY * this.baseData.heightPx
item.positionX = item.index % this.baseData.columns
item.positionY = Math.floor(item.index / this.baseData.columns)
this.$nextTick(() => {
item.x = item.positionX * this.baseData.widthPx
item.y = item.positionY * this.baseData.heightPx
})
}
}
this.updateAddBtnPositioin()
}
},
//
doPreviewImage(url, index) {
if (!this.previewFullImage) return
const images = this.lists.sort((l1, l2) => { return l1.index - l2.index}).map(item => item.data.url || item.data.path)
uni.previewImage({
urls: images,
current: url,
success: () => {
this.$emit('on-preview', url, this.sortList(), this.index)
},
fail: () => {
this.showToast('预览图片失败')
}
})
},
//
checkFileExt(file) {
//
let noArrowExt = false
//
let fileExt = ''
const reg = /.+\./
// #ifdef H5
fileExt = file.name.replace(reg, '').toLowerCase()
// #endif
// #ifndef H5
fileExt = file.path.replace(reg, '').toLowerCase()
// #endif
noArrowExt = this.limitType.some(ext => {
return ext.toLowerCase() === fileExt
})
if (!noArrowExt) this.showToast(`不支持${fileExt}格式的文件`)
return noArrowExt
},
/********************* 拖拽处理 ********************/
//
updateDragInfo() {
this.baseData.widthPx = uni.upx2px(this.width)
this.baseData.heightPx = uni.upx2px(this.height)
//
const query = uni.createSelectorQuery().in(this)
query.select('.tn-image-upload__movable-area').boundingClientRect()
query.exec((res) => {
if (!res) {
setTimeout(() => {
this.updateDragInfo()
}, 10)
return
}
this.baseData.columns = Math.floor(res[0].width / this.baseData.widthPx)
//
this.lists = []
this.fileList.forEach((item) => {
// fileList()fileList
// watchthis.lists
// sometrueeverytrue
let tmp = this.lists.map(value => {
return value.data
}).some(listVal => {
return listVal.url === item.url
})
//
!tmp && this.lists.push(this.handleDragListItem({
url: item.url,
error: false,
progress: 100
}))
})
//
this.updateAddBtnPositioin()
})
},
//
handleDragListItem(item) {
const positionX = this.lists.length % this.baseData.columns
const positionY = Math.floor(this.lists.length / this.baseData.columns)
const x = positionX * this.baseData.widthPx
const y = positionY * this.baseData.heightPx
return {
id: this.unique(),
x,
y,
preX: x,
preY: y,
positionX,
positionY,
zIndex:1,
disabled: true,
opacity: 1,
scale: 1,
index: this.lists.length,
offset: 0,
moveEnd: false,
moving: false,
data: {
...item
}
}
},
// id
unique(n = 6) {
let id = ''
for (let i = 0; i < n; i++) id += Math.floor(Math.random() * 10)
return 'tn_' + new Date().getTime() + id
},
//
updateAddBtnPositioin() {
if (this.lists.length >= this.maxCount) return
this.addBtn.x = (this.lists.length % this.baseData.columns) * this.baseData.widthPx
this.addBtn.y = Math.floor(this.lists.length / this.baseData.columns) * this.baseData.heightPx
},
//
sortList() {
const list = this.lists.slice()
list.sort((l1, l2) => {
return l1.index - l2.index
})
return list.map(item => {
return item.data
})
},
mouseEnterArea () {
// #ifdef H5
this.lists.forEach(item => {
item.disabled = false
})
// #endif
},
mouseLeaveArea () {
// #ifdef H5
if (this.baseData.dragItem) {
this.lists.forEach(item => {
item.disabled = true
item.zIndex = 1
item.offset = 0
item.moveEnd = true
if (item.id === this.baseData.dragItem.id) {
if (this.timer) {
clearTimeout(this.timer)
this.timer = null
}
item.x = item.preX
item.y = item.preY
this.$nextTick(() => {
item.x = item.positionX * this.baseData.widthPx
item.y = item.positionY * this.baseData.heightPx
this.baseData.dragItem = null
})
}
})
this.dragging = false
}
// #endif
},
movableLongPress(item) {
// #ifndef H5
uni.vibrateShort()
// console.log("LongPress--------------------------------------------------------------");
this.lists.forEach(value => {
value.moving = false
})
this.dragging = true
//
const index = this.lists.findIndex(obj => {
return obj.id === item.id
})
item.disabled = false
item.opacity = 0.7
item.scale = 1.1
this.$set(this.lists, index, item)
// #endif
},
movableChange (e, item) {
if (!item || !this.dragging) return
// console.log("movableChange");
item.moving = true
item.preX = e.detail.x
item.preY = e.detail.y
// console.log(item.preX, item.preY);
if (e.detail.source === 'touch') {
if (!item.moveEnd) {
item.offset = Math.sqrt(
Math.pow(item.preX - item.positionX * this.baseData.widthPx, 2) +
Math.pow(item.preY - item.positionY * this.baseData.heightPx, 2))
}
let x = Math.floor((e.detail.x + this.baseData.widthPx / 2) / this.baseData.widthPx)
if (x > this.baseData.columns) return
let y = Math.floor((e.detail.y + this.baseData.heightPx / 2) / this.baseData.heightPx)
let index = this.baseData.columns * y + x
if (item.index !== index && index < this.lists.length) {
for (let obj of this.lists) {
if (item.index > index && obj.index >= index && obj.index < item.index) {
this.updateItemPosition(obj, 1)
} else if (item.index < index && obj.index <= index && obj.index > item.index) {
this.updateItemPosition(obj, -1)
} else if (item.id != obj.id) {
// obj.offset = 0
// console.log(obj.moving);
// if (!obj.moving) {
// obj.preX = obj.positionX * this.baseData.widthPx
// obj.preY = obj.positionY * this.baseData.heightPx
// console.log("moving", obj.id, obj.preX, obj.preY);
// }
// obj.x = obj.preX
// obj.y = obj.preY
// // console.log(obj.id, obj.preX, obj.preY);
// this.$nextTick(() => {
// obj.x = obj.positionX * this.baseData.widthPx
// obj.y = obj.positionY * this.baseData.heightPx
// })
}
}
item.index = index
item.positionX = x
item.positionY = y
// TODO
}
}
},
movableStart (item) {
// console.log("movableStart");
this.lists.forEach(item => {
item.zIndex = 1
// #ifdef H5
item.disabled = false
// #endif
})
item.zIndex = 10
item.moveEnd = false
this.baseData.dragItem = item
// #ifdef H5
this.dragging =true
this.timer = setTimeout(() => {
item.opacity = 0.7
item.scale = 1.1
clearTimeout(this.timer)
this.timer = null
}, 200)
// #endif
},
movableEnd (item) {
if (!this.dragging) return
// console.log("movableEnd");
const index = this.lists.findIndex(obj => {
return obj.id === item.id
})
if (!item.moving) {
item.preX = item.positionX * this.baseData.widthPx
item.preY = item.positionY * this.baseData.heightPx
}
item.x = item.preX
item.y = item.preY
item.offset = 0
item.moveEnd = true
item.moving = false
item.disabled = true
// console.log(item.x, item.y);
// console.log(item.id, item.moving);
// this.$set(this.lists, index, item)
// this.lists[index] = item
// console.log(this.lists[index]);
this.lists.forEach(listValue => {
listValue.moving = false
listValue.disabled = true
})
this.$nextTick(() => {
item.x = item.positionX * this.baseData.widthPx
item.y = item.positionY * this.baseData.heightPx
item.opacity = 1
item.scale = 1
this.baseData.dragItem = null
this.dragging = false
// console.log(item.x, item.y);
this.$set(this.lists, index, item)
})
this.$emit('sort-list', this.sortList())
},
//
updateItemPosition(item, offset) {
const index = this.lists.findIndex(obj => {
return obj.id === item.id
})
item.index += offset
item.offset = 0
item.positionX = item.index % this.baseData.columns
item.positionY = Math.floor(item.index / this.baseData.columns)
if (!item.moving) {
item.preX = item.positionX * this.baseData.widthPx
item.preY = item.positionY * this.baseData.heightPx
}
item.x = item.preX
item.y = item.preY
// console.log("updateItemPosition", item.id, item.preX, item.preY);
// this.$set(this.lists, index, item)
this.$nextTick(() => {
item.x = item.positionX * this.baseData.widthPx
item.y = item.positionY * this.baseData.heightPx
this.$set(this.lists, index, item)
})
}
}
}
</script>
<style lang="scss" scoped>
.tn-image-upload {
position: relative;
&__movable-area {
width: 100%;
}
&__movable-view {
border-radius: 10rpx;
overflow: hidden;
}
&__item {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
align-items: center;
justify-content: center;
width: 200rpx;
height: 200rpx;
background-color: transparent;
position: relative;
border-radius: 10rpx;
overflow: hidden;
&-preview {
border: 1rpx solid $tn-border-solid-color;
&__delete {
display: flex;
align-items: center;
justify-content: center;
position: absolute;
top: 0;
right: 0;
z-index: 10;
border-top: 60rpx solid;
border-left: 60rpx solid transparent;
border-top-color: $tn-color-red;
width: 0rpx;
height: 0rpx;
&--icon {
position: absolute;
top: -50rpx;
right: 6rpx;
color: #FFFFFF;
font-size: 24rpx;
line-height: 1;
}
}
&__progress {
position: absolute;
width: auto;
bottom: 0rpx;
left: 0rpx;
right: 0rpx;
z-index: 9;
/* #ifdef MP-WEIXIN */
display: inline-flex;
/* #endif */
}
&__error-btn {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background-color: $tn-color-red;
color: #FFFFFF;
font-size: 20rpx;
padding: 8rpx 0;
text-align: center;
z-index: 9;
line-height: 1;
}
&__image {
display: block;
width: 100%;
height: 100%;
border-radius: 10rpx;
}
}
&-add {
flex-direction: column;
color: $tn-content-color;
font-size: 26rpx;
&--icon {
font-size: 40rpx;
}
&__tips {
margin-top: 20rpx;
line-height: 40rpx;
}
}
}
&__add {
background-color: $tn-font-holder-color;
position: absolute;
// margin: 10rpx;
// margin-left: 0;
}
}
</style>
@@ -1,5 +1,5 @@
<template>
<view v-if="!disabled" class="tn-image-upload-class tn-image-upload">
<view class="tn-image-upload-class tn-image-upload">
<block v-if="showUploadList">
<view
v-for="(item, index) in lists"
@@ -88,6 +88,10 @@
return []
}
},
isUniCloud:{
type: Boolean,
default: false
},
//
action: {
type: String,
@@ -312,7 +316,7 @@
let file = null
let listOldLength = lists.length
res.tempFiles.map((val, index) => {
if (!this.checkFileExt(val)) return
if (!this.checkFileExt(val)) return
//
if (!multiple && index >= 1) return
@@ -329,7 +333,13 @@
url: val.path,
progress: 0,
error: false,
file: val
file: val,
//#ifdef H5
extension: val.name.substring(val.name.lastIndexOf(".")) //
//#endif
//#ifndef H5
extension: val.path.substring(val.path.lastIndexOf(".")) //
//#endif
})
}
})
@@ -359,6 +369,7 @@
},
//
async uploadFile(index = 0) {
var that=this;
if (this.disabled) return
if (this.uploading) return
//
@@ -395,48 +406,87 @@
}
}
//
if (!this.action) {
this.showToast('请配置上传地址', true)
return
}
this.lists[index].error = false
this.uploading = true
//
const task = uni.uploadFile({
url: this.action,
filePath: this.lists[index].url,
name: this.name,
formData: this.formData,
header: this.header,
success: res => {
// jsonjson
let data = this.toJson && this.$tn.test.jsonString(res.data) ? JSON.parse(res.data) : res.data
if (![200, 201, 204].includes(res.statusCode)) {
this.uploadError(index, data)
} else {
this.lists[index].response = data
this.lists[index].progress = 100
this.lists[index].error = false
this.$emit('on-success', data, index, this.lists, this.index)
}
},
fail: err => {
this.uploadError(index, err)
},
complete: res => {
this.$tn.message.closeLoading()
this.uploading = false
this.uploadFile(index + 1)
this.$emit('on-change', res, index, this.lists, this.index)
}
})
this.lists[index].uploadTask = task
task.onProgressUpdate(res => {
if (res.progress > 0) {
this.lists[index].progress = res.progress
this.$emit('on-progress', res, index, this.lists, this.index)
}
})
if(this.isUniCloud){
//uinCloud
this.lists[index].error = false
this.uploading = true
//
const task = uniCloud.uploadFile({
filePath: that.lists[index].url,
cloudPath: that.$tn.uuid() + that.lists[index].extension, //+
onUploadProgress(progressEvent) {
if (progressEvent.loaded > 0) {
that.lists[index].progress = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
);
that.$emit('on-progress', progressEvent, index, that.lists, that.index)
}
},
success(res) {
if (res.success) {
that.lists[index].response = res
that.lists[index].progress = 100
that.lists[index].error = false
that.$emit('success', res, index, that.lists, that.index)
} else {
that.uploadError(index, res)
}
},
fail: (err) => {
that.uploadError(index, err)
},
complete: (res) => {
// this.$t.message.closeLoading()
that.uploading = false
that.uploadFile(index + 1)
that.$emit('on-change', res, index, that.lists, that.index)
}
});
that.lists[index].uploadTask = task
}else{
if (!this.action) {
this.showToast('请配置上传地址', true)
return
}
this.lists[index].error = false
this.uploading = true
//
const task = uni.uploadFile({
url: this.action,
filePath: this.lists[index].url,
name: this.name,
formData: this.formData,
header: this.header,
success: res => {
// jsonjson
let data = this.toJson && this.$tn.test.jsonString(res.data) ? JSON.parse(res.data) : res.data
if (![200, 201, 204].includes(res.statusCode)) {
this.uploadError(index, data)
} else {
this.lists[index].response = data
this.lists[index].progress = 100
this.lists[index].error = false
this.$emit('on-success', data, index, this.lists, this.index)
}
},
fail: err => {
this.uploadError(index, err)
},
complete: res => {
this.$tn.message.closeLoading()
this.uploading = false
this.uploadFile(index + 1)
this.$emit('on-change', res, index, this.lists, this.index)
}
})
this.lists[index].uploadTask = task
task.onProgressUpdate(res => {
if (res.progress > 0) {
this.lists[index].progress = res.progress
this.$emit('on-progress', res, index, this.lists, this.index)
}
})
}
},
//
uploadError(index, err) {
+318 -327
View File
@@ -1,334 +1,325 @@
<template>
<view
:id="elId"
class="tn-rate-class tn-rate"
@touchmove.stop.prevent="touchMove"
>
<view class="tn-rate__wrap" :class="[elClass]" v-for="(item, index) in count" :key="index">
<view
class="tn-rate__wrap__icon"
:class="[`tn-icon-${(allowHalf && halfIcon ? activeIndex > index + 1 : activeIndex > index) ? elActionIcon : elInactionIcon}`]"
:style="[iconStyle(index)]"
@tap="click(index + 1, $event)"
>
<!-- 半图标 -->
<view
v-if="showHalfIcon(index)"
class="tn-rate__wrap__icon--half"
:class="[`tn-icon-${elActionIcon}`]"
:style="[halfIconStyle]"
></view>
</view>
</view>
</view>
<view :id="elId" class="tn-rate-class tn-rate" @touchmove.stop.prevent="touchMove">
<view class="tn-rate__wrap" :class="[elClass]" v-for="(item, index) in count" :key="index">
<view class="tn-rate__wrap__icon"
:class="[`tn-icon-${(allowHalf && halfIcon ? activeIndex > index + 1 : activeIndex > index) ? elActionIcon : elInactionIcon}`]"
:style="[iconStyle(index)]" @tap="click(index + 1, $event)">
<!-- 半图标 -->
<view v-if="showHalfIcon(index)" class="tn-rate__wrap__icon--half" :class="[`tn-icon-${elActionIcon}`]"
:style="[halfIconStyle]"></view>
</view>
</view>
</view>
</template>
<script>
export default {
name: 'tn-rate',
props: {
//
value: {
type: Number,
default: 0
},
//
count: {
type: Number,
default: 5
},
//
minCount: {
type: Number,
default: 0
},
//
disabled: {
type: Boolean,
default: false
},
//
allowHalf: {
type: Boolean,
default: false
},
//
size: {
type: Number,
default: 32
},
//
activeIcon: {
type: String,
default: 'star-fill'
},
//
inactiveIcon: {
type: String,
default: 'star'
},
//
activeColor: {
type: String,
default: '#01BEFF'
},
//
inactiveColor: {
type: String,
default: '#AAAAAA'
},
//
gutter: {
type: Number,
default: 10
},
//
colors: {
type: Array,
default() {
return []
}
},
//
icons: {
type: Array,
default() {
return []
}
}
},
computed: {
//
showHalfIcon(index) {
return index => {
return this.allowHalf && Math.ceil(this.activeIndex) === index + 1 && this.halfIcon
}
},
//
elActionIcon() {
const len = this.icons.length
// icons324
// 5
if (len && len <= this.count) {
const step = Math.round(Math.ceil(this.activeIndex) / Math.round(this.count / len))
if (step < 1) return this.icons[0]
if (step > len) return this.icons[len - 1]
return this.icons[step - 1]
}
return this.activeIcon
},
//
elInactionIcon() {
const len = this.icons.length
// icons324
// 5
if (len && len <= this.count) {
const step = Math.round(Math.ceil(this.activeIndex) / Math.round(this.count / len))
if (step < 1) return this.icons[0]
if (step > len) return this.icons[len - 1]
return this.icons[step - 1]
}
return this.inactiveIcon
},
//
elActionColor() {
const len = this.colors.length
// colors(5colors32
// 45)
if (len && len <= this.count) {
const step = Math.round(Math.ceil(this.activeIndex) / Math.round(this.count / len))
if (step < 1) return this.colors[0]
if (step > len) return this.colors[len - 1]
return this.colors[step - 1]
}
return this.activeColor
},
//
iconStyle() {
return index => {
let style = {}
style.fontSize = this.$tn.string.getLengthUnitValue(this.size)
style.padding = `0 ${this.$tn.string.getLengthUnitValue(this.gutter / 2)}`
//
if (this.allowHalf && this.halfIcon) {
style.color = this.activeIndex > index + 1 ? this.elActionColor : this.inactiveColor
} else {
style.color = this.activeIndex > index ? this.elActionColor : this.inactiveColor
}
return style
}
},
//
halfIconStyle() {
let style = {}
style.fontSize = this.$tn.string.getLengthUnitValue(this.size)
style.padding = `0 ${this.$tn.string.getLengthUnitValue(this.gutter / 2)}`
style.color = this.elActionColor
return style
}
},
data() {
return {
//
elId: this.$tn.uuid(),
elClass: this.$tn.uuid(),
//
starBoxLeft: 0,
//
activeIndex: this.value,
//
starWidth: 0,
//
starWidthArr: [],
//
halfIcon: false,
}
},
watch: {
value(val) {
this.activeIndex = val
if (this.allowHalf && (val % 1 === 0.5)) {
this.halfIcon = true
} else {
this.halfIcon = false
}
},
size() {
//
this.$nextTick(() => {
this.getElRectById()
this.getElRectByClass()
})
}
},
mounted() {
this.getElRectById()
this.getElRectByClass()
},
methods: {
//
getElRectById() {
this._tGetRect('#'+this.elId).then(res => {
this.starBoxLeft = res.left
})
},
//
getElRectByClass() {
this._tGetRect('.'+this.elClass).then(res => {
this.starWidth = res.width
//
for (let i = 0; i < this.count; i++) {
this.starWidthArr[i] = (i + 1) * this.starWidth
}
})
},
//
touchMove(e) {
if (this.disabled) return
if (!e.changedTouches[0]) return
const movePageX = e.changedTouches[0].pageX
//
const distance = movePageX - this.starBoxLeft
// 0
if (distance <= 0) {
this.activeIndex = 0
}
//
let index = Math.ceil(distance / this.starWidth)
if (this.allowHalf) {
const iconHalfWidth = this.starWidthArr[index - 1] - (this.starWidth / 2)
if (distance < iconHalfWidth) {
this.halfIcon = true
index -= 0.5
} else {
this.halfIcon = false
}
}
this.activeIndex = index > this.count ? this.count : index
if (this.activeIndex < this.minCount) this.activeIndex = this.minCount
this.emitEvent()
},
//
click(index, e) {
if (this.disabled) return
//
if (this.allowHalf) {
if (!e.changedTouches[0]) return
const movePageX = e.changedTouches[0].pageX
//
const distance = movePageX - this.starBoxLeft
const iconHalfWidth = this.starWidthArr[index - 1] - (this.starWidth / 2)
if (distance < iconHalfWidth) {
this.halfIcon = true
} else {
this.halfIcon = false
}
}
// 0
if (index == 1) {
if (this.allowHalf && this.allowHalf) {
if ((this.activeIndex === 0.5 && this.halfIcon) ||
(this.activeIndex === 1 && !this.halfIcon)) {
this.activeIndex = 0
} else {
this.activeIndex = this.halfIcon ? 0.5 : 1
}
} else {
if (this.activeIndex == 1) {
this.activeIndex = 0
} else {
this.activeIndex = 1
}
}
} else {
this.activeIndex = (this.allowHalf && this.halfIcon) ? index - 0.5 : index
}
if (this.activeIndex < this.minCount) this.activeIndex = this.minCount
this.emitEvent()
},
//
emitEvent() {
this.$emit('change', this.activeIndex)
// v-model
this.$emit('input', this.activeIndex)
}
}
}
export default {
name: 'tn-rate',
props: {
//
value: {
type: Number,
default: 0
},
//
count: {
type: Number,
default: 5
},
//
minCount: {
type: Number,
default: 0
},
//
disabled: {
type: Boolean,
default: false
},
//
allowHalf: {
type: Boolean,
default: false
},
//
size: {
type: Number,
default: 32
},
//
activeIcon: {
type: String,
default: 'star-fill'
},
//
inactiveIcon: {
type: String,
default: 'star'
},
//
activeColor: {
type: String,
default: '#01BEFF'
},
//
inactiveColor: {
type: String,
default: '#AAAAAA'
},
//
gutter: {
type: Number,
default: 10
},
//
colors: {
type: Array,
default () {
return []
}
},
//
icons: {
type: Array,
default () {
return []
}
}
},
computed: {
//
showHalfIcon(index) {
return index => {
return this.allowHalf && Math.ceil(this.activeIndex) === index + 1 && this.halfIcon
}
},
//
elActionIcon() {
const len = this.icons.length
// icons324
// 5
if (len && len <= this.count) {
const step = Math.round(Math.ceil(this.activeIndex) / Math.round(this.count / len))
if (step < 1) return this.icons[0]
if (step > len) return this.icons[len - 1]
return this.icons[step - 1]
}
return this.activeIcon
},
//
elInactionIcon() {
const len = this.icons.length
// icons324
// 5
if (len && len <= this.count) {
const step = Math.round(Math.ceil(this.activeIndex) / Math.round(this.count / len))
if (step < 1) return this.icons[0]
if (step > len) return this.icons[len - 1]
return this.icons[step - 1]
}
return this.inactiveIcon
},
//
elActionColor() {
const len = this.colors.length
// colors(5colors32
// 45)
if (len && len <= this.count) {
const step = Math.round(Math.ceil(this.activeIndex) / Math.round(this.count / len))
if (step < 1) return this.colors[0]
if (step > len) return this.colors[len - 1]
return this.colors[step - 1]
}
return this.activeColor
},
//
iconStyle() {
return index => {
let style = {}
style.fontSize = this.$tn.string.getLengthUnitValue(this.size)
style.padding = `0 ${this.$tn.string.getLengthUnitValue(this.gutter / 2)}`
//
if (this.allowHalf && this.halfIcon) {
style.color = this.activeIndex > index + 1 ? this.elActionColor : this.inactiveColor
} else {
style.color = this.activeIndex > index ? this.elActionColor : this.inactiveColor
}
return style
}
},
//
halfIconStyle() {
let style = {}
style.fontSize = this.$tn.string.getLengthUnitValue(this.size)
style.padding = `0 ${this.$tn.string.getLengthUnitValue(this.gutter / 2)}`
style.color = this.elActionColor
return style
}
},
data() {
return {
//
elId: this.$tn.uuid(),
elClass: this.$tn.uuid(),
//
starBoxLeft: 0,
//
activeIndex: this.value,
//
starWidth: 0,
//
starWidthArr: [],
//
halfIcon: false,
}
},
watch: {
value: {
handler(newName, oldName) {
this.activeIndex = newName
if (this.allowHalf && (newName % 1 === 0.5)) {
this.halfIcon = true
} else {
this.halfIcon = false
}
},
immediate: true
},
size() {
//
this.$nextTick(() => {
this.getElRectById()
this.getElRectByClass()
})
}
},
mounted() {
this.getElRectById()
this.getElRectByClass()
},
methods: {
//
getElRectById() {
this._tGetRect('#' + this.elId).then(res => {
this.starBoxLeft = res.left
})
},
//
getElRectByClass() {
this._tGetRect('.' + this.elClass).then(res => {
this.starWidth = res.width
//
for (let i = 0; i < this.count; i++) {
this.starWidthArr[i] = (i + 1) * this.starWidth
}
})
},
//
touchMove(e) {
if (this.disabled) return
if (!e.changedTouches[0]) return
const movePageX = e.changedTouches[0].pageX
//
const distance = movePageX - this.starBoxLeft
// 0
if (distance <= 0) {
this.activeIndex = 0
}
//
let index = Math.ceil(distance / this.starWidth)
if (this.allowHalf) {
const iconHalfWidth = this.starWidthArr[index - 1] - (this.starWidth / 2)
if (distance < iconHalfWidth) {
this.halfIcon = true
index -= 0.5
} else {
this.halfIcon = false
}
}
this.activeIndex = index > this.count ? this.count : index
if (this.activeIndex < this.minCount) this.activeIndex = this.minCount
this.emitEvent()
},
//
click(index, e) {
if (this.disabled) return
//
if (this.allowHalf) {
if (!e.changedTouches[0]) return
const movePageX = e.changedTouches[0].pageX
//
const distance = movePageX - this.starBoxLeft
const iconHalfWidth = this.starWidthArr[index - 1] - (this.starWidth / 2)
if (distance < iconHalfWidth) {
this.halfIcon = true
} else {
this.halfIcon = false
}
}
// 0
if (index == 1) {
if (this.allowHalf && this.allowHalf) {
if ((this.activeIndex === 0.5 && this.halfIcon) ||
(this.activeIndex === 1 && !this.halfIcon)) {
this.activeIndex = 0
} else {
this.activeIndex = this.halfIcon ? 0.5 : 1
}
} else {
if (this.activeIndex == 1) {
this.activeIndex = 0
} else {
this.activeIndex = 1
}
}
} else {
this.activeIndex = (this.allowHalf && this.halfIcon) ? index - 0.5 : index
}
if (this.activeIndex < this.minCount) this.activeIndex = this.minCount
this.emitEvent()
},
//
emitEvent() {
this.$emit('change', this.activeIndex)
// v-model
this.$emit('input', this.activeIndex)
}
}
}
</script>
<style lang="scss" scoped>
.tn-rate {
display: inline-flex;
align-items: center;
margin: 0;
padding: 0;
&__wrap {
&__icon {
position: relative;
box-sizing: border-box;
&--half {
position: absolute;
top: 0;
left: 0;
display: inline-block;
overflow: hidden;
width: 50%;
}
}
}
}
</style>
.tn-rate {
display: inline-flex;
align-items: center;
margin: 0;
padding: 0;
&__wrap {
&__icon {
position: relative;
box-sizing: border-box;
&--half {
position: absolute;
top: 0;
left: 0;
display: inline-block;
overflow: hidden;
width: 50%;
}
}
}
}
</style>
@@ -0,0 +1,401 @@
<template>
<view class="tn-scroll-view-class tn-scroll-view">
<scroll-view
class="scroll-view"
:style="[scrollViewStyle]"
scroll-y
scroll-anchoring
enable-back-to-top
:throttle="false"
:scroll-top="scrollTop"
:lower-threshold="lowerThreshold"
@scroll="handleScroll"
@touchend="handleTouchEnd"
@touchmove.prevent.stop="handleTouchMove"
@touchstart="handleTouchStart"
@scrolltolower="handleScrollTolower"
>
<view class="scroll__content" :style="[scrollContentStyle]">
<view class="scroll__pull-down">
<slot name="pulldown">
<view class="scroll__refresh" :style="[refreshStyle]">
<view><tn-loading :animation="refreshing"></tn-loading></view>
<view class="scroll__refresh--text" :style="[refreshTextStyle]">{{ refreshStateText }}</view>
</view>
</slot>
</view>
<view :id="elScrollDataId" class="scroll__data">
<slot></slot>
</view>
</view>
</scroll-view>
</view>
</template>
<script>
import componentsColor from '../../libs/mixin/components_color.js'
export default {
name: 'tn-scroll-view',
mixins: [ componentsColor ],
props: {
// H5
h5NavHeight: {
type: Number,
default: 45
},
//
customNavHeight: {
type: Number,
default: 0
},
//
offsetTop: {
type: Number,
default: 0
},
//
offsetBottom: {
type: Number,
default: 0
},
// ()
height: {
type: Number,
default: null
},
//
disabled: {
type: Boolean,
default: false
},
//
pullDownDisabled: {
type: Boolean,
default: false
},
//
pullDownSpeed: {
type: Number,
default: 0.5
},
//
refreshDelayed: {
type: Number,
default: 800
},
//
refreshFinishDelayed: {
type: Number,
default: 800
},
//
refresherThreshold: {
type: Number,
default: 70
},
//
lowerThreshold: {
type: Number,
default: 40
},
//
refreshState: {
type: Boolean,
default: false
},
//
refreshingText: {
type: String,
default: '正在刷新'
},
//
refreshSuccessText: {
type: String,
default: '刷新成功'
},
//
pulldownText: {
type: String,
default: '下拉刷新'
},
//
pulldownFinishText: {
type: String,
default: '松开刷新'
}
},
data() {
return {
// id
elScrollDataId: '',
//
systemInfo: {
height: 0,
statusBarHeight: 0
},
//
scrollTop: 0,
//
scrollDataTop: -1,
//
scrollDataOffsetTop: -1,
//
scrollViewHeight: 0,
//
currentScrollTop: 0,
// Y
currentTouchStartY: 0,
//
refreshStateText: '下拉刷新',
//
refreshing: false,
//
refreshFinish: false,
//
pulldowning: false,
//
pullDownHeight: 0,
//
showPullDown: false
}
},
computed: {
scrollViewStyle() {
let style = {}
style.height = this.scrollViewHeight + 'px'
if (!this.backgroundColorClass) {
style.backgroundColor = this.backgroundColorStyle
}
return style
},
scrollContentStyle() {
let style = {}
style.transform = this.showPullDown ? `translateY(${this.pullDownHeight}px)` : `translateY(0px)`
style.transition = this.pulldowning ? `transform 100ms ease-out` : `transform 500ms cubic-bezier(0.19,1.64,0.42,0.72)`
return style
},
refreshStyle() {
let style = {}
style.opacity = this.showPullDown ? 1 : 0
return style
},
refreshTextStyle() {
let style = {}
if (!this.fontColorClass) {
style.color = this.fontColorStyle
}
return style
},
loadTextStyle() {
let style = {}
if (!this.fontColorClass) {
style.color = this.fontColorStyle
}
return style
}
},
watch: {
refreshState(nVal, oVal) {
if (!nVal) {
if (this.showPullDown) {
//
this.pulldowning = false
//
this.showPullDown = false
//
this.refreshing = false
}
}
}
},
created() {
this.elScrollDataId = this.$tn.uuid()
this.getSystemInfo()
},
mounted() {
this.$nextTick(() => {
this.init()
})
},
methods: {
//
init() {
this.refreshStateText = this.pulldownText
// scrollView
this.updateScrollViewInfo()
},
//
getSystemInfo() {
const systemInfo = uni.getSystemInfoSync()
this.systemInfo.height = systemInfo.safeArea.height
this.systemInfo.statusBarHeight = systemInfo.statusBarHeight
},
// scrollView
updateScrollViewInfo() {
if (this.height) {
this.scrollViewHeight = this.height
} else {
// scrollView
// console.log(this.systemInfo, this.offsetTop, this.customNavHeight);
// #ifdef H5
this.scrollViewHeight = this.systemInfo.height - (
this.offsetTop +
(this.customNavHeight ? this.customNavHeight : this.h5NavHeight) +
this.offsetBottom)
this.scrollDataOffsetTop = this.offsetTop + (this.customNavHeight ? this.customNavHeight : this.h5NavHeight)
// #endif
// #ifndef H5
this.scrollViewHeight = this.systemInfo.height - (
this.offsetTop +
this.systemInfo.statusBarHeight +
this.offsetBottom)
this.scrollDataOffsetTop = this.offsetTop + this.systemInfo.statusBarHeight
// #endif
}
},
// scrollView
async getScrollDataInfo() {
const scrollInfo = await this._tGetRect(`#${this.elScrollDataId}`)
this.scrollDataTop = scrollInfo.top
},
//
handleScrollTolower(e) {
if (this.pullUpDisabled) return
this.$emit('scrolltolower', e)
},
//
handleScroll(e) {
this.currentScrollTop = e.detail.scrollTop
this.$emit('scroll', e.detail)
},
//
handleTouchStart(e) {
if (this.disabled) return
this.currentTouchStartY = e.touches[0].clientY
this.getScrollDataInfo()
this.$emit('touchStart', e)
},
//
handleTouchMove(e) {
if (this.disabled) return
if (this.currentScrollTop == 0 && e.touches[0].clientY >= this.currentTouchStartY) {
//
const moveOffset = this.scrollDataTop > 0 ?
(this.scrollDataOffsetTop - this.scrollDataTop) :
(Math.abs(this.scrollDataTop) + this.scrollDataOffsetTop)
this.pulldowning = true
this.showPullDown = true
let pullDownDistance = ((e.touches[0].clientY - this.currentTouchStartY) - moveOffset) * this.pullDownSpeed
this.pullDownHeight = pullDownDistance
// this.pullDownHeight = pullDownDistance > this.refresherThreshold ? this.refresherThreshold : pullDownDistance
this.refreshStateText = this.pullDownHeight >= this.refresherThreshold ? this.pulldownFinishText : this.pulldownText
if (pullDownDistance > this.refresherThreshold) {
this.$emit('refreshReady')
}
}
this.$emit('touchMove', e)
},
//
handleTouchEnd(e) {
if (this.disabled) return
//
if (this.showPullDown) {
//
if (this.pullDownHeight < this.refresherThreshold) {
//
this.pulldowning = false
//
this.pullDownHeight = 0
//
this.showPullDown = false
//
this.$emit('refreshStop')
} else {
this.pullDownHeight = this.pullDownHeight > this.refresherThreshold ? this.refresherThreshold : this.pullDownHeight
this.refresh()
}
}
//
this.$emit('touchEnd', e)
},
//
refresh() {
//
this.refreshFinish = false
//
this.refreshing = true
//
this.refreshStateText = this.refreshingText
// refresh
setTimeout(() => {
this.$emit('refresh')
}, this.refreshDelayed)
},
}
}
</script>
<style lang="scss" scoped>
.tn-scroll-view {
.scroll-view {
position: relative;
touch-action: none;
.scroll__content {
display: flex;
will-change: transform;
flex-direction: column;
.scroll {
&__pull-down {
position: absolute;
left: 0;
width: 100%;
display: flex;
padding: 30rpx 0;
align-items: center;
justify-content: center;
transform: translateY(-100%);
}
&__refresh {
display: flex;
align-items: center;
justify-content: center;
&--text {
margin-left: 10rpx;
}
}
&__data {
}
&__pull-up {
position: absolute;
left: 0;
bottom: 0;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
transform: translateY(100%);
}
&__load {
padding: 20rpx 0;
display: flex;
align-items: center;
justify-content: center;
&--text {
margin-left: 10rpx;
}
}
}
}
}
}
</style>
+75 -13
View File
@@ -30,7 +30,14 @@
>{{ confirmText }}</view>
</view>
<!-- 列表内容 -->
<view class="tn-select__content__body">
<view v-if="searchShow && mode==='single'">
<view class="tn-flex tn-select__content__body__search">
<view class="tn-icon-search tn-padding-sm" ></view>
<input class="tn-select__content__body__search__input" :placeholder="searchPlaceholder" maxlength="255" confirm-type="search" @input="searchInput" @confirm="search" >
</view>
</view>
<picker-view
class="tn-select__content__body__view"
:value="defaultSelector"
@@ -38,6 +45,7 @@
@pickend="pickEnd"
@change="columnChange"
>
<picker-view-column v-for="(item, index) in columnData" :key="index">
<view class="tn-select__content__body__item" v-for="(sub_item, sub_index) in item" :key="sub_index">
<view class="tn-text-ellipsis">
@@ -134,6 +142,16 @@
zIndex: {
type: Number,
default: 0
},
// ()
searchShow:{
type:Boolean,
default:true
},
//placeholder
searchPlaceholder:{
type:String,
default:'搜索'
}
},
computed: {
@@ -154,7 +172,8 @@
// index
lastSelectIndex: [],
//
columnNum: 0
columnNum: 0,
}
},
watch: {
@@ -167,6 +186,33 @@
}
},
methods: {
//
searchInput(e){
console.log(e.detail.value);
this.searchResult(e.detail.value)
},
//
search(e){
console.log(e.detail.value)
this.searchResult(e.detail.value)
},
//
searchResult(value) {
let result = [];
// console.log(this.list)
let data = this.list.filter(item => item.label.indexOf(value) > -1);
if (data.length > 0) {
result.push(data
);
}
// console.log(result)
this.columnData = result;
this.selectValue=[]
if (this.columnData.length>0){
this.setSelectValue()
}
},
//
pickStart() {
// #ifdef MP-WEIXIN
@@ -248,12 +294,13 @@
if (tmp && tmp.extra) data.extra = tmp.extra
this.selectValue.push(data)
}
// console.log("",this.selectValue)
},
//
columnChange(event) {
let index = null
let columnIndex = event.detail.value
this.selectValue = []
if (this.mode === 'multi-auto') {
//
@@ -314,12 +361,12 @@
</script>
<style lang="scss" scoped>
.tn-select {
&__content {
position: relative;
&__header {
position: relative;
display: flex;
@@ -332,38 +379,53 @@
box-sizing: border-box;
font-size: 30rpx;
background-color: #FFFFFF;
&__btn {
padding: 16rpx;
box-sizing: border-box;
text-align: center;
text-decoration: none;
}
&__title {
color: $tn-font-color;
}
&--cancel {
color: $tn-font-sub-color;
}
&--confirm {
color: $tn-main-color;
}
}
&__body {
width: 100%;
height: 500rpx;
overflow: hidden;
background-color: #FFFFFF;
&__search{
z-index: 5;
align-items: center;
border-radius: 19px;
background: #f8f8f8;
width: calc(100% - 60rpx);
margin: 0 auto;
position: relative;
top: 15px;
&__input{
width: 600rpx;
}
}
&__view {
height: 100%;
box-sizing: border-box;
}
&__item {
display: flex;
flex-direction: row;
@@ -375,6 +437,6 @@
}
}
}
}
</style>
@@ -0,0 +1,657 @@
function setTimeout(instance, cb, time) {
if (time > 0) {
var s = getDate().getTime()
var fn = function () {
if (getDate().getTime() - s > time) {
cb && cb()
} else
instance.requestAnimationFrame(fn)
}
fn()
}
else
cb && cb()
}
// 判断触摸的移动方向
function decideSwiperDirection(startTouches, currentTouches, direction) {
// 震动偏移容差
var toleranceShake = 30
// 移动容差
var toleranceTranslate = 10
if (direction === 'horizontal') {
// 水平方向移动
if (Math.abs(currentTouches.y - startTouches.y) <= toleranceShake) {
// console.log(currentTouches.x, startTouches.x);
if (Math.abs(currentTouches.x - startTouches.x) > toleranceTranslate) {
if (currentTouches.x - startTouches.x > 0) {
return 'right'
} else if (currentTouches.x - startTouches.x < 0) {
return 'left'
}
}
}
} else if (direction === 'vertical') {
// 垂直方向移动
if (Math.abs(currentTouches.x - startTouches.x) <= toleranceShake) {
// console.log(currentTouches.x, startTouches.x);
if (Math.abs(currentTouches.y - startTouches.y) > toleranceTranslate) {
if (currentTouches.y - startTouches.y > 0) {
return 'down'
} else if (currentTouches.y - startTouches.y < 0) {
return 'up'
}
}
}
}
return ''
}
// 更新轮播样式信息
function updateSwiperStyle(currentTouches, instance, state) {
var itemData = state.itemData
var itemsInstance = state.itemsInstance
var list = state.list
var currentIndex = state.currentIndex
var touchRelactive = state.touchRelactive
// console.log(itemAnimationWidth);
if (itemData.direction === 'horizontal') {
// 水平方向
var itemAnimationWidth = state.itemAnimationWidth
// 偏移的x轴距离
var translateX = currentTouches.x - touchRelactive.x
if (currentTouches.x > itemData.windowWidth || currentTouches.x < 0) return
// console.log(translateX);
// 更新其他轮播样式
if (state.direction == 'left') {
// 设置当前激活元素的偏移量
instance.setStyle({
'transform': 'translate3d('+ translateX + 'px, 0px, 0px)',
'z-index': list[currentIndex].zIndex + 1,
'opacity': list[currentIndex].opacity
})
// 移动距离是否超过了指定的容器宽度
if (Math.abs(translateX) > itemAnimationWidth) {
state.itemsInstance.forEach( function(itemInstance, index) {
if (index != currentIndex) {
var preIndex = (index == 0) ? list.length - 1 : index - 1
var distanceRate = (Math.abs(translateX) - itemAnimationWidth) / (itemData.itemWidth - itemAnimationWidth)
var itemTranslateX = list[index].translateX - (list[index].translateX - list[preIndex].translateX) * distanceRate
var itemScale = list[index].scale + (list[preIndex].scale - list[index].scale) * distanceRate
var itemOpacity = list[index].opacity + (list[preIndex].opacity - list[index].opacity) * distanceRate
// console.log(preIndex);
// console.log(list[index]);
// console.log(distanceRate);
// console.log(itemTranslateX);
// console.log(itemScale);
// console.log(itemOpacity);
// console.log('-----------------------------------------------------------');
itemInstance.setStyle({
'transform': 'translate3d(' + itemTranslateX + 'px, 0px, 0px) scale(' + itemScale + ')',
'z-index': list[index].zIndex,
'opacity': itemOpacity
})
}
})
}
} else if (state.direction == 'right') {
var preIndex = (currentIndex == 0) ? list.length - 1 : currentIndex - 1
// 右滑的时候把最底部的取出,并放到最高层级
state.itemsInstance[preIndex].setStyle({
'transform': 'translate3d(-' + (itemData.itemWidth - translateX) + 'px, 0px, 0px) scale(1)',
'z-index': list[currentIndex].zIndex + 1,
'opacity': list[currentIndex].opacity
})
// 当前轮播逐渐缩小
if (Math.abs(translateX) < itemAnimationWidth) {
state.itemsInstance.forEach( function(itemInstance, index) {
if (index != preIndex) {
var replaceIndex = index == list.length - 1 ? 0 : index + 1
var distanceRate = Math.abs(translateX) / itemAnimationWidth
var itemTranslateX = list[index].translateX + (list[replaceIndex].translateX - list[index].translateX) * distanceRate
var itemScale = list[index].scale - (list[index].scale - list[replaceIndex].scale) * distanceRate
var itemOpacity = list[index].opacity - (list[index].opacity - list[replaceIndex].opacity) * distanceRate
// console.log(preIndex);
// console.log(index);
// console.log(replaceIndex);
// console.log(list[index]);
// console.log(list[replaceIndex].translateX - list[index].translateX);
// console.log(distanceRate);
// console.log(itemTranslateX);
// console.log(itemScale);
// console.log('-----------------------------------------------------------');
itemInstance.setStyle({
'transform': 'translate3d(' + itemTranslateX + 'px, 0px, 0px) scale(' + itemScale + ')',
'z-index': list[index].zIndex,
'opacity': itemOpacity
})
}
})
}
}
} else if (itemData.direction === 'vertical') {
// 垂直方向
var itemAnimationHeight = state.itemAnimationHeight
// 偏移的y轴距离
var translateY = currentTouches.y - touchRelactive.y
if (currentTouches.y > itemData.windowHeight || currentTouches.y < 0) return
// console.log(translateX);
// 更新其他轮播样式
if (state.direction == 'up') {
// 设置当前激活元素的偏移量
instance.setStyle({
'transform': 'translate3d(0px, '+ translateY + 'px, 0px)',
'z-index': list[currentIndex].zIndex + 1,
'opacity': list[currentIndex].opacity
})
// 移动距离是否超过了指定的容器宽度
if (Math.abs(translateY) > itemAnimationHeight) {
state.itemsInstance.forEach( function(itemInstance, index) {
if (index != currentIndex) {
var preIndex = (index == 0) ? list.length - 1 : index - 1
var distanceRate = (Math.abs(translateY) - itemAnimationHeight) / (itemData.itemHeight - itemAnimationHeight)
var itemTranslateY = list[index].translateY - (list[index].translateY - list[preIndex].translateY) * distanceRate
var itemScale = list[index].scale + (list[preIndex].scale - list[index].scale) * distanceRate
var itemOpacity = list[index].opacity + (list[preIndex].opacity - list[index].opacity) * distanceRate
// console.log(preIndex);
// console.log(list[index]);
// console.log(distanceRate);
// console.log(itemTranslateX);
// console.log(itemScale);
// console.log('-----------------------------------------------------------');
itemInstance.setStyle({
'transform': 'translate3d(0px, ' + itemTranslateY + 'px, 0px) scale(' + itemScale + ')',
'z-index': list[index].zIndex,
'opacity': itemOpacity
})
}
})
}
} else if (state.direction == 'down') {
var preIndex = (currentIndex == 0) ? list.length - 1 : currentIndex - 1
// 下滑的时候把最底部的取出,并放到最高层级
state.itemsInstance[preIndex].setStyle({
'transform': 'translate3d(0px, -' + (itemData.itemHeight - translateY) + 'px, 0px) scale(1)',
'z-index': list[currentIndex].zIndex + 1,
'opacity': list[currentIndex].opacity
})
// 当前轮播逐渐缩小
if (Math.abs(translateY) < itemAnimationHeight) {
state.itemsInstance.forEach( function(itemInstance, index) {
if (index != preIndex) {
var replaceIndex = index == list.length - 1 ? 0 : index + 1
var distanceRate = Math.abs(translateY) / itemAnimationHeight
var itemTranslateY = list[index].translateY + (list[replaceIndex].translateY - list[index].translateY) * distanceRate
var itemScale = list[index].scale - (list[index].scale - list[replaceIndex].scale) * distanceRate
var itemOpacity = list[index].opacity - (list[index].opacity - list[replaceIndex].opacity) * distanceRate
// console.log(preIndex);
// console.log(index);
// console.log(replaceIndex);
// console.log(list[index]);
// console.log(list[replaceIndex].translateX - list[index].translateX);
// console.log(distanceRate);
// console.log(itemTranslateX);
// console.log(itemScale);
// console.log('-----------------------------------------------------------');
itemInstance.setStyle({
'transform': 'translate3d(0px, ' + itemTranslateY + 'px, 0px) scale(' + itemScale + ')',
'z-index': list[index].zIndex,
'opacity': itemOpacity
})
}
})
}
}
}
}
// 更新当前轮播序号
function updateCurrentSwiperIndex(index, ownerInstance, state) {
state.currentIndex = index
ownerInstance.callMethod('changeSwiperIndex', {
index: index
})
}
// 切换到下一个轮播
function switchNextSwiper(newIndex, touches, instance, state) {
var currentIndex = state.currentIndex
var list = state.list
var direction = state.itemData.direction
var touchRelactive = state.touchRelactive || {x: 0, y: 0}
// 已经完成轮播切换
var currentListItemData = JSON.parse(JSON.stringify(list))
if (direction === 'horizontal') {
// 水平方向移动
var itemWidth = state.itemData.itemWidth
// 当前轮播移动到最左边
instance.setStyle({
'transform': 'translate3d(-'+ itemWidth + 'px, 0px, 0px) scale(1)',
'z-index': list[currentIndex].zIndex + 1,
'opacity': list[currentIndex].opacity
})
// 计算当前移动需要的剩余时间
var time = Math.floor((itemWidth - Math.abs(touches.pageX - touchRelactive.x)) / itemWidth * 250)
setTimeout(instance, function() {
for (var i = list.length - 1; i >= 0; i--) {
var replaceIndex = i - 1 < 0 ? list.length - 1 : i - 1
// console.log(i);
// console.log(replaceIndex);
state.itemsInstance[i].setStyle({
'transform': 'translate3d('+ currentListItemData[replaceIndex].translateX + 'px, 0px, 0px) scale(' + currentListItemData[replaceIndex].scale + ')',
'z-index': currentListItemData[replaceIndex].zIndex,
'opacity': currentListItemData[replaceIndex].opacity
})
state.list[i] = currentListItemData[replaceIndex]
}
}, time)
} else if (direction === 'vertical') {
// 垂直方向移动
var itemHeight = state.itemData.itemHeight
// 当前轮播移动到最上边
instance.setStyle({
'transform': 'translate3d(0px, -'+ itemHeight + 'px, 0px) scale(1)',
'z-index': list[currentIndex].zIndex + 1,
'opacity': list[currentIndex].opacity
})
// 计算当前移动需要的剩余时间
var time = Math.floor((itemHeight - Math.abs(touches.pageY - touchRelactive.y)) / itemHeight * 250)
setTimeout(instance, function() {
for (var i = list.length - 1; i >= 0; i--) {
var replaceIndex = i - 1 < 0 ? list.length - 1 : i - 1
// console.log(i);
// console.log(replaceIndex);
state.itemsInstance[i].setStyle({
'transform': 'translate3d(0px, '+ currentListItemData[replaceIndex].translateY + 'px, 0px) scale(' + currentListItemData[replaceIndex].scale + ')',
'z-index': currentListItemData[replaceIndex].zIndex,
'opacity': currentListItemData[replaceIndex].opacity
})
state.list[i] = currentListItemData[replaceIndex]
}
}, time)
}
}
// 切换到上一个轮播
function switchPrevSwiper(newIndex, touches, instance, state) {
var currentIndex = state.currentIndex
var list = state.list
var direction = state.itemData.direction
var touchRelactive = state.touchRelactive || {x: 0, y: 0}
var currentListItemData = JSON.parse(JSON.stringify(list))
if (direction === 'horizontal') {
// 水平方向移动
var itemWidth = state.itemData.itemWidth
// 当前上一个轮播移动到正常位置
state.itemsInstance[newIndex].setStyle({
'transform': 'translate3d(0px, 0px, 0px) scale(1)',
'z-index': list[currentIndex].zIndex + 1,
'opacity': list[currentIndex].opacity
})
// 计算当前移动需要的剩余时间
var time = Math.floor((itemWidth - Math.abs(touches.pageX - touchRelactive.x)) / itemWidth * 250)
// 更新除当前上一个轮播外的其他轮播,向后移动一个层级
// 更新列表位置相关数据
setTimeout(instance, function() {
for (var i = 0; i < list.length; i++) {
var replaceIndex = (i + 1 > list.length - 1) ? 0 : i + 1
state.itemsInstance[i].setStyle({
'transform': 'translate3d('+ currentListItemData[replaceIndex].translateX + 'px, 0px, 0px) scale(' + currentListItemData[replaceIndex].scale + ')',
'z-index': currentListItemData[replaceIndex].zIndex,
'opacity': currentListItemData[replaceIndex].opacity
})
state.list[i] = currentListItemData[replaceIndex]
}
}, time)
} else if (direction === 'vertical') {
// 垂直方向移动
var itemHeight = state.itemData.itemHeight
// 当前上一个轮播移动到正常位置
state.itemsInstance[newIndex].setStyle({
'transform': 'translate3d(0px, 0px, 0px) scale(1)',
'z-index': list[currentIndex].zIndex + 1,
'opacity': list[currentIndex].opacity
})
// 计算当前移动需要的剩余时间
var time = Math.floor((itemHeight - Math.abs(touches.pageY - touchRelactive.y)) / itemHeight * 250)
// 更新除当前上一个轮播外的其他轮播,向后移动一个层级
// 更新列表位置相关数据
setTimeout(instance, function() {
for (var i = 0; i < list.length; i++) {
var replaceIndex = (i + 1 > list.length - 1) ? 0 : i + 1
state.itemsInstance[i].setStyle({
'transform': 'translate3d(0px, '+ currentListItemData[replaceIndex].translateY + 'px, 0px) scale(' + currentListItemData[replaceIndex].scale + ')',
'z-index': currentListItemData[replaceIndex].zIndex,
'opacity': currentListItemData[replaceIndex].opacity
})
state.list[i] = currentListItemData[replaceIndex]
}
}, time)
}
}
// 反转动画
function toggleSwiperAnimation(state, add) {
if (!state.itemsInstance) return
if (add === true) {
state.itemsInstance.forEach(function(item, index) {
if (!item.hasClass('tn-stack-swiper__item__transition')) {
item.addClass('tn-stack-swiper__item__transition')
}
})
} else {
state.itemsInstance.forEach(function(item, index) {
if (item.hasClass('tn-stack-swiper__item__transition')) {
item.removeClass('tn-stack-swiper__item__transition')
}
})
}
}
// 更新数据
var itemDataObserver = function (newVal, oldVal, ownerInstance, instance) {
var state = ownerInstance.getState()
state.itemData = newVal
}
// 列表初始化
var listObserver = function(newVal, oldVal, ownerInstance, instance) {
var state = ownerInstance.getState()
var itemData = state.itemData
state.itemsInstance = ownerInstance.selectAllComponents('.tn-stack-swiper__item')
state.list = newVal || []
state.list.forEach(function(item, index) {
var itemInstance = state.itemsInstance[index]
if (item && itemInstance) {
if (itemData.direction === 'horizontal') {
itemInstance.setStyle({
'transform': 'translate3d('+ item.translateX + 'px, 0px, 0px) scale(' + item.scale + ')',
'z-index': item.zIndex,
'opacity': item.opacity
})
} else if (itemData.direction === 'vertical') {
itemInstance.setStyle({
'transform': 'translate3d(0px, '+ item.translateY + 'px, 0px) scale(' + item.scale + ')',
'z-index': item.zIndex,
'opacity': item.opacity
})
}
}
})
}
// 切换轮播位置
var swiperIndexChange = function(newVal, oldVal, ownerInstance, instance) {
var state = ownerInstance.getState()
// console.log(newVal);
// ownerInstance.callMethod('printLog', newVal)
// console.log(oldVal);
// ownerInstance.callMethod('printLog', oldVal)
// 排除第一次初始化和手动切换的情况
if (oldVal < 0 || typeof oldVal == 'undefined' || state.currentIndex == newVal) {
if (oldVal < 0 || typeof oldVal == 'undefined') {
state.currentIndex = 0
}
return
}
state.currentIndex = newVal
// console.log(state.currentIndex);
if (newVal > oldVal || (oldVal == state.list.length - 1 && newVal == 0)) {
// console.log("next");
// state.itemsInstance.forEach(function(item, index) {
// item.addClass("tn-stack-swiper__item__transition")
// })
switchNextSwiper(newVal, {
pageX: 0
}, state.itemsInstance[oldVal], state)
} else if (newVal < oldVal || (oldVal == 0 && newVal == state.list.length - 1)) {
// console.log("prev");
}
}
// 自动轮播切换状态
var autoplayFlagChange = function(newVal, oldVal, ownerInstance, instance) {
var state = ownerInstance.getState()
if (newVal === true) {
toggleSwiperAnimation(state, true)
} else {
toggleSwiperAnimation(state, false)
}
}
// 开始触摸
var touchStart = function (event, ownerInstance) {
// console.log('touchStart');
var instance = event.instance
var dataset = instance.getDataset()
var state = ownerInstance.getState()
var itemData = state.itemData
// 判断是否为为当前显示的轮播
if (dataset.index != state.currentIndex) return
var touches = event.changedTouches[0]
if (!touches) return
// 记录当前滑动开始的xy坐标
state.touchRelactive = {
x: touches.pageX,
y: touches.pageY
}
// 记录触摸id,用于处理多指的情况
state.touchId = touches.identifier
if (itemData.direction === 'horizontal') {
// 水平方向移动
// 设置左右滑动时相对偏移距离
state.itemAnimationWidth = itemData.itemWidth * (dataset.switchrate / 100)
} else if (itemData.direction === 'vertical') {
// 垂直方向移动
// 设置上下滑动时相对偏移距离
state.itemAnimationHeight = itemData.itemHeight * (dataset.switchrate / 100)
}
// 移除运动动画时间
toggleSwiperAnimation(state, false)
// 标记开始触摸
state.touching = true
ownerInstance.callMethod('changeTouchState', {
touching: true
})
// 停止执行自动轮播
ownerInstance.callMethod('clearAutoPlayTimer')
}
// 开始移动
var touchMove = function (event, ownerInstance) {
// console.log('touchMove');
var instance = event.instance
var dataset = instance.getDataset()
var state = ownerInstance.getState()
var itemData = state.itemData
// 判断是否为为当前显示的轮播
if (dataset.index != state.currentIndex) return
// 还没开始触摸直接返回
if (!state.touching) return
var touches = event.changedTouches[0]
if (!touches) return
// 判断是否为同一个触摸点
if (state.touchId != touches.identifier) return
var currentTouchRelactive = {
x: touches.pageX,
y: touches.pageY
}
// 是否已经确定了移动方向
if (!state.direction) {
state.direction = decideSwiperDirection(state.touchRelactive, currentTouchRelactive, itemData.direction)
}
// console.log(decideSwiperDirection(state.touchRelactive, currentTouchRelactive));
updateSwiperStyle(currentTouchRelactive, instance, state)
}
// 移动结束
var touchEnd = function (event, ownerInstance) {
// console.log('touchEnd');
var instance = event.instance
var dataset = instance.getDataset()
var state = ownerInstance.getState()
var itemData = state.itemData
var list = state.list
var touchRelactive = state.touchRelactive
// 判断是否为为当前显示的轮播
if (dataset.index != state.currentIndex) return
// 还没开始触摸直接返回
if (!state.touching) return
var touches = event.changedTouches[0]
if (!touches) return
// 判断是否为同一个触摸点
if (state.touchId != touches.identifier) return
// 添加运动动画时间
toggleSwiperAnimation(state, true)
if (itemData.direction === 'horizontal') {
// 水平方向移动
var itemAnimationWidth = state.itemAnimationWidth
// 判断时左滑还是右滑
// 判断是否超过自动滚动到下一页还是回滚
if (state.direction == 'left') {
if (Math.abs(touches.pageX - touchRelactive.x) < itemAnimationWidth) {
list.forEach(function(item, index) {
var itemInstance = state.itemsInstance[index]
if (item && itemInstance) {
itemInstance.setStyle({
'transform': 'translate3d('+ item.translateX + 'px, 0px, 0px) scale(' + item.scale + ')',
'z-index': item.zIndex
})
}
})
} else {
var newIndex = state.currentIndex + 1 > list.length - 1 ? 0 : state.currentIndex + 1
switchNextSwiper(newIndex, touches, instance, state)
updateCurrentSwiperIndex(newIndex, ownerInstance, state)
}
} else if (state.direction == 'right') {
if (Math.abs(touches.pageX - touchRelactive.x) < itemAnimationWidth) {
// 滑动显示图片回滚
var preIndex = (state.currentIndex == 0) ? list.length - 1 : state.currentIndex - 1
state.itemsInstance[preIndex].setStyle({
'transform': 'translate3d(-' + itemData.itemWidth + 'px, 0px, 0px) scale(1)',
'z-index': list[state.currentIndex].zIndex + 1,
'opacity': list[state.currentIndex].opacity
})
list.forEach(function(item, index) {
var itemInstance = state.itemsInstance[index]
if (item && itemInstance) {
itemInstance.setStyle({
'transform': 'translate3d('+ item.translateX + 'px, 0px, 0px) scale(' + item.scale + ')',
'z-index': item.zIndex,
'opacity': item.opacity
})
}
})
} else {
var newIndex = (state.currentIndex - 1 < 0) ? list.length - 1 : state.currentIndex - 1
switchPrevSwiper(newIndex, touches, instance, state)
updateCurrentSwiperIndex(newIndex, ownerInstance, state)
}
}
} else if (itemData.direction === 'vertical') {
// 垂直方向移动
var itemAnimationHeight = state.itemAnimationHeight
// 判断时上滑还是下滑
// 判断是否超过自动滚动到下一页还是回滚
if (state.direction == 'up') {
if (Math.abs(touches.pageY - touchRelactive.y) < itemAnimationHeight) {
list.forEach(function(item, index) {
var itemInstance = state.itemsInstance[index]
if (item && itemInstance) {
itemInstance.setStyle({
'transform': 'translate3d(0px, '+ item.translateY + 'px, 0px) scale(' + item.scale + ')',
'z-index': item.zIndex,
'opacity': item.opacity
})
}
})
} else {
var newIndex = state.currentIndex + 1 > list.length - 1 ? 0 : state.currentIndex + 1
switchNextSwiper(newIndex, touches, instance, state)
updateCurrentSwiperIndex(newIndex, ownerInstance, state)
}
} else if (state.direction == 'down') {
if (Math.abs(touches.pageY - touchRelactive.y) < itemAnimationHeight) {
// 滑动显示图片回滚
var preIndex = (state.currentIndex == 0) ? list.length - 1 : state.currentIndex - 1
state.itemsInstance[preIndex].setStyle({
'transform': 'translate3d(0px, -' + itemData.itemHeight + 'px, 0px) scale(1)',
'z-index': list[state.currentIndex].zIndex + 1,
'opacity': list[state.currentIndex].opacity
})
list.forEach(function(item, index) {
var itemInstance = state.itemsInstance[index]
if (item && itemInstance) {
itemInstance.setStyle({
'transform': 'translate3d(0px, '+ item.translateY + 'px, 0px) scale(' + item.scale + ')',
'z-index': item.zIndex,
'opacity': item.opacity
})
}
})
} else {
var newIndex = (state.currentIndex - 1 < 0) ? list.length - 1 : state.currentIndex - 1
switchPrevSwiper(newIndex, touches, instance, state)
updateCurrentSwiperIndex(newIndex, ownerInstance, state)
}
}
}
// 清除对应的标志位
state.touchRelactive = null
state.touching = false
state.direction = null
state.touchId = null
ownerInstance.callMethod('changeTouchState', {
touching: false
})
// 重新开始执行自动轮播
ownerInstance.callMethod('setAutoPlay')
}
module.exports = {
itemDataObserver: itemDataObserver,
listObserver: listObserver,
swiperIndexChange: swiperIndexChange,
autoplayFlagChange: autoplayFlagChange,
touchStart: touchStart,
touchMove: touchMove,
touchEnd: touchEnd
}
@@ -0,0 +1,657 @@
function setTimeout(instance, cb, time) {
if (time > 0) {
var s = getDate().getTime()
var fn = function () {
if (getDate().getTime() - s > time) {
cb && cb()
} else
instance.requestAnimationFrame(fn)
}
fn()
}
else
cb && cb()
}
// 判断触摸的移动方向
function decideSwiperDirection(startTouches, currentTouches, direction) {
// 震动偏移容差
var toleranceShake = 30
// 移动容差
var toleranceTranslate = 10
if (direction === 'horizontal') {
// 水平方向移动
if (Math.abs(currentTouches.y - startTouches.y) <= toleranceShake) {
// console.log(currentTouches.x, startTouches.x);
if (Math.abs(currentTouches.x - startTouches.x) > toleranceTranslate) {
if (currentTouches.x - startTouches.x > 0) {
return 'right'
} else if (currentTouches.x - startTouches.x < 0) {
return 'left'
}
}
}
} else if (direction === 'vertical') {
// 垂直方向移动
if (Math.abs(currentTouches.x - startTouches.x) <= toleranceShake) {
// console.log(currentTouches.x, startTouches.x);
if (Math.abs(currentTouches.y - startTouches.y) > toleranceTranslate) {
if (currentTouches.y - startTouches.y > 0) {
return 'down'
} else if (currentTouches.y - startTouches.y < 0) {
return 'up'
}
}
}
}
return ''
}
// 更新轮播样式信息
function updateSwiperStyle(currentTouches, instance, state) {
var itemData = state.itemData
var itemsInstance = state.itemsInstance
var list = state.list
var currentIndex = state.currentIndex
var touchRelactive = state.touchRelactive
// console.log(itemAnimationWidth);
if (itemData.direction === 'horizontal') {
// 水平方向
var itemAnimationWidth = state.itemAnimationWidth
// 偏移的x轴距离
var translateX = currentTouches.x - touchRelactive.x
if (currentTouches.x > itemData.windowWidth || currentTouches.x < 0) return
// console.log(translateX);
// 更新其他轮播样式
if (state.direction == 'left') {
// 设置当前激活元素的偏移量
instance.setStyle({
'transform': 'translate3d('+ translateX + 'px, 0px, 0px)',
'z-index': list[currentIndex].zIndex + 1,
'opacity': list[currentIndex].opacity
})
// 移动距离是否超过了指定的容器宽度
if (Math.abs(translateX) > itemAnimationWidth) {
state.itemsInstance.forEach( function(itemInstance, index) {
if (index != currentIndex) {
var preIndex = (index == 0) ? list.length - 1 : index - 1
var distanceRate = (Math.abs(translateX) - itemAnimationWidth) / (itemData.itemWidth - itemAnimationWidth)
var itemTranslateX = list[index].translateX - (list[index].translateX - list[preIndex].translateX) * distanceRate
var itemScale = list[index].scale + (list[preIndex].scale - list[index].scale) * distanceRate
var itemOpacity = list[index].opacity + (list[preIndex].opacity - list[index].opacity) * distanceRate
// console.log(preIndex);
// console.log(list[index]);
// console.log(distanceRate);
// console.log(itemTranslateX);
// console.log(itemScale);
// console.log(itemOpacity);
// console.log('-----------------------------------------------------------');
itemInstance.setStyle({
'transform': 'translate3d(' + itemTranslateX + 'px, 0px, 0px) scale(' + itemScale + ')',
'z-index': list[index].zIndex,
'opacity': itemOpacity
})
}
})
}
} else if (state.direction == 'right') {
var preIndex = (currentIndex == 0) ? list.length - 1 : currentIndex - 1
// 右滑的时候把最底部的取出,并放到最高层级
state.itemsInstance[preIndex].setStyle({
'transform': 'translate3d(-' + (itemData.itemWidth - translateX) + 'px, 0px, 0px) scale(1)',
'z-index': list[currentIndex].zIndex + 1,
'opacity': list[currentIndex].opacity
})
// 当前轮播逐渐缩小
if (Math.abs(translateX) < itemAnimationWidth) {
state.itemsInstance.forEach( function(itemInstance, index) {
if (index != preIndex) {
var replaceIndex = index == list.length - 1 ? 0 : index + 1
var distanceRate = Math.abs(translateX) / itemAnimationWidth
var itemTranslateX = list[index].translateX + (list[replaceIndex].translateX - list[index].translateX) * distanceRate
var itemScale = list[index].scale - (list[index].scale - list[replaceIndex].scale) * distanceRate
var itemOpacity = list[index].opacity - (list[index].opacity - list[replaceIndex].opacity) * distanceRate
// console.log(preIndex);
// console.log(index);
// console.log(replaceIndex);
// console.log(list[index]);
// console.log(list[replaceIndex].translateX - list[index].translateX);
// console.log(distanceRate);
// console.log(itemTranslateX);
// console.log(itemScale);
// console.log('-----------------------------------------------------------');
itemInstance.setStyle({
'transform': 'translate3d(' + itemTranslateX + 'px, 0px, 0px) scale(' + itemScale + ')',
'z-index': list[index].zIndex,
'opacity': itemOpacity
})
}
})
}
}
} else if (itemData.direction === 'vertical') {
// 垂直方向
var itemAnimationHeight = state.itemAnimationHeight
// 偏移的y轴距离
var translateY = currentTouches.y - touchRelactive.y
if (currentTouches.y > itemData.windowHeight || currentTouches.y < 0) return
// console.log(translateX);
// 更新其他轮播样式
if (state.direction == 'up') {
// 设置当前激活元素的偏移量
instance.setStyle({
'transform': 'translate3d(0px, '+ translateY + 'px, 0px)',
'z-index': list[currentIndex].zIndex + 1,
'opacity': list[currentIndex].opacity
})
// 移动距离是否超过了指定的容器宽度
if (Math.abs(translateY) > itemAnimationHeight) {
state.itemsInstance.forEach( function(itemInstance, index) {
if (index != currentIndex) {
var preIndex = (index == 0) ? list.length - 1 : index - 1
var distanceRate = (Math.abs(translateY) - itemAnimationHeight) / (itemData.itemHeight - itemAnimationHeight)
var itemTranslateY = list[index].translateY - (list[index].translateY - list[preIndex].translateY) * distanceRate
var itemScale = list[index].scale + (list[preIndex].scale - list[index].scale) * distanceRate
var itemOpacity = list[index].opacity + (list[preIndex].opacity - list[index].opacity) * distanceRate
// console.log(preIndex);
// console.log(list[index]);
// console.log(distanceRate);
// console.log(itemTranslateX);
// console.log(itemScale);
// console.log('-----------------------------------------------------------');
itemInstance.setStyle({
'transform': 'translate3d(0px, ' + itemTranslateY + 'px, 0px) scale(' + itemScale + ')',
'z-index': list[index].zIndex,
'opacity': itemOpacity
})
}
})
}
} else if (state.direction == 'down') {
var preIndex = (currentIndex == 0) ? list.length - 1 : currentIndex - 1
// 下滑的时候把最底部的取出,并放到最高层级
state.itemsInstance[preIndex].setStyle({
'transform': 'translate3d(0px, -' + (itemData.itemHeight - translateY) + 'px, 0px) scale(1)',
'z-index': list[currentIndex].zIndex + 1,
'opacity': list[currentIndex].opacity
})
// 当前轮播逐渐缩小
if (Math.abs(translateY) < itemAnimationHeight) {
state.itemsInstance.forEach( function(itemInstance, index) {
if (index != preIndex) {
var replaceIndex = index == list.length - 1 ? 0 : index + 1
var distanceRate = Math.abs(translateY) / itemAnimationHeight
var itemTranslateY = list[index].translateY + (list[replaceIndex].translateY - list[index].translateY) * distanceRate
var itemScale = list[index].scale - (list[index].scale - list[replaceIndex].scale) * distanceRate
var itemOpacity = list[index].opacity - (list[index].opacity - list[replaceIndex].opacity) * distanceRate
// console.log(preIndex);
// console.log(index);
// console.log(replaceIndex);
// console.log(list[index]);
// console.log(list[replaceIndex].translateX - list[index].translateX);
// console.log(distanceRate);
// console.log(itemTranslateX);
// console.log(itemScale);
// console.log('-----------------------------------------------------------');
itemInstance.setStyle({
'transform': 'translate3d(0px, ' + itemTranslateY + 'px, 0px) scale(' + itemScale + ')',
'z-index': list[index].zIndex,
'opacity': itemOpacity
})
}
})
}
}
}
}
// 更新当前轮播序号
function updateCurrentSwiperIndex(index, ownerInstance, state) {
state.currentIndex = index
ownerInstance.callMethod('changeSwiperIndex', {
index: index
})
}
// 切换到下一个轮播
function switchNextSwiper(newIndex, touches, instance, state) {
var currentIndex = state.currentIndex
var list = state.list
var direction = state.itemData.direction
var touchRelactive = state.touchRelactive || {x: 0, y: 0}
// 已经完成轮播切换
var currentListItemData = JSON.parse(JSON.stringify(list))
if (direction === 'horizontal') {
// 水平方向移动
var itemWidth = state.itemData.itemWidth
// 当前轮播移动到最左边
instance.setStyle({
'transform': 'translate3d(-'+ itemWidth + 'px, 0px, 0px) scale(1)',
'z-index': list[currentIndex].zIndex + 1,
'opacity': list[currentIndex].opacity
})
// 计算当前移动需要的剩余时间
var time = Math.floor((itemWidth - Math.abs(touches.pageX - touchRelactive.x)) / itemWidth * 250)
setTimeout(instance, function() {
for (var i = list.length - 1; i >= 0; i--) {
var replaceIndex = i - 1 < 0 ? list.length - 1 : i - 1
// console.log(i);
// console.log(replaceIndex);
state.itemsInstance[i].setStyle({
'transform': 'translate3d('+ currentListItemData[replaceIndex].translateX + 'px, 0px, 0px) scale(' + currentListItemData[replaceIndex].scale + ')',
'z-index': currentListItemData[replaceIndex].zIndex,
'opacity': currentListItemData[replaceIndex].opacity
})
state.list[i] = currentListItemData[replaceIndex]
}
}, time)
} else if (direction === 'vertical') {
// 垂直方向移动
var itemHeight = state.itemData.itemHeight
// 当前轮播移动到最上边
instance.setStyle({
'transform': 'translate3d(0px, -'+ itemHeight + 'px, 0px) scale(1)',
'z-index': list[currentIndex].zIndex + 1,
'opacity': list[currentIndex].opacity
})
// 计算当前移动需要的剩余时间
var time = Math.floor((itemHeight - Math.abs(touches.pageY - touchRelactive.y)) / itemHeight * 250)
setTimeout(instance, function() {
for (var i = list.length - 1; i >= 0; i--) {
var replaceIndex = i - 1 < 0 ? list.length - 1 : i - 1
// console.log(i);
// console.log(replaceIndex);
state.itemsInstance[i].setStyle({
'transform': 'translate3d(0px, '+ currentListItemData[replaceIndex].translateY + 'px, 0px) scale(' + currentListItemData[replaceIndex].scale + ')',
'z-index': currentListItemData[replaceIndex].zIndex,
'opacity': currentListItemData[replaceIndex].opacity
})
state.list[i] = currentListItemData[replaceIndex]
}
}, time)
}
}
// 切换到上一个轮播
function switchPrevSwiper(newIndex, touches, instance, state) {
var currentIndex = state.currentIndex
var list = state.list
var direction = state.itemData.direction
var touchRelactive = state.touchRelactive || {x: 0, y: 0}
var currentListItemData = JSON.parse(JSON.stringify(list))
if (direction === 'horizontal') {
// 水平方向移动
var itemWidth = state.itemData.itemWidth
// 当前上一个轮播移动到正常位置
state.itemsInstance[newIndex].setStyle({
'transform': 'translate3d(0px, 0px, 0px) scale(1)',
'z-index': list[currentIndex].zIndex + 1,
'opacity': list[currentIndex].opacity
})
// 计算当前移动需要的剩余时间
var time = Math.floor((itemWidth - Math.abs(touches.pageX - touchRelactive.x)) / itemWidth * 250)
// 更新除当前上一个轮播外的其他轮播,向后移动一个层级
// 更新列表位置相关数据
setTimeout(instance, function() {
for (var i = 0; i < list.length; i++) {
var replaceIndex = (i + 1 > list.length - 1) ? 0 : i + 1
state.itemsInstance[i].setStyle({
'transform': 'translate3d('+ currentListItemData[replaceIndex].translateX + 'px, 0px, 0px) scale(' + currentListItemData[replaceIndex].scale + ')',
'z-index': currentListItemData[replaceIndex].zIndex,
'opacity': currentListItemData[replaceIndex].opacity
})
state.list[i] = currentListItemData[replaceIndex]
}
}, time)
} else if (direction === 'vertical') {
// 垂直方向移动
var itemHeight = state.itemData.itemHeight
// 当前上一个轮播移动到正常位置
state.itemsInstance[newIndex].setStyle({
'transform': 'translate3d(0px, 0px, 0px) scale(1)',
'z-index': list[currentIndex].zIndex + 1,
'opacity': list[currentIndex].opacity
})
// 计算当前移动需要的剩余时间
var time = Math.floor((itemHeight - Math.abs(touches.pageY - touchRelactive.y)) / itemHeight * 250)
// 更新除当前上一个轮播外的其他轮播,向后移动一个层级
// 更新列表位置相关数据
setTimeout(instance, function() {
for (var i = 0; i < list.length; i++) {
var replaceIndex = (i + 1 > list.length - 1) ? 0 : i + 1
state.itemsInstance[i].setStyle({
'transform': 'translate3d(0px, '+ currentListItemData[replaceIndex].translateY + 'px, 0px) scale(' + currentListItemData[replaceIndex].scale + ')',
'z-index': currentListItemData[replaceIndex].zIndex,
'opacity': currentListItemData[replaceIndex].opacity
})
state.list[i] = currentListItemData[replaceIndex]
}
}, time)
}
}
// 反转动画
function toggleSwiperAnimation(state, add) {
if (!state.itemsInstance) return
if (add === true) {
state.itemsInstance.forEach(function(item, index) {
if (!item.hasClass('tn-stack-swiper__item__transition')) {
item.addClass('tn-stack-swiper__item__transition')
}
})
} else {
state.itemsInstance.forEach(function(item, index) {
if (item.hasClass('tn-stack-swiper__item__transition')) {
item.removeClass('tn-stack-swiper__item__transition')
}
})
}
}
// 更新数据
var itemDataObserver = function (newVal, oldVal, ownerInstance, instance) {
var state = ownerInstance.getState()
state.itemData = newVal
}
// 列表初始化
var listObserver = function(newVal, oldVal, ownerInstance, instance) {
var state = ownerInstance.getState()
var itemData = state.itemData
state.itemsInstance = ownerInstance.selectAllComponents('.tn-stack-swiper__item')
state.list = newVal || []
state.list.forEach(function(item, index) {
var itemInstance = state.itemsInstance[index]
if (item && itemInstance) {
if (itemData.direction === 'horizontal') {
itemInstance.setStyle({
'transform': 'translate3d('+ item.translateX + 'px, 0px, 0px) scale(' + item.scale + ')',
'z-index': item.zIndex,
'opacity': item.opacity
})
} else if (itemData.direction === 'vertical') {
itemInstance.setStyle({
'transform': 'translate3d(0px, '+ item.translateY + 'px, 0px) scale(' + item.scale + ')',
'z-index': item.zIndex,
'opacity': item.opacity
})
}
}
})
}
// 切换轮播位置
var swiperIndexChange = function(newVal, oldVal, ownerInstance, instance) {
var state = ownerInstance.getState()
// console.log(newVal);
// ownerInstance.callMethod('printLog', newVal)
// console.log(oldVal);
// ownerInstance.callMethod('printLog', oldVal)
// 排除第一次初始化和手动切换的情况
if (oldVal < 0 || typeof oldVal == 'undefined' || state.currentIndex == newVal) {
if (oldVal < 0 || typeof oldVal == 'undefined') {
state.currentIndex = 0
}
return
}
state.currentIndex = newVal
// console.log(state.currentIndex);
if (newVal > oldVal || (oldVal == state.list.length - 1 && newVal == 0)) {
// console.log("next");
// state.itemsInstance.forEach(function(item, index) {
// item.addClass("tn-stack-swiper__item__transition")
// })
switchNextSwiper(newVal, {
pageX: 0
}, state.itemsInstance[oldVal], state)
} else if (newVal < oldVal || (oldVal == 0 && newVal == state.list.length - 1)) {
// console.log("prev");
}
}
// 自动轮播切换状态
var autoplayFlagChange = function(newVal, oldVal, ownerInstance, instance) {
var state = ownerInstance.getState()
if (newVal === true) {
toggleSwiperAnimation(state, true)
} else {
toggleSwiperAnimation(state, false)
}
}
// 开始触摸
var touchStart = function (event, ownerInstance) {
// console.log('touchStart');
var instance = event.instance
var dataset = instance.getDataset()
var state = ownerInstance.getState()
var itemData = state.itemData
// 判断是否为为当前显示的轮播
if (dataset.index != state.currentIndex) return
var touches = event.changedTouches[0]
if (!touches) return
// 记录当前滑动开始的xy坐标
state.touchRelactive = {
x: touches.pageX,
y: touches.pageY
}
// 记录触摸id,用于处理多指的情况
state.touchId = touches.identifier
if (itemData.direction === 'horizontal') {
// 水平方向移动
// 设置左右滑动时相对偏移距离
state.itemAnimationWidth = itemData.itemWidth * (dataset.switchrate / 100)
} else if (itemData.direction === 'vertical') {
// 垂直方向移动
// 设置上下滑动时相对偏移距离
state.itemAnimationHeight = itemData.itemHeight * (dataset.switchrate / 100)
}
// 移除运动动画时间
toggleSwiperAnimation(state, false)
// 标记开始触摸
state.touching = true
ownerInstance.callMethod('changeTouchState', {
touching: true
})
// 停止执行自动轮播
ownerInstance.callMethod('clearAutoPlayTimer')
}
// 开始移动
var touchMove = function (event, ownerInstance) {
// console.log('touchMove');
var instance = event.instance
var dataset = instance.getDataset()
var state = ownerInstance.getState()
var itemData = state.itemData
// 判断是否为为当前显示的轮播
if (dataset.index != state.currentIndex) return
// 还没开始触摸直接返回
if (!state.touching) return
var touches = event.changedTouches[0]
if (!touches) return
// 判断是否为同一个触摸点
if (state.touchId != touches.identifier) return
var currentTouchRelactive = {
x: touches.pageX,
y: touches.pageY
}
// 是否已经确定了移动方向
if (!state.direction) {
state.direction = decideSwiperDirection(state.touchRelactive, currentTouchRelactive, itemData.direction)
}
// console.log(decideSwiperDirection(state.touchRelactive, currentTouchRelactive));
updateSwiperStyle(currentTouchRelactive, instance, state)
}
// 移动结束
var touchEnd = function (event, ownerInstance) {
// console.log('touchEnd');
var instance = event.instance
var dataset = instance.getDataset()
var state = ownerInstance.getState()
var itemData = state.itemData
var list = state.list
var touchRelactive = state.touchRelactive
// 判断是否为为当前显示的轮播
if (dataset.index != state.currentIndex) return
// 还没开始触摸直接返回
if (!state.touching) return
var touches = event.changedTouches[0]
if (!touches) return
// 判断是否为同一个触摸点
if (state.touchId != touches.identifier) return
// 添加运动动画时间
toggleSwiperAnimation(state, true)
if (itemData.direction === 'horizontal') {
// 水平方向移动
var itemAnimationWidth = state.itemAnimationWidth
// 判断时左滑还是右滑
// 判断是否超过自动滚动到下一页还是回滚
if (state.direction == 'left') {
if (Math.abs(touches.pageX - touchRelactive.x) < itemAnimationWidth) {
list.forEach(function(item, index) {
var itemInstance = state.itemsInstance[index]
if (item && itemInstance) {
itemInstance.setStyle({
'transform': 'translate3d('+ item.translateX + 'px, 0px, 0px) scale(' + item.scale + ')',
'z-index': item.zIndex
})
}
})
} else {
var newIndex = state.currentIndex + 1 > list.length - 1 ? 0 : state.currentIndex + 1
switchNextSwiper(newIndex, touches, instance, state)
updateCurrentSwiperIndex(newIndex, ownerInstance, state)
}
} else if (state.direction == 'right') {
if (Math.abs(touches.pageX - touchRelactive.x) < itemAnimationWidth) {
// 滑动显示图片回滚
var preIndex = (state.currentIndex == 0) ? list.length - 1 : state.currentIndex - 1
state.itemsInstance[preIndex].setStyle({
'transform': 'translate3d(-' + itemData.itemWidth + 'px, 0px, 0px) scale(1)',
'z-index': list[state.currentIndex].zIndex + 1,
'opacity': list[state.currentIndex].opacity
})
list.forEach(function(item, index) {
var itemInstance = state.itemsInstance[index]
if (item && itemInstance) {
itemInstance.setStyle({
'transform': 'translate3d('+ item.translateX + 'px, 0px, 0px) scale(' + item.scale + ')',
'z-index': item.zIndex,
'opacity': item.opacity
})
}
})
} else {
var newIndex = (state.currentIndex - 1 < 0) ? list.length - 1 : state.currentIndex - 1
switchPrevSwiper(newIndex, touches, instance, state)
updateCurrentSwiperIndex(newIndex, ownerInstance, state)
}
}
} else if (itemData.direction === 'vertical') {
// 垂直方向移动
var itemAnimationHeight = state.itemAnimationHeight
// 判断时上滑还是下滑
// 判断是否超过自动滚动到下一页还是回滚
if (state.direction == 'up') {
if (Math.abs(touches.pageY - touchRelactive.y) < itemAnimationHeight) {
list.forEach(function(item, index) {
var itemInstance = state.itemsInstance[index]
if (item && itemInstance) {
itemInstance.setStyle({
'transform': 'translate3d(0px, '+ item.translateY + 'px, 0px) scale(' + item.scale + ')',
'z-index': item.zIndex,
'opacity': item.opacity
})
}
})
} else {
var newIndex = state.currentIndex + 1 > list.length - 1 ? 0 : state.currentIndex + 1
switchNextSwiper(newIndex, touches, instance, state)
updateCurrentSwiperIndex(newIndex, ownerInstance, state)
}
} else if (state.direction == 'down') {
if (Math.abs(touches.pageY - touchRelactive.y) < itemAnimationHeight) {
// 滑动显示图片回滚
var preIndex = (state.currentIndex == 0) ? list.length - 1 : state.currentIndex - 1
state.itemsInstance[preIndex].setStyle({
'transform': 'translate3d(0px, -' + itemData.itemHeight + 'px, 0px) scale(1)',
'z-index': list[state.currentIndex].zIndex + 1,
'opacity': list[state.currentIndex].opacity
})
list.forEach(function(item, index) {
var itemInstance = state.itemsInstance[index]
if (item && itemInstance) {
itemInstance.setStyle({
'transform': 'translate3d(0px, '+ item.translateY + 'px, 0px) scale(' + item.scale + ')',
'z-index': item.zIndex,
'opacity': item.opacity
})
}
})
} else {
var newIndex = (state.currentIndex - 1 < 0) ? list.length - 1 : state.currentIndex - 1
switchPrevSwiper(newIndex, touches, instance, state)
updateCurrentSwiperIndex(newIndex, ownerInstance, state)
}
}
}
// 清除对应的标志位
state.touchRelactive = null
state.touching = false
state.direction = null
state.touchId = null
ownerInstance.callMethod('changeTouchState', {
touching: false
})
// 重新开始执行自动轮播
ownerInstance.callMethod('setAutoPlay')
}
module.exports = {
itemDataObserver: itemDataObserver,
listObserver: listObserver,
swiperIndexChange: swiperIndexChange,
autoplayFlagChange: autoplayFlagChange,
touchStart: touchStart,
touchMove: touchMove,
touchEnd: touchEnd
}
@@ -0,0 +1,284 @@
<template>
<view
class="tn-stack-swiper-class tn-stack-swiper"
:style="{
width: $tn.string.getLengthUnitValue(width),
height: $tn.string.getLengthUnitValue(height)
}"
:list="swiperList"
:itemData="itemData"
:currentIndex="swiperIndex"
:autoplayFlag="autoplayFlag"
:change:list="wxs.listObserver"
:change:itemData="wxs.itemDataObserver"
:change:currentIndex="wxs.swiperIndexChange"
:change:autoplayFlag="wxs.autoplayFlagChange"
>
<block v-for="(item, index) in list" :key="index">
<!-- #ifdef MP-WEIXIN -->
<view
class="tn-stack-swiper__item tn-stack-swiper__item__transition"
:class="[`tn-stack-swiper__item--${direction}`]"
:data-index="index"
:data-switchRate="switchRate"
@touchstart="wxs.touchStart"
:catch:touchmove="touching?wxs.touchMove:''"
:catch:touchend="touching?wxs.touchEnd:''"
>
<image class="tn-stack-swiper__image" :src="item.image"></image>
</view>
<!-- #endif -->
<!-- #ifndef MP-WEIXIN -->
<view
class="tn-stack-swiper__item"
:class="[`tn-stack-swiper__item--${direction}`]"
:data-index="index"
:data-switchRate="switchRate"
@touchstart="wxs.touchStart"
@touchmove="wxs.touchMove"
@touchend="wxs.touchEnd"
>
<image class="tn-stack-swiper__image" :src="item.image"></image>
</view>
<!-- #endif -->
</block>
</view>
</template>
<!-- #ifdef MP-WEIXIN -->
<script src="./index.wxs" lang="wxs" module="wxs"></script>
<!-- #endif -->
<!-- #ifndef MP-WEIXIN -->
<script src="./index-h5.wxs" lang="wxs" module="wxs"></script>
<!-- #endif -->
<script>
export default {
name: 'tn-stack-swiper',
props: {
//
// {
// //
// image: 'xxx'
// }
list: {
type: Array,
default() {
return []
}
},
// rpx
width: {
type: [String, Number],
default: '100%'
},
// rpx
height: {
type: [String, Number],
default: 500
},
//
autoplay: {
type: Boolean,
default: false
},
// ms
interval: {
type: Number,
default: 5000
},
// , [0 - 100]
// item
switchRate: {
type: Number,
default: 30
},
// [0-1]
scaleRate: {
type: Number,
default: 0.1
},
//
translateRate: {
type: Number,
default: 16
},
//
opacityRate: {
type: Number,
default:10
},
//
// horizontal -> vertical ->
direction: {
type: String,
default: 'horizontal'
}
},
data() {
return {
autoplayTimer: null,
// window
windowWidth: 0,
// item
swiperItemWidth: 0,
// item
swiperItemHeight: 0,
// item
swiperIndex: -1,
//
touching: true,
//
swiperList: [],
//
autoplayFlag: false
}
},
computed: {
itemData() {
return {
windowWidth: this.windowWidth,
itemWidth: this.swiperItemWidth,
itemHeight: this.swiperItemHeight,
direction: this.direction,
autoplaying: this.autoplayFlag
}
}
},
watch: {
list(val) {
this.swiperList = []
this.$nextTick(() => {
this.initSwiperRectInfo()
})
},
autoplay(val) {
if (!val) {
this.clearAutoPlayTimer()
} else {
this.setAutoPlay()
}
}
},
created() {
this.autoplayFlag = this.autoplay
},
mounted() {
this.$nextTick(() => {
this.initSwiperRectInfo()
})
},
destroyed() {
this.clearAutoPlayTimer()
},
methods: {
//
async initSwiperRectInfo() {
//
// this.touching = true
// item
const swiperItemRect = await this._tGetRect('.tn-stack-swiper__item')
if (!swiperItemRect || !swiperItemRect.width || !swiperItemRect.height) {
setTimeout(() => {
this.initSwiperRectInfo()
}, 50)
return
}
this.swiperItemWidth = swiperItemRect.width
this.swiperItemHeight = swiperItemRect.height
// this.touching = false
//
const systemInfo = uni.getSystemInfoSync()
this.windowWidth = systemInfo.windowWidth
this.swiperIndex = 0
// swiper
this.swiperList = this.list.map((item, index) => {
const scale = 1 - (this.scaleRate * index)
if (this.direction === 'horizontal') {
item.translateX = ((index * this.translateRate) * 0.01 * this.swiperItemWidth)
} else if (this.direction === 'vertical') {
item.translateY = ((index * this.translateRate) * 0.01 * this.swiperItemHeight)
}
item.opacity = (1 - ((index * this.opacityRate) * 0.01))
item.zIndex = this.list.length - index
item.scale = scale <= 0 ? 0 : scale
return item
})
this.setAutoPlay()
},
//
setAutoPlay() {
if (this.autoplay) {
this.clearAutoPlayTimer()
this.autoplayFlag = true
this.autoplayTimer = setInterval(() => {
this.swiperIndex = this.swiperIndex + 1 > this.swiperList.length - 1 ? 0 : this.swiperIndex + 1
}, this.interval)
}
},
//
clearAutoPlayTimer() {
if (this.autoplayTimer != null) {
this.autoplayFlag = false
clearInterval(this.autoplayTimer)
}
},
// index
changeSwiperIndex(e) {
// console.log(e.index);
this.swiperIndex = e.index
this.$emit('change', { index: e.index })
},
//
changeTouchState(e) {
this.touching = e.touching
},
//
printLog(data) {
console.log("log", data);
}
}
}
</script>
<style lang="scss" scoped>
.tn-stack-swiper {
position: relative;
&__item {
position: absolute;
border-radius: 20rpx;
overflow: hidden;
&--horizontal {
width: 88%;
height: 100%;
transform-origin: left center;
}
&--vertical {
width: 100%;
height: 88%;
transform-origin: top center;
}
&__transition {
transition-property: transform,opacity;
transition-duration: 0.25s;
transition-timing-function: ease-out;
// transition: transform, opacity 0.25s ease-in-out !important;
}
}
&__image {
width: 100%;
height: 100%;
}
}
</style>
+100
View File
@@ -0,0 +1,100 @@
<template>
<view class="tn-table-class tn-table" :class="[tableClass]" :style="[tableStyle]">
<slot></slot>
</view>
</template>
<script>
export default {
name: 'tn-table',
props: {
//
borderWidth: {
type: [String, Number],
default: ''
},
//
borderColor: {
type: String,
default: ''
},
//
borderTop: {
type: Boolean,
default: true
},
//
borderRight: {
type: Boolean,
default: false
},
//
borderBottom: {
type: Boolean,
default: false
},
//
borderLeft: {
type: Boolean,
default: true
}
},
computed: {
parentData() {
return [this.borderWidth, this.borderColor]
},
tableClass() {
let clazz = ''
return clazz
},
tableStyle() {
let style = {}
if (this.borderWidth !== '') {
style.borderWidth = this.$tn.string.getLengthUnitValue(this.borderWidth)
}
if (this.borderColor) {
style.borderColor = this.borderColor
}
if (this.borderLeft) {
style.borderLeftStyle = 'solid'
}
if (this.borderRight) {
style.borderRightStyle = 'solid'
}
if (this.borderTop) {
style.borderTopStyle = 'solid'
}
if (this.borderBottom) {
style.borderBottomStyle = 'solid'
}
return style
}
},
data() {
return {}
},
created() {
this.children = []
},
watch: {
parentData() {
//
if (this.children.length) {
this.children.map((child) => {
typeof(child.updateParentData) === 'function' && child.updateParentData()
})
}
}
}
}
</script>
<style lang="scss" scoped>
.tn-table {
box-sizing: border-box;
border-width: 1rpx;
border-style: none;
border-color: #AAAAAA;
}
</style>
+307
View File
@@ -0,0 +1,307 @@
<template>
<view class="tn-td-class tn-td" :class="[tdClass]" :style="[tdStyle]" @tap.stop="handleClick">
<slot></slot>
</view>
</template>
<script>
import componentsColorMixin from '../../libs/mixin/components_color.js'
export default {
name: 'tn-td',
options: {
// Vue(shadow)
virtualHost: true
},
mixins: [componentsColorMixin],
props: {
//
// [1-24]
span: {
type: Number,
default: 24
},
//
// span
width: {
type: [String, Number],
default: ''
},
//
height: {
type: [String, Number],
default: ''
},
//
bold: {
type: Boolean,
default: false
},
//
padding: {
type: String,
default: ''
},
//
borderColor: {
type: String,
default: ''
},
//
borderWidth: {
type: [String, Number],
default: ''
},
//
borderLeft: {
type: Boolean,
default: false
},
//
borderBottom: {
type: Boolean,
default: false
},
//
borderRight: {
type: Boolean,
default: true
},
//
ellipsis: {
type: Boolean,
default: false
},
//
// left center right
textAlign: {
type: String,
default: 'left'
},
//
// left center right
alignItems: {
type: String,
default: 'left'
},
//
shrink: {
type: Boolean,
default: true
},
//
grow: {
type: Boolean,
default: false
},
//
hidden: {
type: Boolean,
default: false
},
//
fixed: {
type: Boolean,
default: false
},
// zIndex
zIndex: {
type: Number,
default: 0
},
//
index: {
type: [String, Number],
default: 0
},
// keys
keys: {
type: [String, Number],
default: ''
}
},
computed: {
tdClass() {
let clazz = ''
clazz += `${this.ellipsis ? 'tn-td--ellipsis' : 'tn-td--normal'}`
if (this.backgroundColorClass) {
clazz += ` ${this.backgroundColorClass}`
}
if (this.fontColorClass) {
clazz += ` ${this.fontColorClass}`
}
if (this.alignItems) {
clazz += ` tn-td--${this.alignItems}`
}
if (this.textAlign) {
clazz += ` tn-td__text--${this.textAlign}`
}
if (!this.shrink) {
clazz += ' tn-td--shrink'
}
if (this.grow) {
clazz += ' tn-td--grow'
}
if (this.hidden) {
clazz += ' tn-td--hidden'
}
return clazz
},
tdStyle() {
let style = {}
if (this.backgroundColorStyle) {
style.backgroundColor = this.backgroundColorStyle
}
if (this.fontColorStyle) {
style.color = this.fontColorStyle
}
if (this.fontSizeStyle) {
style.fontSize = this.fontSizeStyle
}
style.width = this.getWidth()
if (this.height) {
style.height = this.$tn.string.getLengthUnitValue(this.height)
}
style.fontWeight = this.bold ? 'bold' : 'normal'
if (this.padding) {
style.padding = this.padding
}
if (this.borderWidth !== '' || this.parentData.borderWidthValue !== '') {
style.borderWidth = this.borderWidth !== '' ? this.$tn.string.getLengthUnitValue(this.borderWidth) : this.$tn.string.getLengthUnitValue(this.parentData.borderWidthValue)
}
if (this.borderColor || this.parentData.borderColorValue) {
style.borderColor = this.borderColor || this.parentData.borderColorValue
}
if (this.borderLeft) {
style.borderLeftStyle = 'solid'
}
if (this.borderRight) {
style.borderRightStyle = 'solid'
}
if (this.borderBottom) {
style.borderBottomStyle = 'solid'
}
if (this.fixed) {
style.zIndex = this.zIndex ? this.zIndex : this.$tn.zIndex.tableTd
}
return style
}
},
data() {
return {
parentData: {
borderColorValue: null,
borderWidthValue: null
}
}
},
created() {
this.parent = false
this.updateParentData()
this.parent && this.parent.children.push(this)
},
methods: {
//
getWidth() {
if (this.width) {
return this.$tn.string.getLengthUnitValue(this.width)
}
return [
'4.16666667%',
'8.33333333%',
'12.5%',
'16.66666667%',
'20.83333333%',
'25%',
'29.16666667%',
'33.33333333%',
'37.5%',
'41.66666667%',
'45.83333333%',
'50%',
'54.16666667%',
'58.33333333%',
'62.5%',
'66.66666667%',
'70.83333333%',
'75%',
'79.16666667%',
'83.33333333%',
'87.5%',
'91.66666667%',
'95.83333333%',
'100%'
][this.span - 1]
},
//
handleClick() {
this.$emit('click', {
index: this.index,
key: this.keys
})
},
//
updateParentData() {
this.getParentData('tn-tr')
}
}
}
</script>
<style lang="scss" scoped>
.tn-td {
box-sizing: border-box;
position: relative;
word-break: break-all;
background-color: transparent;
height: auto;
padding: 12rpx;
border-width: 1rpx;
border-style: none;
border-color: #AAAAAA;
&--normal {
display: inline-flex;
align-items: center;
}
&--ellipsis {
display: inline-block;
overflow: hidden;
white-space: nowrap !important;
text-overflow: ellipsis;
}
&--shrink {
flex-shrink: 0;
}
&--grow {
flex-grow: 1;
}
&--left {
justify-content: flex-start;
}
&--center {
justify-content: center;
}
&--right {
justify-content: flex-end;
}
&__text {
&--left {
text-align: left;
}
&--center {
text-align: center;
}
&--right {
text-align: right;
}
}
&--hidden {
visibility: hidden;
}
}
</style>
+210
View File
@@ -0,0 +1,210 @@
<template>
<view class="tn-tr-class tn-tr" :class="[trClass]" :style="[trStyle]">
<slot></slot>
</view>
</template>
<script>
import componentsColorMixin from '../../libs/mixin/components_color.js'
export default {
name: 'tn-tr',
options: {
// Vue(shadow)
virtualHost: true
},
mixins: [componentsColorMixin],
props: {
//
width: {
type: [String, Number],
default: ''
},
//
borderColor: {
type: String,
default: ''
},
//
borderWidth: {
type: [String, Number],
default: ''
},
//
borderLeft: {
type: Boolean,
default: false
},
//
borderTop: {
type: Boolean,
default: false
},
//
wrap: {
type: Boolean,
default: false
},
//
fixed: {
type: Boolean,
default: false
},
// left
left: {
type: [String, Number],
default: 0
},
// right
right: {
type: [String, Number],
default: 0
},
// top()
top: {
type: [String, Number],
default: 0
},
//
margin: {
type: String,
default: ''
},
// zIndex
zIndex: {
type: Number,
default: 0
},
//
index: {
type: [String, Number],
default: 0
},
//
params: {
type: String,
default: ''
}
},
computed: {
borderWidthValue() {
return this.borderWidth || this.parentData.borderWidth || ''
},
borderColorValue() {
return this.borderColor || this.parentData.borderColor || ''
},
trClass() {
let clazz = ''
if (this.backgroundColorClass) {
clazz += ` ${this.backgroundColorClass}`
}
if (this.fontColorClass) {
clazz += ` ${this.fontColorClass}`
}
if (this.wrap) {
clazz += ' tn-tr--wrap'
}
if (this.fixed) {
clazz += ' tn-tr--fixed'
}
return clazz
},
trStyle() {
let style = {}
if (this.width) {
style.width = this.$tn.string.getLengthUnitValue(this.width)
}
if (this.backgroundColorStyle) {
style.backgroundColor = this.backgroundColorStyle
}
if (this.fontColorStyle) {
style.color = this.fontColorStyle
}
if (this.fontSizeStyle) {
style.fontSize = this.fontSizeStyle
}
if (this.borderWidth !== '' || this.parentData.borderWidth !== '') {
style.borderWidth = this.borderWidth !== '' ? this.$tn.string.getLengthUnitValue(this.borderWidth) : this.$tn.string.getLengthUnitValue(this.parentData.borderWidth)
}
if (this.borderColor || this.parentData.borderColor) {
style.borderColor = this.borderColor || this.parentData.borderColor
}
if (this.borderLeft) {
style.borderLeftStyle = 'solid'
}
if (this.borderTop) {
style.borderTopStyle = 'solid'
}
if (this.fixed) {
style.left = this.left ? this.$tn.string.getLengthUnitValue(this.left) : 'auto'
style.right = this.right ? this.$tn.string.getLengthUnitValue(this.right) : 'auto'
style.top = this.top ? this.$tn.string.getLengthUnitValue(this.top) : 'auto'
}
if (this.margin) {
style.margin = this.margin
}
style.zIndex = this.zIndex ? this.zIndex : this.$tn.zIndex.tableTr
return style
}
},
data() {
return {
parentData: {
borderColor: null,
borderWidth: null
}
}
},
watch: {
parentData: {
handler() {
//
if (this.children.length) {
this.children.map((child) => {
typeof(child.updateParentData) === 'function' && child.updateParentData()
})
}
},
deep: true
}
},
created() {
this.children = []
this.parent = false
this.updateParentData()
this.parent && this.parent.children.push(this)
},
methods: {
handleClick() {
this.$emit('click', {
index: this.index,
params: this.params
})
},
//
updateParentData() {
this.getParentData('tn-table')
}
}
}
</script>
<style lang="scss" scoped>
.tn-tr {
width: 100%;
display: flex;
box-sizing: border-box;
background-color: #FFFFFF;
border-width: 1rpx;
border-style: none none solid none;
border-color: #AAAAAA;
&--wrap {
flex-wrap: wrap;
}
&--fixed {
position: fixed;
}
}
</style>
@@ -0,0 +1,143 @@
<template>
<view class="tn-tree-node-class tn-tree-node">
<view class="tn-tree__label" @tap="handleClick">
<view
v-if="node.children && node.children.length > 0 && triangle"
class="tn-tree__triangle"
:class="[{'tn-tree__triangle--90deg': !collapsed}]"
></view>
<view class="tn-tree__label__item">
<view v-if="collapsed && node.image" class="tn-tree__label__item__image">
<image :src="node.image" mode="widthFix"></image>
</view>
<view v-if="!collapsed && node.activeImage" class="tn-tree__label__item__image">
<image :src="node.activeImage" mode="widthFix"></image>
</view>
<view class="tn-tree__label__item__text">{{ node.text }}</view>
</view>
</view>
<view v-if="!collapsed && node.children && node.children.length > 0" class="tn-tree__children">
<tn-tree-node
v-for="(item, index) in node.children"
:key="index"
:node="item"
:collapsible="collapsible"
:triangle="triangle"
@click="nodeClick"
></tn-tree-node>
</view>
</view>
</template>
<script>
//easycomtn-tree-node
export default {
name: 'tn-tree-node',
props: {
//
node: {
type: Object,
default() {
return {}
}
},
//
collapsible: {
type: Boolean,
default: true
},
//
triangle: {
type: Boolean,
default: true
}
},
watch: {
node(val) {
if (val.collapsed !== this.collapsed && this.node.children && this.node.children.length > 0) {
this.collapsed = val.collapsed
}
}
},
data() {
return {
//
collapsed: true
}
},
created() {
if (this.node.collapsed === false) {
this.collapsed = false
}
},
methods: {
//
handleClick(e) {
if (this.collapsible && this.node.children && this.node.children.length > 0) {
this.collapsed = !this.collapsed
}
this.$emit('click', this.node)
},
nodeClick(e) {
this.$emit('click', e)
}
}
}
</script>
<style lang="scss" scoped>
.tn-tree-node {
.tn-tree {
&__label {
position: relative;
display: inline-flex;
align-items: center;
padding: 20rpx 30rpx;
background-color: transparent;
/* #ifdef H5 */
cursor: pointer;
/* #endif */
// 齿
-webkit-font-smoothing: antialiased;
&__item {
display: flex;
flex-direction: row;
align-items: center;
&__image {
width: 40rpx;
height: 40rpx;
margin-right: 16rpx;
image {
width: 100%;
height: 100%;
}
}
}
}
&__children {
padding-left: 60rpx;
position: relative;
}
&__triangle {
width: 0;
height: 0;
border-top: 12rpx solid transparent;
border-bottom: 12rpx solid transparent;
border-left: 16rpx solid #080808;
margin-right: 20rpx;
transition: transform 0.25s ease-out;
flex-shrink: 0;
&--90deg {
transform:rotate(90deg) translate3d(0, 0, 0);
}
}
}
}
</style>
@@ -0,0 +1,50 @@
<template>
<view class="tn-tree-view-class tn-tree-view">
<tn-tree-node
v-for="(item, index) in treeData"
:key="index"
:node="item"
:collapsible="collapsible"
:triangle="triangle"
@click="handleClick"
></tn-tree-node>
</view>
</template>
<script>
//easycomtn-tree-node
export default {
name: 'tn-tree-view',
props: {
//
treeData: {
type: Array,
default() {
return []
}
},
//
collapsible: {
type: Boolean,
default: true
},
//
triangle: {
type: Boolean,
default: true
}
},
methods: {
handleClick(e) {
this.$emit('click', e)
}
}
}
</script>
<style lang="scss" scoped>
.tn-tree-view {
width: 100%;
position: relative;
}
</style>
@@ -0,0 +1,165 @@
<template>
<view class="tn-waterfall-class tn-waterfall">
<view id="tn-waterfall-left" class="tn-waterfall__column"><slot name="left" :leftList="leftList"></slot></view>
<view id="tn-waterfall-right" class="tn-waterfall__column"><slot name="right" :rightList="rightList"></slot></view>
</view>
</template>
<script>
export default {
name: 'tn-waterfall',
props: {
//
value: {
type: Array,
default() {
return []
}
},
// idid
// {id: 1, name: 'tuniao'}id
idKey: {
type: String,
default: 'id'
},
//
// ms
addTime: {
type: Number,
default: 200
}
},
computed: {
// value
copyValue() {
return this.cloneData(this.value)
}
},
watch: {
copyValue(nVal, oVal) {
//
let startIndex = Array.isArray(oVal) && oVal.length > 0 ? oVal.length : 0
//
this.tempList = this.tempList.concat(this.cloneData(nVal.slice(startIndex)))
this.splitData()
}
},
data() {
return {
//
leftList: [],
//
rightList: [],
//
tempList: []
}
},
mounted() {
this.tempList = this.cloneData(this.copyValue)
this.splitData()
},
methods: {
//
async splitData() {
if (!this.tempList.length) return
let leftRect = await this._tGetRect('#tn-waterfall-left')
let rightRect = await this._tGetRect('#tn-waterfall-right')
let item = this.tempList[0]
// await[]itemundefined
//
if (!item) return
//
if (leftRect.height < rightRect.height) {
this.leftList.push(item)
} else if (leftRect.height > rightRect.height) {
this.rightList.push(item)
} else {
//
if (this.leftList.length <= this.rightList.length) {
this.leftList.push(item)
} else {
this.rightList.push(item)
}
}
//
this.tempList.splice(0, 1)
//
if (this.tempList.length) {
setTimeout(() => {
this.splitData()
}, this.addTime)
} else {
this.$emit('finish')
}
},
//
cloneData(data) {
return JSON.parse(JSON.stringify(data))
},
//
clear() {
this.leftList = []
this.rightList = []
this.$emit('input', [])
this.tempList = []
},
// id
remove(id) {
// -1
let index = -1
index = this.leftList.findIndex(val => val[this.idKey] == id)
if (index != -1) {
// index-1
this.leftList.splice(index, 1)
} else {
//
index = this.rightList.findIndex(val => val[this.idKey] == id)
if (index != -1) this.rightList.splice(index, 1)
}
//
index = this.value.findIndex(val => val[this.idKey] == id)
if (index != -1) this.$emit('input', this.value.splice(index, 1))
},
//
modify(id, key, value) {
// -1
let index = -1
index = this.leftList.findIndex(val => val[this.idKey] == id)
if (index != -1) {
// index-1
this.leftList[index][key] = value
} else {
//
index = this.rightList.findIndex(val => val[this.idKey] == id)
if (index != -1) this.rightList[index][key] = value
}
//
index = this.value.findIndex(val => val[this.idKey] == id)
if(index != -1) {
let data = this.cloneData(this.value)
data[index][key] = value
this.$emit('input', data)
}
}
}
}
</script>
<style lang="scss" scoped>
.tn-waterfall {
display: flex;
flex-direction: row;
align-items: flex-start;
&__column {
display: flex;
flex-direction: column;
flex: 1;
height: auto;
}
}
</style>
+1164 -31
View File
File diff suppressed because one or more lines are too long
+3
View File
@@ -1,5 +1,7 @@
// 引入全局mixin
import mixin from './libs/mixin/mixin.js'
// 全局挂载引入http相关请求拦截插件
import Request from './libs/luch-request'
// 调试输出信息
function wranning(str) {
@@ -39,6 +41,7 @@ import zIndex from './libs/config/zIndex.js'
import colorInfo from './libs/config/color.js'
const $tn = {
http: new Request(),
updateCustomBar: updateCustomBarInfo,
color,
message,
@@ -0,0 +1,99 @@
import buildURL from '../helpers/buildURL'
import buildFullPath from '../core/buildFullPath'
import settle from '../core/settle'
import { isUndefined } from "../utils"
/**
* 返回可选值存在的配置
* @param {Array} keys - 可选值数组
* @param {Object} config2 - 配置
* @return {{}} - 存在的配置项
*/
const mergeKeys = (keys, config2) => {
let config = {}
keys.forEach(prop => {
if (!isUndefined(config2[prop])) {
config[prop] = config2[prop]
}
})
return config
}
export default (config) => {
return new Promise((resolve, reject) => {
let fullPath = buildURL(buildFullPath(config.baseURL, config.url), config.params)
const _config = {
url: fullPath,
header: config.header,
complete: (response) => {
config.fullPath = fullPath
response.config = config
try {
// 对可能字符串不是json 的情况容错
if (typeof response.data === 'string') {
response.data = JSON.parse(response.data)
}
// eslint-disable-next-line no-empty
} catch (e) {
}
settle(resolve, reject, response)
}
}
let requestTask
if (config.method === 'UPLOAD') {
delete _config.header['content-type']
delete _config.header['Content-Type']
let otherConfig = {
// #ifdef MP-ALIPAY
fileType: config.fileType,
// #endif
filePath: config.filePath,
name: config.name
}
const optionalKeys = [
// #ifdef APP-PLUS || H5
'files',
// #endif
// #ifdef H5
'file',
// #endif
// #ifdef H5 || APP-PLUS
'timeout',
// #endif
'formData'
]
requestTask = uni.uploadFile({..._config, ...otherConfig, ...mergeKeys(optionalKeys, config)})
} else if (config.method === 'DOWNLOAD') {
// #ifdef H5 || APP-PLUS
if (!isUndefined(config['timeout'])) {
_config['timeout'] = config['timeout']
}
// #endif
requestTask = uni.downloadFile(_config)
} else {
const optionalKeys = [
'data',
'method',
// #ifdef H5 || APP-PLUS || MP-ALIPAY || MP-WEIXIN
'timeout',
// #endif
'dataType',
// #ifndef MP-ALIPAY
'responseType',
// #endif
// #ifdef APP-PLUS
'sslVerify',
// #endif
// #ifdef H5
'withCredentials',
// #endif
// #ifdef APP-PLUS
'firstIpv4',
// #endif
]
requestTask = uni.request({..._config,...mergeKeys(optionalKeys, config)})
}
if (config.getTask) {
config.getTask(requestTask, config)
}
})
}
@@ -0,0 +1,51 @@
'use strict'
function InterceptorManager() {
this.handlers = []
}
/**
* Add a new interceptor to the stack
*
* @param {Function} fulfilled The function to handle `then` for a `Promise`
* @param {Function} rejected The function to handle `reject` for a `Promise`
*
* @return {Number} An ID used to remove interceptor later
*/
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
this.handlers.push({
fulfilled: fulfilled,
rejected: rejected
})
return this.handlers.length - 1
}
/**
* Remove an interceptor from the stack
*
* @param {Number} id The ID that was returned by `use`
*/
InterceptorManager.prototype.eject = function eject(id) {
if (this.handlers[id]) {
this.handlers[id] = null
}
}
/**
* Iterate over all the registered interceptors
*
* This method is particularly useful for skipping over any
* interceptors that may have become `null` calling `eject`.
*
* @param {Function} fn The function to call for each interceptor
*/
InterceptorManager.prototype.forEach = function forEach(fn) {
this.handlers.forEach(h => {
if (h !== null) {
fn(h)
}
})
}
export default InterceptorManager
+200
View File
@@ -0,0 +1,200 @@
/**
* @Class Request
* @description luch-request http请求插件
* @version 3.0.7
* @Author lu-ch
* @Date 2021-09-04
* @Email webwork.s@qq.com
* 文档: https://www.quanzhan.co/luch-request/
* github: https://github.com/lei-mu/luch-request
* DCloud: http://ext.dcloud.net.cn/plugin?id=392
* HBuilderX: beat-3.0.4 alpha-3.0.4
*/
import dispatchRequest from './dispatchRequest'
import InterceptorManager from './InterceptorManager'
import mergeConfig from './mergeConfig'
import defaults from './defaults'
import { isPlainObject } from '../utils'
import clone from '../utils/clone'
export default class Request {
/**
* @param {Object} arg - 全局配置
* @param {String} arg.baseURL - 全局根路径
* @param {Object} arg.header - 全局header
* @param {String} arg.method = [GET|POST|PUT|DELETE|CONNECT|HEAD|OPTIONS|TRACE] - 全局默认请求方式
* @param {String} arg.dataType = [json] - 全局默认的dataType
* @param {String} arg.responseType = [text|arraybuffer] - 全局默认的responseType支付宝小程序不支持
* @param {Object} arg.custom - 全局默认的自定义参数
* @param {Number} arg.timeout - 全局默认的超时时间单位 ms默认60000H5(HBuilderX 2.9.9+)APP(HBuilderX 2.9.9+)微信小程序2.10.0支付宝小程序
* @param {Boolean} arg.sslVerify - 全局默认的是否验证 ssl 证书默认true.仅App安卓端支持HBuilderX 2.3.3+
* @param {Boolean} arg.withCredentials - 全局默认的跨域请求时是否携带凭证cookies默认false仅H5支持HBuilderX 2.6.15+
* @param {Boolean} arg.firstIpv4 - 全DNS解析时优先使用ipv4默认false App-Android 支持 (HBuilderX 2.8.0+)
* @param {Function(statusCode):Boolean} arg.validateStatus - 全局默认的自定义验证器默认statusCode >= 200 && statusCode < 300
*/
constructor(arg = {}) {
if (!isPlainObject(arg)) {
arg = {}
console.warn('设置全局参数必须接收一个Object')
}
this.config = clone({...defaults, ...arg})
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
}
}
/**
* @Function
* @param {Request~setConfigCallback} f - 设置全局默认配置
*/
setConfig(f) {
this.config = f(this.config)
}
middleware(config) {
config = mergeConfig(this.config, config)
let chain = [dispatchRequest, undefined]
let promise = Promise.resolve(config)
this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
chain.unshift(interceptor.fulfilled, interceptor.rejected)
})
this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
chain.push(interceptor.fulfilled, interceptor.rejected)
})
while (chain.length) {
promise = promise.then(chain.shift(), chain.shift())
}
return promise
}
/**
* @Function
* @param {Object} config - 请求配置项
* @prop {String} options.url - 请求路径
* @prop {Object} options.data - 请求参数
* @prop {Object} [options.responseType = config.responseType] [text|arraybuffer] - 响应的数据类型
* @prop {Object} [options.dataType = config.dataType] - 如果设为 json会尝试对返回的数据做一次 JSON.parse
* @prop {Object} [options.header = config.header] - 请求header
* @prop {Object} [options.method = config.method] - 请求方法
* @returns {Promise<unknown>}
*/
request(config = {}) {
return this.middleware(config)
}
get(url, options = {}) {
return this.middleware({
url,
method: 'GET',
...options
})
}
post(url, data, options = {}) {
return this.middleware({
url,
data,
method: 'POST',
...options
})
}
// #ifndef MP-ALIPAY
put(url, data, options = {}) {
return this.middleware({
url,
data,
method: 'PUT',
...options
})
}
// #endif
// #ifdef APP-PLUS || H5 || MP-WEIXIN || MP-BAIDU
delete(url, data, options = {}) {
return this.middleware({
url,
data,
method: 'DELETE',
...options
})
}
// #endif
// #ifdef H5 || MP-WEIXIN
connect(url, data, options = {}) {
return this.middleware({
url,
data,
method: 'CONNECT',
...options
})
}
// #endif
// #ifdef H5 || MP-WEIXIN || MP-BAIDU
head(url, data, options = {}) {
return this.middleware({
url,
data,
method: 'HEAD',
...options
})
}
// #endif
// #ifdef APP-PLUS || H5 || MP-WEIXIN || MP-BAIDU
options(url, data, options = {}) {
return this.middleware({
url,
data,
method: 'OPTIONS',
...options
})
}
// #endif
// #ifdef H5 || MP-WEIXIN
trace(url, data, options = {}) {
return this.middleware({
url,
data,
method: 'TRACE',
...options
})
}
// #endif
upload(url, config = {}) {
config.url = url
config.method = 'UPLOAD'
return this.middleware(config)
}
download(url, config = {}) {
config.url = url
config.method = 'DOWNLOAD'
return this.middleware(config)
}
}
/**
* setConfig回调
* @return {Object} - 返回操作后的config
* @callback Request~setConfigCallback
* @param {Object} config - 全局默认config
*/
@@ -0,0 +1,20 @@
'use strict'
import isAbsoluteURL from '../helpers/isAbsoluteURL'
import combineURLs from '../helpers/combineURLs'
/**
* Creates a new URL by combining the baseURL with the requestedURL,
* only when the requestedURL is not already an absolute URL.
* If the requestURL is absolute, this function returns the requestedURL untouched.
*
* @param {string} baseURL The base URL
* @param {string} requestedURL Absolute or relative URL to combine
* @returns {string} The combined full path
*/
export default function buildFullPath(baseURL, requestedURL) {
if (baseURL && !isAbsoluteURL(requestedURL)) {
return combineURLs(baseURL, requestedURL)
}
return requestedURL
}
@@ -0,0 +1,30 @@
/**
* 默认的全局配置
*/
export default {
baseURL: '',
header: {},
method: 'GET',
dataType: 'json',
// #ifndef MP-ALIPAY
responseType: 'text',
// #endif
custom: {},
// #ifdef H5 || APP-PLUS || MP-ALIPAY || MP-WEIXIN
timeout: 60000,
// #endif
// #ifdef APP-PLUS
sslVerify: true,
// #endif
// #ifdef H5
withCredentials: false,
// #endif
// #ifdef APP-PLUS
firstIpv4: false,
// #endif
validateStatus: function validateStatus(status) {
return status >= 200 && status < 300
}
}
@@ -0,0 +1,6 @@
import adapter from '../adapters/index'
export default (config) => {
return adapter(config)
}
@@ -0,0 +1,103 @@
import {deepMerge, isUndefined} from '../utils'
/**
* 合并局部配置优先的配置如果局部有该配置项则用局部如果全局有该配置项则用全局
* @param {Array} keys - 配置项
* @param {Object} globalsConfig - 当前的全局配置
* @param {Object} config2 - 局部配置
* @return {{}}
*/
const mergeKeys = (keys, globalsConfig, config2) => {
let config = {}
keys.forEach(prop => {
if (!isUndefined(config2[prop])) {
config[prop] = config2[prop]
} else if (!isUndefined(globalsConfig[prop])) {
config[prop] = globalsConfig[prop]
}
})
return config
}
/**
*
* @param globalsConfig - 当前实例的全局配置
* @param config2 - 当前的局部配置
* @return - 合并后的配置
*/
export default (globalsConfig, config2 = {}) => {
const method = config2.method || globalsConfig.method || 'GET'
let config = {
baseURL: globalsConfig.baseURL || '',
method: method,
url: config2.url || '',
params: config2.params || {},
custom: {...(globalsConfig.custom || {}), ...(config2.custom || {})},
header: deepMerge(globalsConfig.header || {}, config2.header || {})
}
const defaultToConfig2Keys = ['getTask', 'validateStatus']
config = {...config, ...mergeKeys(defaultToConfig2Keys, globalsConfig, config2)}
// eslint-disable-next-line no-empty
if (method === 'DOWNLOAD') {
// #ifdef H5 || APP-PLUS
if (!isUndefined(config2.timeout)) {
config['timeout'] = config2['timeout']
} else if (!isUndefined(globalsConfig.timeout)) {
config['timeout'] = globalsConfig['timeout']
}
// #endif
} else if (method === 'UPLOAD') {
delete config.header['content-type']
delete config.header['Content-Type']
const uploadKeys = [
// #ifdef APP-PLUS || H5
'files',
// #endif
// #ifdef MP-ALIPAY
'fileType',
// #endif
// #ifdef H5
'file',
// #endif
'filePath',
'name',
// #ifdef H5 || APP-PLUS
'timeout',
// #endif
'formData',
]
uploadKeys.forEach(prop => {
if (!isUndefined(config2[prop])) {
config[prop] = config2[prop]
}
})
// #ifdef H5 || APP-PLUS
if (isUndefined(config.timeout) && !isUndefined(globalsConfig.timeout)) {
config['timeout'] = globalsConfig['timeout']
}
// #endif
} else {
const defaultsKeys = [
'data',
// #ifdef H5 || APP-PLUS || MP-ALIPAY || MP-WEIXIN
'timeout',
// #endif
'dataType',
// #ifndef MP-ALIPAY
'responseType',
// #endif
// #ifdef APP-PLUS
'sslVerify',
// #endif
// #ifdef H5
'withCredentials',
// #endif
// #ifdef APP-PLUS
'firstIpv4',
// #endif
]
config = {...config, ...mergeKeys(defaultsKeys, globalsConfig, config2)}
}
return config
}
@@ -0,0 +1,16 @@
/**
* Resolve or reject a Promise based on response status.
*
* @param {Function} resolve A function that resolves the promise.
* @param {Function} reject A function that rejects the promise.
* @param {object} response The response.
*/
export default function settle(resolve, reject, response) {
const validateStatus = response.config.validateStatus
const status = response.statusCode
if (status && (!validateStatus || validateStatus(status))) {
resolve(response)
} else {
reject(response)
}
}
@@ -0,0 +1,69 @@
'use strict'
import * as utils from './../utils'
function encode(val) {
return encodeURIComponent(val).
replace(/%40/gi, '@').
replace(/%3A/gi, ':').
replace(/%24/g, '$').
replace(/%2C/gi, ',').
replace(/%20/g, '+').
replace(/%5B/gi, '[').
replace(/%5D/gi, ']')
}
/**
* Build a URL by appending params to the end
*
* @param {string} url The base of the url (e.g., http://www.google.com)
* @param {object} [params] The params to be appended
* @returns {string} The formatted url
*/
export default function buildURL(url, params) {
/*eslint no-param-reassign:0*/
if (!params) {
return url
}
var serializedParams
if (utils.isURLSearchParams(params)) {
serializedParams = params.toString()
} else {
var parts = []
utils.forEach(params, function serialize(val, key) {
if (val === null || typeof val === 'undefined') {
return
}
if (utils.isArray(val)) {
key = key + '[]'
} else {
val = [val]
}
utils.forEach(val, function parseValue(v) {
if (utils.isDate(v)) {
v = v.toISOString()
} else if (utils.isObject(v)) {
v = JSON.stringify(v)
}
parts.push(encode(key) + '=' + encode(v))
})
})
serializedParams = parts.join('&')
}
if (serializedParams) {
var hashmarkIndex = url.indexOf('#')
if (hashmarkIndex !== -1) {
url = url.slice(0, hashmarkIndex)
}
url += (url.indexOf('?') === -1 ? '?' : '&') + serializedParams
}
return url
}
@@ -0,0 +1,14 @@
'use strict'
/**
* Creates a new URL by combining the specified URLs
*
* @param {string} baseURL The base URL
* @param {string} relativeURL The relative URL
* @returns {string} The combined URL
*/
export default function combineURLs(baseURL, relativeURL) {
return relativeURL
? baseURL.replace(/\/+$/, '') + '/' + relativeURL.replace(/^\/+/, '')
: baseURL
}
@@ -0,0 +1,14 @@
'use strict'
/**
* Determines whether the specified URL is absolute
*
* @param {string} url The URL to test
* @returns {boolean} True if the specified URL is absolute, otherwise false
*/
export default function isAbsoluteURL(url) {
// A URL is considered absolute if it begins with "<scheme>://" or "//" (protocol-relative URL).
// RFC 3986 defines scheme name as a sequence of characters beginning with a letter and followed
// by any combination of letters, digits, plus, period, or hyphen.
return /^([a-z][a-z\d+\-.]*:)?\/\//i.test(url)
}
+116
View File
@@ -0,0 +1,116 @@
type AnyObject = Record<string | number | symbol, any>
type HttpPromise<T> = Promise<HttpResponse<T>>;
type Tasks = UniApp.RequestTask | UniApp.UploadTask | UniApp.DownloadTask
export interface RequestTask {
abort: () => void;
offHeadersReceived: () => void;
onHeadersReceived: () => void;
}
export interface HttpRequestConfig<T = Tasks> {
/** 请求基地址 */
baseURL?: string;
/** 请求服务器接口地址 */
url?: string;
/** 请求查询参数,自动拼接为查询字符串 */
params?: AnyObject;
/** 请求体参数 */
data?: AnyObject;
/** 文件对应的 key */
name?: string;
/** HTTP 请求中其他额外的 form data */
formData?: AnyObject;
/** 要上传文件资源的路径。 */
filePath?: string;
/** 需要上传的文件列表。使用 files 时,filePath 和 name 不生效,App、H5 2.6.15+ */
files?: Array<{
name?: string;
file?: File;
uri: string;
}>;
/** 要上传的文件对象,仅H5(2.6.15+)支持 */
file?: File;
/** 请求头信息 */
header?: AnyObject;
/** 请求方式 */
method?: "GET" | "POST" | "PUT" | "DELETE" | "CONNECT" | "HEAD" | "OPTIONS" | "TRACE" | "UPLOAD" | "DOWNLOAD";
/** 如果设为 json,会尝试对返回的数据做一次 JSON.parse */
dataType?: string;
/** 设置响应的数据类型,支付宝小程序不支持 */
responseType?: "text" | "arraybuffer";
/** 自定义参数 */
custom?: AnyObject;
/** 超时时间,仅微信小程序(2.10.0)、支付宝小程序支持 */
timeout?: number;
/** DNS解析时优先使用ipv4,仅 App-Android 支持 (HBuilderX 2.8.0+) */
firstIpv4?: boolean;
/** 验证 ssl 证书 仅5+App安卓端支持(HBuilderX 2.3.3+ */
sslVerify?: boolean;
/** 跨域请求时是否携带凭证(cookies)仅H5支持(HBuilderX 2.6.15+ */
withCredentials?: boolean;
/** 返回当前请求的task, options。请勿在此处修改options。 */
getTask?: (task: T, options: HttpRequestConfig<T>) => void;
/** 全局自定义验证器 */
validateStatus?: (statusCode: number) => boolean | void;
}
export interface HttpResponse<T = any> {
config: HttpRequestConfig;
statusCode: number;
cookies: Array<string>;
data: T;
errMsg: string;
header: AnyObject;
}
export interface HttpUploadResponse<T = any> {
config: HttpRequestConfig;
statusCode: number;
data: T;
errMsg: string;
}
export interface HttpDownloadResponse extends HttpResponse {
tempFilePath: string;
}
export interface HttpError {
config: HttpRequestConfig;
statusCode?: number;
cookies?: Array<string>;
data?: any;
errMsg: string;
header?: AnyObject;
}
export interface HttpInterceptorManager<V, E = V> {
use(
onFulfilled?: (config: V) => Promise<V> | V,
onRejected?: (config: E) => Promise<E> | E
): void;
eject(id: number): void;
}
export abstract class HttpRequestAbstract {
constructor(config?: HttpRequestConfig);
config: HttpRequestConfig;
interceptors: {
request: HttpInterceptorManager<HttpRequestConfig, HttpRequestConfig>;
response: HttpInterceptorManager<HttpResponse, HttpError>;
}
middleware<T = any>(config: HttpRequestConfig): HttpPromise<T>;
request<T = any>(config: HttpRequestConfig<UniApp.RequestTask>): HttpPromise<T>;
get<T = any>(url: string, config?: HttpRequestConfig<UniApp.RequestTask>): HttpPromise<T>;
upload<T = any>(url: string, config?: HttpRequestConfig<UniApp.UploadTask>): HttpPromise<T>;
delete<T = any>(url: string, data?: AnyObject, config?: HttpRequestConfig<UniApp.RequestTask>): HttpPromise<T>;
head<T = any>(url: string, data?: AnyObject, config?: HttpRequestConfig<UniApp.RequestTask>): HttpPromise<T>;
post<T = any>(url: string, data?: AnyObject, config?: HttpRequestConfig<UniApp.RequestTask>): HttpPromise<T>;
put<T = any>(url: string, data?: AnyObject, config?: HttpRequestConfig<UniApp.RequestTask>): HttpPromise<T>;
connect<T = any>(url: string, data?: AnyObject, config?: HttpRequestConfig<UniApp.RequestTask>): HttpPromise<T>;
options<T = any>(url: string, data?: AnyObject, config?: HttpRequestConfig<UniApp.RequestTask>): HttpPromise<T>;
trace<T = any>(url: string, data?: AnyObject, config?: HttpRequestConfig<UniApp.RequestTask>): HttpPromise<T>;
download(url: string, config?: HttpRequestConfig<UniApp.DownloadTask>): Promise<HttpDownloadResponse>;
setConfig(onSend: (config: HttpRequestConfig) => HttpRequestConfig): void;
}
declare class HttpRequest extends HttpRequestAbstract { }
export default HttpRequest;
+2
View File
@@ -0,0 +1,2 @@
import Request from './core/Request'
export default Request
+135
View File
@@ -0,0 +1,135 @@
'use strict'
// utils is a library of generic helper functions non-specific to axios
var toString = Object.prototype.toString
/**
* Determine if a value is an Array
*
* @param {Object} val The value to test
* @returns {boolean} True if value is an Array, otherwise false
*/
export function isArray (val) {
return toString.call(val) === '[object Array]'
}
/**
* Determine if a value is an Object
*
* @param {Object} val The value to test
* @returns {boolean} True if value is an Object, otherwise false
*/
export function isObject (val) {
return val !== null && typeof val === 'object'
}
/**
* Determine if a value is a Date
*
* @param {Object} val The value to test
* @returns {boolean} True if value is a Date, otherwise false
*/
export function isDate (val) {
return toString.call(val) === '[object Date]'
}
/**
* Determine if a value is a URLSearchParams object
*
* @param {Object} val The value to test
* @returns {boolean} True if value is a URLSearchParams object, otherwise false
*/
export function isURLSearchParams (val) {
return typeof URLSearchParams !== 'undefined' && val instanceof URLSearchParams
}
/**
* Iterate over an Array or an Object invoking a function for each item.
*
* If `obj` is an Array callback will be called passing
* the value, index, and complete array for each item.
*
* If 'obj' is an Object callback will be called passing
* the value, key, and complete object for each property.
*
* @param {Object|Array} obj The object to iterate
* @param {Function} fn The callback to invoke for each item
*/
export function forEach (obj, fn) {
// Don't bother if no value provided
if (obj === null || typeof obj === 'undefined') {
return
}
// Force an array if not already something iterable
if (typeof obj !== 'object') {
/*eslint no-param-reassign:0*/
obj = [obj]
}
if (isArray(obj)) {
// Iterate over array values
for (var i = 0, l = obj.length; i < l; i++) {
fn.call(null, obj[i], i, obj)
}
} else {
// Iterate over object keys
for (var key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
fn.call(null, obj[key], key, obj)
}
}
}
}
/**
* 是否为boolean
* @param val
* @returns {boolean}
*/
export function isBoolean(val) {
return typeof val === 'boolean'
}
/**
* 是否为真正的对象{} new Object
* @param {any} obj - 检测的对象
* @returns {boolean}
*/
export function isPlainObject(obj) {
return Object.prototype.toString.call(obj) === '[object Object]'
}
/**
* Function equal to merge with the difference being that no reference
* to original objects is kept.
*
* @see merge
* @param {Object} obj1 Object to merge
* @returns {Object} Result of all merge properties
*/
export function deepMerge(/* obj1, obj2, obj3, ... */) {
let result = {}
function assignValue(val, key) {
if (typeof result[key] === 'object' && typeof val === 'object') {
result[key] = deepMerge(result[key], val)
} else if (typeof val === 'object') {
result[key] = deepMerge({}, val)
} else {
result[key] = val
}
}
for (let i = 0, l = arguments.length; i < l; i++) {
forEach(arguments[i], assignValue)
}
return result
}
export function isUndefined (val) {
return typeof val === 'undefined'
}
+264
View File
@@ -0,0 +1,264 @@
/* eslint-disable */
var clone = (function() {
'use strict';
function _instanceof(obj, type) {
return type != null && obj instanceof type;
}
var nativeMap;
try {
nativeMap = Map;
} catch(_) {
// maybe a reference error because no `Map`. Give it a dummy value that no
// value will ever be an instanceof.
nativeMap = function() {};
}
var nativeSet;
try {
nativeSet = Set;
} catch(_) {
nativeSet = function() {};
}
var nativePromise;
try {
nativePromise = Promise;
} catch(_) {
nativePromise = function() {};
}
/**
* Clones (copies) an Object using deep copying.
*
* This function supports circular references by default, but if you are certain
* there are no circular references in your object, you can save some CPU time
* by calling clone(obj, false).
*
* Caution: if `circular` is false and `parent` contains circular references,
* your program may enter an infinite loop and crash.
*
* @param `parent` - the object to be cloned
* @param `circular` - set to true if the object to be cloned may contain
* circular references. (optional - true by default)
* @param `depth` - set to a number if the object is only to be cloned to
* a particular depth. (optional - defaults to Infinity)
* @param `prototype` - sets the prototype to be used when cloning an object.
* (optional - defaults to parent prototype).
* @param `includeNonEnumerable` - set to true if the non-enumerable properties
* should be cloned as well. Non-enumerable properties on the prototype
* chain will be ignored. (optional - false by default)
*/
function clone(parent, circular, depth, prototype, includeNonEnumerable) {
if (typeof circular === 'object') {
depth = circular.depth;
prototype = circular.prototype;
includeNonEnumerable = circular.includeNonEnumerable;
circular = circular.circular;
}
// maintain two arrays for circular references, where corresponding parents
// and children have the same index
var allParents = [];
var allChildren = [];
var useBuffer = typeof Buffer != 'undefined';
if (typeof circular == 'undefined')
circular = true;
if (typeof depth == 'undefined')
depth = Infinity;
// recurse this function so we don't reset allParents and allChildren
function _clone(parent, depth) {
// cloning null always returns null
if (parent === null)
return null;
if (depth === 0)
return parent;
var child;
var proto;
if (typeof parent != 'object') {
return parent;
}
if (_instanceof(parent, nativeMap)) {
child = new nativeMap();
} else if (_instanceof(parent, nativeSet)) {
child = new nativeSet();
} else if (_instanceof(parent, nativePromise)) {
child = new nativePromise(function (resolve, reject) {
parent.then(function(value) {
resolve(_clone(value, depth - 1));
}, function(err) {
reject(_clone(err, depth - 1));
});
});
} else if (clone.__isArray(parent)) {
child = [];
} else if (clone.__isRegExp(parent)) {
child = new RegExp(parent.source, __getRegExpFlags(parent));
if (parent.lastIndex) child.lastIndex = parent.lastIndex;
} else if (clone.__isDate(parent)) {
child = new Date(parent.getTime());
} else if (useBuffer && Buffer.isBuffer(parent)) {
if (Buffer.from) {
// Node.js >= 5.10.0
child = Buffer.from(parent);
} else {
// Older Node.js versions
child = new Buffer(parent.length);
parent.copy(child);
}
return child;
} else if (_instanceof(parent, Error)) {
child = Object.create(parent);
} else {
if (typeof prototype == 'undefined') {
proto = Object.getPrototypeOf(parent);
child = Object.create(proto);
}
else {
child = Object.create(prototype);
proto = prototype;
}
}
if (circular) {
var index = allParents.indexOf(parent);
if (index != -1) {
return allChildren[index];
}
allParents.push(parent);
allChildren.push(child);
}
if (_instanceof(parent, nativeMap)) {
parent.forEach(function(value, key) {
var keyChild = _clone(key, depth - 1);
var valueChild = _clone(value, depth - 1);
child.set(keyChild, valueChild);
});
}
if (_instanceof(parent, nativeSet)) {
parent.forEach(function(value) {
var entryChild = _clone(value, depth - 1);
child.add(entryChild);
});
}
for (var i in parent) {
var attrs = Object.getOwnPropertyDescriptor(parent, i);
if (attrs) {
child[i] = _clone(parent[i], depth - 1);
}
try {
var objProperty = Object.getOwnPropertyDescriptor(parent, i);
if (objProperty.set === 'undefined') {
// no setter defined. Skip cloning this property
continue;
}
child[i] = _clone(parent[i], depth - 1);
} catch(e){
if (e instanceof TypeError) {
// when in strict mode, TypeError will be thrown if child[i] property only has a getter
// we can't do anything about this, other than inform the user that this property cannot be set.
continue
} else if (e instanceof ReferenceError) {
//this may happen in non strict mode
continue
}
}
}
if (Object.getOwnPropertySymbols) {
var symbols = Object.getOwnPropertySymbols(parent);
for (var i = 0; i < symbols.length; i++) {
// Don't need to worry about cloning a symbol because it is a primitive,
// like a number or string.
var symbol = symbols[i];
var descriptor = Object.getOwnPropertyDescriptor(parent, symbol);
if (descriptor && !descriptor.enumerable && !includeNonEnumerable) {
continue;
}
child[symbol] = _clone(parent[symbol], depth - 1);
Object.defineProperty(child, symbol, descriptor);
}
}
if (includeNonEnumerable) {
var allPropertyNames = Object.getOwnPropertyNames(parent);
for (var i = 0; i < allPropertyNames.length; i++) {
var propertyName = allPropertyNames[i];
var descriptor = Object.getOwnPropertyDescriptor(parent, propertyName);
if (descriptor && descriptor.enumerable) {
continue;
}
child[propertyName] = _clone(parent[propertyName], depth - 1);
Object.defineProperty(child, propertyName, descriptor);
}
}
return child;
}
return _clone(parent, depth);
}
/**
* Simple flat clone using prototype, accepts only objects, usefull for property
* override on FLAT configuration object (no nested props).
*
* USE WITH CAUTION! This may not behave as you wish if you do not know how this
* works.
*/
clone.clonePrototype = function clonePrototype(parent) {
if (parent === null)
return null;
var c = function () {};
c.prototype = parent;
return new c();
};
// private utility functions
function __objToStr(o) {
return Object.prototype.toString.call(o);
}
clone.__objToStr = __objToStr;
function __isDate(o) {
return typeof o === 'object' && __objToStr(o) === '[object Date]';
}
clone.__isDate = __isDate;
function __isArray(o) {
return typeof o === 'object' && __objToStr(o) === '[object Array]';
}
clone.__isArray = __isArray;
function __isRegExp(o) {
return typeof o === 'object' && __objToStr(o) === '[object RegExp]';
}
clone.__isRegExp = __isRegExp;
function __getRegExpFlags(re) {
var flags = '';
if (re.global) flags += 'g';
if (re.ignoreCase) flags += 'i';
if (re.multiline) flags += 'm';
return flags;
}
clone.__getRegExpFlags = __getRegExpFlags;
return clone;
})();
export default clone
+13
View File
@@ -0,0 +1,13 @@
// 引入配置
import config from '@/common/config'
// 初始化请求配置
uni.$tn.http.setConfig((defaultConfig) => {
// defaultConfig 为默认全局配置
defaultConfig.baseURL = config.baseUrl // 根域名
return defaultConfig
})
module.exports = (vm) => {
require('./requestInterceptors')(vm)
require('./responseInterceptors')(vm)
}
+15
View File
@@ -0,0 +1,15 @@
/**
* 请求拦截
* @param {Object} http
*/
module.exports = (vm) => {
uni.$tn.http.interceptors.request.use((config) => { // 可以使用async await 做异步操作
// 初始化请求拦截器时,会执行此方法,此时data为undefined,默认赋予{}
config.data = config.data || {}
// 可以在此通过vm引用vuex中的变量,具体值在vm.vuex_[name]中
// console.log(vm.vuex_user);
return config
}, (config) => { // 可以使用async await 做异步操作
Promise.reject(config)
})
}
+28
View File
@@ -0,0 +1,28 @@
/**
* 相应拦截
* @param {Object} http
*/
module.exports = (vm) => {
uni.$tn.http.interceptors.response.use((response) => { // 可以使用async await 做异步操作
const data = response.data
// 自定义参数
const custom = response.config?.custom
// 服务端返回的状态码不等于200,则reject()
if (data.code !== 200) {
// 如果没有显式定义custom的toast参数为false的话,默认对报错进行toast弹出提示
if (custom.toast !== false) {
uni.$tn.message.toast(data.message)
}
// 如果需要catch返回,则进行reject
if (custom?.catch) {
return Promise.reject(data)
} else {
// 返回pending中的promise
return new Promise(() => {})
}
}
return data.data || {}
}, (response) => { // 对响应错误做点什么 statusCode !== 200
return Promise.reject(response)
})
}