mirror of
https://gitee.com/TSpecific/tuniao-ui.git
synced 2026-06-06 19:44:38 +08:00
优化代码,修复已知bug
This commit is contained in:
@@ -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.
|
||||
@@ -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的信息。
|
||||
+86
-24
@@ -8,30 +8,53 @@
|
||||
<!-- 页面内容 -->
|
||||
<view :style="{paddingTop: vuex_custom_bar_height + 'px'}">
|
||||
|
||||
<tn-sticky :customNavHeight="vuex_custom_bar_height">
|
||||
<view class="search-fixed">
|
||||
<view class="search-content">
|
||||
<input class="search-content__input" placeholder-class="search-content__input-placeholder" placeholder="请输入图标名称吖" @input="saerchInput" />
|
||||
<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>
|
||||
</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
|
||||
<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': index === currentIconIndex}]"
|
||||
@click="clickIcon(index, item.name)"
|
||||
>
|
||||
: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-${item.name}`]"></view>
|
||||
<view :class="[`tn-icon-${icons_item.icon}`]"></view>
|
||||
</view>
|
||||
<view class="icon__item--title tn-text-ellipsis">{{ item.name }}</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目前400+,里面缺少你想要的吗?</view>
|
||||
<view>UI期待你的需求留言</view>
|
||||
<view>后续图标多了,加上分类</view>
|
||||
<view>
|
||||
icon目前700+,支持
|
||||
<text class="tn-color-orange tn-text-lg tn-padding-xs">中文、英文</text>
|
||||
搜索
|
||||
</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>
|
||||
@@ -53,32 +76,71 @@
|
||||
// 用户输入的内容
|
||||
searchValue: '',
|
||||
// 当前点击的图标序号
|
||||
currentIconIndex: -1
|
||||
currentIconIndex: -1,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
resultIconList() {
|
||||
if (this.searchValue === '') return this.iconList
|
||||
return this.iconList.filter((item) => {
|
||||
return item.name.includes(this.searchValue)
|
||||
//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) {
|
||||
this.searchValue = e.target.value
|
||||
//重新设置为-1,避免选中之前选中的图标
|
||||
this.currentIconIndex=-1;
|
||||
this.searchValue = e.target.value;
|
||||
this.resultIconList
|
||||
},
|
||||
// 点击图标
|
||||
clickIcon(index, name) {
|
||||
clickIcon(index, name ,icon) {
|
||||
this.currentIconIndex = index
|
||||
this.$tn.message.toast(name, false, null, 'none', 5000)
|
||||
// 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>
|
||||
.search-fixed {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
transition: all 0.25s ease-out;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* 搜索框 start */
|
||||
.search-content {
|
||||
@@ -102,12 +164,14 @@
|
||||
font-size: 24rpx;
|
||||
}
|
||||
}
|
||||
|
||||
/* 搜索框 end */
|
||||
|
||||
/* 图标容器 start */
|
||||
.icon-shadow{
|
||||
.icon-shadow {
|
||||
box-shadow: 0rpx 0rpx 80rpx 0rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.icon {
|
||||
&__container {
|
||||
margin-bottom: 30rpx;
|
||||
@@ -158,12 +222,10 @@
|
||||
|
||||
&--title {
|
||||
width: 100%;
|
||||
color: #78909C;
|
||||
font-size: 28rpx;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* 图标容器 end */
|
||||
|
||||
/* 图标容器 end */
|
||||
</style>
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -8,18 +8,18 @@
|
||||
<!-- 页面内容 -->
|
||||
<view :style="{paddingTop: vuex_custom_bar_height + 'px'}">
|
||||
|
||||
<dynamic-demo-template ref="demoTemplate" :tips="tips" :sectionList="sectionList" :full="true" @click="click">
|
||||
<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>
|
||||
<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)"
|
||||
>
|
||||
<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>
|
||||
@@ -27,7 +27,12 @@
|
||||
</view>
|
||||
</block>
|
||||
<block v-else>
|
||||
{{ licensePlateValue[index] || '' }}
|
||||
<block v-if="licensePlateValue[index]===''">
|
||||
|
||||
</block>
|
||||
<block v-else>
|
||||
{{ licensePlateValue[index]}}
|
||||
</block>
|
||||
</block>
|
||||
</view>
|
||||
<view class="car-point" v-if="index === 1">
|
||||
@@ -40,19 +45,9 @@
|
||||
</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>
|
||||
<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>
|
||||
|
||||
@@ -62,7 +57,9 @@
|
||||
import dynamicDemoTemplate from '@/libs/components/dynamic-demo-template.vue'
|
||||
export default {
|
||||
name: 'componentsKeyboard',
|
||||
components: {dynamicDemoTemplate},
|
||||
components: {
|
||||
dynamicDemoTemplate
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
value: false,
|
||||
@@ -76,44 +73,41 @@
|
||||
// 输入的值
|
||||
inputValue: '',
|
||||
// 输入的车牌
|
||||
licensePlateValue: ['','','','','','','',''],
|
||||
licensePlateValue: ['', '', '', '', '', '', '', ''],
|
||||
// 当前选择输入的车牌号码位置
|
||||
currentLicensePlateIndex: 0,
|
||||
|
||||
tips: ['无需依赖额外的样式文件','使用tn-keyboard组件'],
|
||||
sectionList: [
|
||||
{
|
||||
tips: ['无需依赖额外的样式文件', '使用tn-keyboard组件'],
|
||||
sectionList: [{
|
||||
name: '参数切换',
|
||||
section: [
|
||||
{
|
||||
section: [{
|
||||
title: '模式',
|
||||
optional: ['数字','身份证','车牌'],
|
||||
optional: ['数字', '身份证', '车牌'],
|
||||
methods: 'modeChange'
|
||||
},
|
||||
{
|
||||
title: '显示点',
|
||||
optional: ['显示','隐藏'],
|
||||
optional: ['显示', '隐藏'],
|
||||
methods: 'dotEnabledChange'
|
||||
},
|
||||
{
|
||||
title: '打乱顺序',
|
||||
optional: ['是','否'],
|
||||
optional: ['是', '否'],
|
||||
methods: 'randomEnabledChange',
|
||||
current: 1
|
||||
},
|
||||
{
|
||||
title: '显示顶部工具栏',
|
||||
optional: ['显示','隐藏'],
|
||||
optional: ['显示', '隐藏'],
|
||||
methods: 'tooltipChange'
|
||||
},
|
||||
{
|
||||
title: '遮罩显示',
|
||||
optional: ['是','否'],
|
||||
optional: ['是', '否'],
|
||||
methods: 'maskChange'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}]
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@@ -162,7 +156,7 @@
|
||||
break
|
||||
case 2:
|
||||
this.mode = 'car'
|
||||
this.licensePlateValue = ['','','','','','','','']
|
||||
this.licensePlateValue = ['', '', '', '', '', '', '', '']
|
||||
this.$refs.demoTemplate.updateSectionBtnsState(1, false)
|
||||
break
|
||||
}
|
||||
@@ -260,7 +254,6 @@
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.number-value {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -64,7 +64,11 @@
|
||||
{
|
||||
value: 5,
|
||||
label: '胖虎'
|
||||
}
|
||||
},
|
||||
{
|
||||
value: 6,
|
||||
label: '大星'
|
||||
},
|
||||
],
|
||||
maskCloseable: true,
|
||||
|
||||
|
||||
@@ -22,4 +22,7 @@ const app = new Vue({
|
||||
...App
|
||||
})
|
||||
|
||||
// 引入请求封装
|
||||
require('./util/request/index')(app)
|
||||
|
||||
app.$mount()
|
||||
+3
-2
@@ -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" : {
|
||||
|
||||
@@ -237,9 +237,9 @@ export default {
|
||||
},
|
||||
{
|
||||
icon: 'rocket',
|
||||
title: '期待你的加入',
|
||||
author: '期待你的加入',
|
||||
url: '/templatePage/life/candle/candle'
|
||||
title: '微信红包封面',
|
||||
author: '微信红包封面',
|
||||
url: '/templatePage/life/cover/cover'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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 |
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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',
|
||||
// 当前swiperItem的index序号
|
||||
index: 0,
|
||||
// swiperItem的的数量
|
||||
swiperItemLength: 0
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.parent = false
|
||||
this.updateParentData()
|
||||
// 获取当前父组件children的数量作为当前swiperItem的序号
|
||||
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的序号设置偏移位置
|
||||
// 判断当前swiperItem是否为第一个,如果是则将最后的swiperItem移动到当前的前一个位置(即最前面)
|
||||
if (currentIndex === 0 && this.index === swiperItemLength - 1) {
|
||||
if (this.parentData.vertical) {
|
||||
this.translateX = 0
|
||||
this.translateY = -100
|
||||
} else {
|
||||
this.translateX = -100
|
||||
this.translateY = 0
|
||||
}
|
||||
}
|
||||
// 判断当前swiperItem是否为最后一个,如果是则将最前的swiperItem移动到当前的后一个位置(即最后面)
|
||||
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)
|
||||
})
|
||||
|
||||
},
|
||||
// 根据swiperIndex更新swiperItemContainer的样式
|
||||
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
|
||||
}
|
||||
},
|
||||
// 更新swiperContainer和swiperItem相关联信息
|
||||
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>
|
||||
@@ -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
|
||||
}
|
||||
@@ -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时,
|
||||
// // 会触发watch,导致重新把原来的图片再次添加到this.lists
|
||||
// // 数组的some方法意思是,只要数组元素有任意一个元素条件符合,就返回true,而另一个数组的every方法的意思是数组所有元素都符合条件才返回true
|
||||
// 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正常),会导致父组件定义的函数体中的this变成子组件的this
|
||||
// 通过bind()方法,绑定父组件的this,让this的this为父组件的上下文
|
||||
// 因为upload组件可能会被嵌套在其他组件内,比如tn-form,这时this.$parent其实为tn-form的this,
|
||||
// 非页面的this,所以这里需要往上历遍,一直寻找到最顶端的$parent,这里用了this.$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 => {
|
||||
// 判断啊是否为json字符串,将其转换为json格式
|
||||
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时,
|
||||
// 会触发watch,导致重新把原来的图片再次添加到this.lists
|
||||
// 数组的some方法意思是,只要数组元素有任意一个元素条件符合,就返回true,而另一个数组的every方法的意思是数组所有元素都符合条件才返回true
|
||||
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,
|
||||
@@ -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,6 +406,44 @@
|
||||
}
|
||||
}
|
||||
// 检查上传地址
|
||||
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
|
||||
@@ -437,6 +486,7 @@
|
||||
this.$emit('on-progress', res, index, this.lists, this.index)
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
// 上传失败
|
||||
uploadError(index, err) {
|
||||
|
||||
@@ -1,23 +1,12 @@
|
||||
<template>
|
||||
<view
|
||||
:id="elId"
|
||||
class="tn-rate-class tn-rate"
|
||||
@touchmove.stop.prevent="touchMove"
|
||||
>
|
||||
<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"
|
||||
<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)"
|
||||
>
|
||||
: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 v-if="showHalfIcon(index)" class="tn-rate__wrap__icon--half" :class="[`tn-icon-${elActionIcon}`]"
|
||||
:style="[halfIconStyle]"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -85,14 +74,14 @@
|
||||
// 自定义颜色
|
||||
colors: {
|
||||
type: Array,
|
||||
default() {
|
||||
default () {
|
||||
return []
|
||||
}
|
||||
},
|
||||
// 自定义图标
|
||||
icons: {
|
||||
type: Array,
|
||||
default() {
|
||||
default () {
|
||||
return []
|
||||
}
|
||||
}
|
||||
@@ -187,14 +176,17 @@
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value(val) {
|
||||
this.activeIndex = val
|
||||
if (this.allowHalf && (val % 1 === 0.5)) {
|
||||
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(() => {
|
||||
@@ -210,13 +202,13 @@
|
||||
methods: {
|
||||
// 获取评分组件盒子的布局信息
|
||||
getElRectById() {
|
||||
this._tGetRect('#'+this.elId).then(res => {
|
||||
this._tGetRect('#' + this.elId).then(res => {
|
||||
this.starBoxLeft = res.left
|
||||
})
|
||||
},
|
||||
// 获取单个星星的尺寸
|
||||
getElRectByClass() {
|
||||
this._tGetRect('.'+this.elClass).then(res => {
|
||||
this._tGetRect('.' + this.elClass).then(res => {
|
||||
this.starWidth = res.width
|
||||
// 把每个星星最右边到盒子最左边的距离
|
||||
for (let i = 0; i < this.count; i++) {
|
||||
@@ -307,7 +299,6 @@
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-rate {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -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>
|
||||
@@ -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,6 +294,7 @@
|
||||
if (tmp && tmp.extra) data.extra = tmp.extra
|
||||
this.selectValue.push(data)
|
||||
}
|
||||
// console.log("默认",this.selectValue)
|
||||
},
|
||||
// 列选项
|
||||
columnChange(event) {
|
||||
@@ -359,6 +406,21 @@
|
||||
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;
|
||||
|
||||
@@ -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
|
||||
|
||||
// 记录当前滑动开始的x,y坐标
|
||||
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
|
||||
|
||||
// 记录当前滑动开始的x,y坐标
|
||||
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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
//如果未开启easycom模式,请自行引入tn-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>
|
||||
//如果未开启easycom模式,请自行引入tn-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 []
|
||||
}
|
||||
},
|
||||
// 数据的id值,根据id值对数据执行删除操作
|
||||
// 如数据为:{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节点查询和定时器,数组有可能会变成空[],导致item的值为undefined
|
||||
// 解决多次快速滚动会导致数据乱的问题
|
||||
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>
|
||||
+1160
-27
File diff suppressed because one or more lines are too long
@@ -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
|
||||
@@ -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。默认60000。H5(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
@@ -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;
|
||||
@@ -0,0 +1,2 @@
|
||||
import Request from './core/Request'
|
||||
export default Request
|
||||
@@ -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'
|
||||
}
|
||||
@@ -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
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user