init
This commit is contained in:
21
uni_modules/nutui-uni/LICENSE
Normal file
21
uni_modules/nutui-uni/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 yang1206
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
83
uni_modules/nutui-uni/README.md
Normal file
83
uni_modules/nutui-uni/README.md
Normal file
@@ -0,0 +1,83 @@
|
||||
<p align="center">
|
||||
<img src="https://s2.loli.net/2023/08/30/1AxH9rbqi4kvCls.png"
|
||||
width="100"
|
||||
height="100" style="max-width: 100%;" alt="logo" />
|
||||
</p>
|
||||
<h1 align="center">nutui-uniapp</h1>
|
||||
<p align="center">京东风格的轻量级 uni-app 组件库,支持移动端 H5 和 小程序开发</p>
|
||||
<p align="center">
|
||||
<a href="https://github.com/nutui-uniapp/nutui-uniapp">
|
||||
<img alt="GitHub Repo stars" src="https://img.shields.io/github/stars/nutui-uniapp/nutui-uniapp?logo=github&color=%234d80f0&link=https%3A%2F%2Fgithub.com%2nutui-uniapp%2Fnutui-uniapp">
|
||||
</a>
|
||||
<a href="https://www.npmjs.com/package/nutui-uniapp">
|
||||
<img alt="npm" src="https://img.shields.io/npm/v/nutui-uniapp?logo=npm&color=%234d80f0&link=https%3A%2F%2Fwww.npmjs.com%2Fpackage%2Fnutui-uniapp">
|
||||
</a>
|
||||
<a href="https://www.npmjs.com/package/nutui-uniapp">
|
||||
<img alt="npm" src="https://img.shields.io/npm/dw/nutui-uniapp?logo=npm&link=https%3A%2F%2Fwww.npmjs.com%2Fpackage%2Fnutui-uniapp">
|
||||
</a>
|
||||
<a href="https://www.npmjs.com/package/nutui-uniapp">
|
||||
<img src="https://img.shields.io/npm/dt/nutui-uniapp?style=flat-square" alt="downloads">
|
||||
</a>
|
||||
<a href="https://app.netlify.com/sites/nutui-uniapp/deploys">
|
||||
<img src="https://api.netlify.com/api/v1/badges/dbbf78de-0649-4b88-a06a-f3b18d053776/deploy-status" alt="netlify" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<img src="https://s2.loli.net/2023/07/05/eJwPvqCY8EcZ7Vi.png" width="164" alt="NutUI" />
|
||||
|
||||
<img src="https://s2.loli.net/2023/07/05/QyW2RHcmnuvIFwp.jpg" width="166" title="请用微信扫码" alt="NutUI>
|
||||
|
||||
</p>
|
||||
|
||||
## 介绍
|
||||
|
||||
nutui-uniapp 组件库,基于Taro版[`NutUi`](https://nutui.jd.com/#/) 4.x版本修改而来,适配了uni-app, 使用 Vue 技术栈开发小程序应用,开箱即用,拥有丰富的业务组件。
|
||||
|
||||
## 特性
|
||||
|
||||
- 🚀 80+ 高质量组件,覆盖移动端主流场景
|
||||
- 💪 支持一套代码同时开发多端
|
||||
- 📖 基于京东 APP 10.0 视觉规范
|
||||
- 🍭 支持按需引用
|
||||
- 💪 支持 TypeScript
|
||||
- 💪 支持动态定制主题
|
||||
- 🍭 支持暗黑模式
|
||||
- 🌍 支持国际化
|
||||
|
||||
## 快速上手
|
||||
|
||||
请参考[快速上手](https://nutui-uniapp.pages.dev/guide/quick-start.html)。
|
||||
|
||||
## 链接
|
||||
|
||||
- [意见反馈](https://github.com/nutui-uniapp/nutui-uniapp/issues)
|
||||
- [更新日志](https://github.com/nutui-uniapp/nutui-uniapp/releases)
|
||||
- [常见问题](https://nutui-uniapp.pages.dev/guide/faq.html)
|
||||
- [Discussions 讨论区](https://github.com/nutui-uniapp/nutui-uniapp/discussions)
|
||||
|
||||
## 贡献指南
|
||||
|
||||
修改代码请阅读我们的 [贡献指南](https://github.com/nutui-uniapp/nutui-uniapp/blob/main/CONTRIBUTING.md)。
|
||||
|
||||
使用过程中发现任何问题都可以提 [Issue](https://github.com/nutui-uniapp/nutui-uniapp/issues) 给我们,当然,我们也非常欢迎你给我们发 [PR](https://github.com/nutui-uniapp/nutui-uniapp/pulls)。
|
||||
|
||||
## 贡献者们
|
||||
|
||||
感谢以下所有给 nutui-uniapp 贡献过代码的 [开发者](https://github.com/nutui-uniapp/nutui-uniapp/graphs/contributors)。
|
||||
|
||||
<a href="https://github.com/nutui-uniapp/nutui-uniapp/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=nutui-uniapp/nutui-uniapp" alt="contributors" />
|
||||
</a>
|
||||
|
||||
## 感谢
|
||||
|
||||
- [ano-ui](https://github.com/ano-ui/ano-ui)
|
||||
- [NutUi](https://github.com/jdf2e/nutui)
|
||||
- [Uni-NutUi](https://github.com/jwaterwater/uni-nutui)
|
||||
- [vin-ui](https://github.com/vingogo/vin-ui)
|
||||
- [uni-helper](https://github.com/uni-helper)
|
||||
|
||||
## License
|
||||
|
||||
[MIT](https://github.com/nutui-uniapp/nutui-uniapp/blob/main/LICENSE) License © 2023-PRESENT [Yang1206](https://github.com/yang1206) and all contributors.
|
||||
4318
uni_modules/nutui-uni/attributes.json
Normal file
4318
uni_modules/nutui-uni/attributes.json
Normal file
File diff suppressed because it is too large
Load Diff
885
uni_modules/nutui-uni/changelog.md
Normal file
885
uni_modules/nutui-uni/changelog.md
Normal file
@@ -0,0 +1,885 @@
|
||||
## 1.8.0(2024-10-14)
|
||||
### Bug Fixes
|
||||
|
||||
* **dialog:** 修复小程序平台启用 `close-on-popstate` 时 `onMounted` 报错 ([#435](https://github.com/nutui-uniapp/nutui-uniapp/issues/435)) ([93127f5](https://github.com/nutui-uniapp/nutui-uniapp/commit/93127f557ad6cbbf04b88611868f9fb59a786875))
|
||||
* **popup:** 修复 `visible` 初始值为 `true` 时丢失 `z-index` 问题 ([#437](https://github.com/nutui-uniapp/nutui-uniapp/issues/437)) ([47adc2a](https://github.com/nutui-uniapp/nutui-uniapp/commit/47adc2a17ac85413d310d7849186782b1414e809))
|
||||
* **short-password:** 修复 `close-on-click-overlay` 属性无效 ([#436](https://github.com/nutui-uniapp/nutui-uniapp/issues/436)) ([e62dfb0](https://github.com/nutui-uniapp/nutui-uniapp/commit/e62dfb0ac69fa2b7c4834d640dee0426d43cb5b8))
|
||||
|
||||
# [1.8.0](https://github.com/nutui-uniapp/nutui-uniapp/compare/v1.7.17...v1.8.0) (2024-10-14)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **dialog:** 修复小程序平台启用 `close-on-popstate` 时 `onMounted` 报错 ([#435](https://github.com/nutui-uniapp/nutui-uniapp/issues/435)) ([93127f5](https://github.com/nutui-uniapp/nutui-uniapp/commit/93127f557ad6cbbf04b88611868f9fb59a786875))
|
||||
* **popup:** 修复 `visible` 初始值为 `true` 时丢失 `z-index` 问题 ([#437](https://github.com/nutui-uniapp/nutui-uniapp/issues/437)) ([47adc2a](https://github.com/nutui-uniapp/nutui-uniapp/commit/47adc2a17ac85413d310d7849186782b1414e809))
|
||||
* **short-password:** 修复 `close-on-click-overlay` 属性无效 ([#436](https://github.com/nutui-uniapp/nutui-uniapp/issues/436)) ([e62dfb0](https://github.com/nutui-uniapp/nutui-uniapp/commit/e62dfb0ac69fa2b7c4834d640dee0426d43cb5b8))
|
||||
|
||||
## [1.7.17](https://github.com/xiaohe0601/nutui-uniapp/compare/v1.7.16...v1.7.17) (2024-09-18)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **inputnumber:** 新增step-strictly属性 ([#421](https://github.com/xiaohe0601/nutui-uniapp/issues/421)) ([c0a3c47](https://github.com/xiaohe0601/nutui-uniapp/commit/c0a3c47099f19db14716704d5ba2b52dc31541ec))
|
||||
* **inputnumber:** 修复失焦后未正确按照最大/最小值修改输入值 ([#420](https://github.com/xiaohe0601/nutui-uniapp/issues/420)) ([be3059f](https://github.com/xiaohe0601/nutui-uniapp/commit/be3059f5e82e713a7df5f33eb31cc8fc89b7766a))
|
||||
|
||||
### Features
|
||||
|
||||
* **cell:** 新增title-width属性 ([#418](https://github.com/xiaohe0601/nutui-uniapp/issues/418)) ([3079495](https://github.com/xiaohe0601/nutui-uniapp/commit/3079495a522af1ae75073efa0830dde43835bd00))
|
||||
|
||||
## [1.7.16](https://github.com/xiaohe0601/nutui-uniapp/compare/v1.7.15...v1.7.16) (2024-09-04)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **popover,shakedice,tour:** fix scss warning ([#413](https://github.com/xiaohe0601/nutui-uniapp/issues/413)) ([28df492](https://github.com/xiaohe0601/nutui-uniapp/commit/28df492f6daa00da26e72f619e8f2d5073489785))
|
||||
|
||||
### Features
|
||||
|
||||
* **notify:** 新增useNotify ([873b454](https://github.com/xiaohe0601/nutui-uniapp/commit/873b4545ec277b6227ea90d23d8880d0d531edb8))
|
||||
|
||||
## [1.7.15](https://github.com/nutui-uniapp/nutui-uniapp/compare/v1.7.14...v1.7.15) (2024-08-17)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复部分机型不支持css inset ([#400](https://github.com/nutui-uniapp/nutui-uniapp/issues/400)) ([f282aad](https://github.com/nutui-uniapp/nutui-uniapp/commit/f282aad819bad85060f2ec6b4302df9c7e1ad61c))
|
||||
* **number-keyboard:** 修复部分机型不支持inset ([#396](https://github.com/nutui-uniapp/nutui-uniapp/issues/396)) ([d06336e](https://github.com/nutui-uniapp/nutui-uniapp/commit/d06336e2996fbe264b56de7d8e142c9a5368ddea))
|
||||
* **range:** 修复可能存在flex-shrink为0时的样式异常 ([0f3ced7](https://github.com/nutui-uniapp/nutui-uniapp/commit/0f3ced77bdb2edc0c3bbd1691af8a6916cc39da3))
|
||||
* **short-password:** 修复部分机型不支持inset ([#397](https://github.com/nutui-uniapp/nutui-uniapp/issues/397)) ([7d4eb7e](https://github.com/nutui-uniapp/nutui-uniapp/commit/7d4eb7ed4caf13a0ff9b76411602e0e359a8a359))
|
||||
* **short-password:** 修复忘记密码文本对齐问题 ([#393](https://github.com/nutui-uniapp/nutui-uniapp/issues/393)) ([bb000ae](https://github.com/nutui-uniapp/nutui-uniapp/commit/bb000ae7dc1add2f58a5a1927c9a12339f8dcbe9))
|
||||
* **sticky:** 修复降级为fixed实现时效果异常 ([#402](https://github.com/nutui-uniapp/nutui-uniapp/issues/402)) ([15f8338](https://github.com/nutui-uniapp/nutui-uniapp/commit/15f83380e467dfbd3fad6cfe77e157668b024260))
|
||||
* update type declaration from '@vue/runtime-core' to 'vue' for Vu… ([#407](https://github.com/nutui-uniapp/nutui-uniapp/issues/407)) ([14d9d78](https://github.com/nutui-uniapp/nutui-uniapp/commit/14d9d785b71b0a8be3437b286f42b2fda5340d42))
|
||||
|
||||
### Features
|
||||
|
||||
* **form:** 为form组件的validate方法的返回添加类型声明 ([#389](https://github.com/nutui-uniapp/nutui-uniapp/issues/389)) ([b3e2f3b](https://github.com/nutui-uniapp/nutui-uniapp/commit/b3e2f3b6e93f96f9cbf0cdf3b186b6d4a6fcb4ec))
|
||||
|
||||
## [1.7.14](https://github.com/nutui-uniapp/nutui-uniapp/compare/v1.7.13...v1.7.14) (2024-08-05)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **range:** 修复点击控制导致NaN问题 ([415c87c](https://github.com/nutui-uniapp/nutui-uniapp/commit/415c87c7d7738d57f4406387208875409cebfa31))
|
||||
|
||||
### Features
|
||||
|
||||
* **cell:** 新增icon、clickable属性 ([#384](https://github.com/nutui-uniapp/nutui-uniapp/issues/384)) ([3e4ff54](https://github.com/nutui-uniapp/nutui-uniapp/commit/3e4ff54ff558fbe37ace5201aa2721fbd07e659f))
|
||||
* **checkbox:** 新增checked-value、unchecked-value属性 ([#385](https://github.com/nutui-uniapp/nutui-uniapp/issues/385)) ([9ef1b34](https://github.com/nutui-uniapp/nutui-uniapp/commit/9ef1b34f9ac63eb12d54cf60ac5f6ad9c6945043))
|
||||
|
||||
## [1.7.13](https://github.com/nutui-uniapp/nutui-uniapp/compare/v1.7.12...v1.7.13) (2024-07-30)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **avatar:** 修复支付宝小程序不支持??语法 ([b7df6be](https://github.com/nutui-uniapp/nutui-uniapp/commit/b7df6beddf1044a078c67681f75d7f3eb90bf4e6))
|
||||
* **navbar:** 修复 navbar safe-area-inset-top 不生效的问题 ([#379](https://github.com/nutui-uniapp/nutui-uniapp/issues/379)) ([74ff480](https://github.com/nutui-uniapp/nutui-uniapp/commit/74ff480985067e7f0ebeebb74d264fe5aa3aa1d4))
|
||||
* **popup,safearea:** 修复部分低版本系统safearea不生效问题 ([271e24a](https://github.com/nutui-uniapp/nutui-uniapp/commit/271e24a9b206487627bf5b59a364b306e79f50fa))
|
||||
|
||||
### Features
|
||||
|
||||
* **menu:** add scroll-top prop ([#377](https://github.com/nutui-uniapp/nutui-uniapp/issues/377)) ([7b31fdf](https://github.com/nutui-uniapp/nutui-uniapp/commit/7b31fdf9db08a0696a6ce4d6c8c52d437666aa64))
|
||||
|
||||
## [1.7.12](https://github.com/nutui-uniapp/nutui-uniapp/compare/v1.7.11...v1.7.12) (2024-07-09)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **input:** 修复由于[#324](https://github.com/nutui-uniapp/nutui-uniapp/issues/324)、[#325](https://github.com/nutui-uniapp/nutui-uniapp/issues/325)引出的blur事件失效 ([afb5c76](https://github.com/nutui-uniapp/nutui-uniapp/commit/afb5c76bf10972321d79c51c7a240be5eb9559a6))
|
||||
|
||||
## [1.7.11](https://github.com/nutui-uniapp/nutui-uniapp/compare/v1.7.10...v1.7.11) (2024-07-08)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复comment生产环境web端卡死 ([#352](https://github.com/nutui-uniapp/nutui-uniapp/issues/352)) ([be73ccc](https://github.com/nutui-uniapp/nutui-uniapp/commit/be73cccab1afc1d4a93d8c653437bd17b7ce4a23))
|
||||
* **actionsheet:** 修复custom-style表现异常 ([#360](https://github.com/nutui-uniapp/nutui-uniapp/issues/360)) ([25fc6a1](https://github.com/nutui-uniapp/nutui-uniapp/commit/25fc6a12fa943fd1b6c233dc15b529867fd03446))
|
||||
* **input:** 修复小程序端clear清除困难问题 ([#324](https://github.com/nutui-uniapp/nutui-uniapp/issues/324)) ([155b00b](https://github.com/nutui-uniapp/nutui-uniapp/commit/155b00bcb3255763634a0227f197894dd2e1aae2))
|
||||
* **input:** 修复由于capture引起的blur参数丢失 ([#325](https://github.com/nutui-uniapp/nutui-uniapp/issues/325)) ([a1351ad](https://github.com/nutui-uniapp/nutui-uniapp/commit/a1351addfe187914983fad1da4f9039348574eda))
|
||||
* **popup:** 修复z-index表现异常 ([#364](https://github.com/nutui-uniapp/nutui-uniapp/issues/364)) ([cd50fc3](https://github.com/nutui-uniapp/nutui-uniapp/commit/cd50fc31d3b8e13771b30429de003cf6522e81ee))
|
||||
* **toast:** 修复部分情况下toast的duration属性无效 ([1885ded](https://github.com/nutui-uniapp/nutui-uniapp/commit/1885ded405a0cadf00dd2adb9865c98a05f7e682))
|
||||
|
||||
### Features
|
||||
|
||||
* **menuitem:** 新增item-click事件 ([#323](https://github.com/nutui-uniapp/nutui-uniapp/issues/323)) ([df9eeed](https://github.com/nutui-uniapp/nutui-uniapp/commit/df9eeed564f89f3ed111f91f2837c0ec8b0088cc))
|
||||
* **tab-pane:** 新增padding、background样式变量 ([#326](https://github.com/nutui-uniapp/nutui-uniapp/issues/326)) ([bf5ebae](https://github.com/nutui-uniapp/nutui-uniapp/commit/bf5ebae39aa9769c9c212fae0910c2e12621fdec))
|
||||
|
||||
## [1.7.10](https://github.com/nutui-uniapp/nutui-uniapp/compare/v1.7.9...v1.7.10) (2024-05-08)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **input-number:** 修复小程序不支持vBind ([60427bc](https://github.com/nutui-uniapp/nutui-uniapp/commit/60427bc61a23383a42985fa276c40ada4a5c6b6d))
|
||||
|
||||
## [1.7.9](https://github.com/nutui-uniapp/nutui-uniapp/compare/v1.7.8...v1.7.9) (2024-05-08)
|
||||
|
||||
### Features
|
||||
|
||||
* **input-number:** 支持透传属性至 input 元素 ([7547ed0](https://github.com/nutui-uniapp/nutui-uniapp/commit/7547ed0d7f458babb76c19ddd3c5bd093e021474))
|
||||
* **number-keyboard:** 新增 confirm 事件 ([0a6b06c](https://github.com/nutui-uniapp/nutui-uniapp/commit/0a6b06cfd21fe3e7957e082115947e393a7072f7))
|
||||
|
||||
## [1.7.8](https://github.com/nutui-uniapp/nutui-uniapp/compare/v1.7.7...v1.7.8) (2024-04-23)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复useSelectorQuery在app-plus环境获取instance报错 ([#307](https://github.com/nutui-uniapp/nutui-uniapp/issues/307)) ([3499c41](https://github.com/nutui-uniapp/nutui-uniapp/commit/3499c41bd6a3d2420ba32d5272a41b12c8da86d7))
|
||||
* 修改tabs选项卡微笑曲线属性,在navbar组件content slot中样式出错bug ([0f04867](https://github.com/nutui-uniapp/nutui-uniapp/commit/0f048670229498aa5a3dc8ec63c0fe744d8cfad2))
|
||||
* **address:** 修复在选择地址后,下一级地址无法滚动到顶部 ([#301](https://github.com/nutui-uniapp/nutui-uniapp/issues/301)) ([6971ece](https://github.com/nutui-uniapp/nutui-uniapp/commit/6971ece48b5e039f82d184e672556bad5aafb6af))
|
||||
* **cell:** 修复to属性无效 ([#294](https://github.com/nutui-uniapp/nutui-uniapp/issues/294)) ([f691dff](https://github.com/nutui-uniapp/nutui-uniapp/commit/f691dffeed47aed81a83d42aad625526b4efe85a))
|
||||
* **sku:** 移除defineExpose导入 ([e10294b](https://github.com/nutui-uniapp/nutui-uniapp/commit/e10294bfd4f6217df21a2bbc5c620c1587a178e9))
|
||||
* **tabs:** 修复 vertical 方式 tab 数量过多时滚动定位不准确的问题 ([a1334df](https://github.com/nutui-uniapp/nutui-uniapp/commit/a1334df9eebec32ebe0638242922d70ad5a9fe23))
|
||||
* **uploader:** 修复uid生成可能重复 ([1f49808](https://github.com/nutui-uniapp/nutui-uniapp/commit/1f49808b03fbaa0aab5865a2cbe65cbf933ce00c)), closes [#306](https://github.com/nutui-uniapp/nutui-uniapp/issues/306)
|
||||
|
||||
### Features
|
||||
|
||||
* **sku:** 新增重置商品数量方法 ([#305](https://github.com/nutui-uniapp/nutui-uniapp/issues/305)) ([8e8260b](https://github.com/nutui-uniapp/nutui-uniapp/commit/8e8260bfddf884169a8c4f3f271a7dd58bc0c66a))
|
||||
* **switch:** add disabled prop & mark disable deprecated ([#299](https://github.com/nutui-uniapp/nutui-uniapp/issues/299)) ([9e8ac05](https://github.com/nutui-uniapp/nutui-uniapp/commit/9e8ac05e4edd41116fa5f7f80d2cf98f0d3a81cd))
|
||||
|
||||
## [1.7.7](https://github.com/nutui-uniapp/nutui-uniapp/compare/v1.7.6...v1.7.7) (2024-04-12)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **ecard:** 修复输入框表现异常 ([#291](https://github.com/nutui-uniapp/nutui-uniapp/issues/291)) ([5763f25](https://github.com/nutui-uniapp/nutui-uniapp/commit/5763f25855bdd33e0068a9a1c7b2556f1e9dfaaa))
|
||||
* **swipe:** 修复组件宽度超出范围以及点击关闭问题 ([#284](https://github.com/nutui-uniapp/nutui-uniapp/issues/284)) ([48cc120](https://github.com/nutui-uniapp/nutui-uniapp/commit/48cc12026e0ec9befb7bc7215621649dee75032c))
|
||||
|
||||
### Features
|
||||
|
||||
* **datepicker:** 事件新增date参数 ([#289](https://github.com/nutui-uniapp/nutui-uniapp/issues/289)) ([29947b5](https://github.com/nutui-uniapp/nutui-uniapp/commit/29947b5660586650e6d8d281fa353ec47a6e6680))
|
||||
* **tag:** 新增disbaled、close-icon-size属性 ([#293](https://github.com/nutui-uniapp/nutui-uniapp/issues/293)) ([85b329c](https://github.com/nutui-uniapp/nutui-uniapp/commit/85b329cdb897164be082f4abe35c56289db1710c))
|
||||
|
||||
## [1.7.6](https://github.com/nutui-uniapp/nutui-uniapp/compare/v1.7.5...v1.7.6) (2024-04-10)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **input:** 修复H5环境下type为number无效 ([#279](https://github.com/nutui-uniapp/nutui-uniapp/issues/279)) ([ec41815](https://github.com/nutui-uniapp/nutui-uniapp/commit/ec41815ae502f56341575706364e3a0eafeb9253))
|
||||
* **picker:** 修复非H5平台mask缺失 ([96666cf](https://github.com/nutui-uniapp/nutui-uniapp/commit/96666cf7c93c8e38c5b22c3118e2c1a7fe7a2247))
|
||||
* **searchbar:** 修复app端输入框对齐问题 ([#282](https://github.com/nutui-uniapp/nutui-uniapp/issues/282)) ([1f77dab](https://github.com/nutui-uniapp/nutui-uniapp/commit/1f77dab96d3fa0d589e383565c9a1254a309a31a))
|
||||
|
||||
### Features
|
||||
|
||||
* **addresslist:** 新增是否使用插槽相关属性 ([#274](https://github.com/nutui-uniapp/nutui-uniapp/issues/274)) ([1727859](https://github.com/nutui-uniapp/nutui-uniapp/commit/1727859c45d65322271e3f7121b825f10845ccac))
|
||||
* **calendar:** 添加calendar组件点击遮罩关闭事件和点击关闭图标事件 ([#273](https://github.com/nutui-uniapp/nutui-uniapp/issues/273)) ([e70e654](https://github.com/nutui-uniapp/nutui-uniapp/commit/e70e654b57a8884cc4589877f56670652160c33d))
|
||||
|
||||
## [1.7.5](https://github.com/nutui-uniapp/nutui-uniapp/compare/v1.7.4...v1.7.5) (2024-03-29)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **popover:** 修复Popover 气泡弹出框功能问题 ([#269](https://github.com/nutui-uniapp/nutui-uniapp/issues/269)) ([bbf03e5](https://github.com/nutui-uniapp/nutui-uniapp/commit/bbf03e5a973b95d66f76edeb59114872afc606a6))
|
||||
* **list:** List 虚拟列表 listData 类型错误 ([8cba423](https://github.com/nutui-uniapp/nutui-uniapp/commit/8cba4236b178c2def23ccfd07ee1528f7530c007))
|
||||
* **checkbox:** 修复checkbox重复添加至checkboxgroup.children的问题 ([#266](https://github.com/nutui-uniapp/nutui-uniapp/issues/266)) ([3f2c2b3](https://github.com/nutui-uniapp/nutui-uniapp/commit/3f2c2b3afa0348032eadff9ef5aa8415843ecac8))
|
||||
* **dialog:** 修复dialog title响应式失效的问题 ([#267](https://github.com/nutui-uniapp/nutui-uniapp/issues/267)) ([1a0c7cf](https://github.com/nutui-uniapp/nutui-uniapp/commit/1a0c7cf72c448e6e5ff6d7890dd7739e5bbd13b0))
|
||||
|
||||
### Features
|
||||
|
||||
* **toast:** 新增useToast控制方式 ([#260](https://github.com/nutui-uniapp/nutui-uniapp/issues/260)) ([c014e7a](https://github.com/nutui-uniapp/nutui-uniapp/commit/c014e7a8ed31b9ad4d6bdbf11942528abea1bb26))
|
||||
|
||||
### Refactors
|
||||
|
||||
* **drag**: 重构 drag组件,修复drag组件多次拖动时位置瞬移到起始点的问题 ([5989869](<https://github.com/nutui-uniapp/nutui-uniapp/commit/5989869>))
|
||||
|
||||
## [1.7.4](https://github.com/nutui-uniapp/nutui-uniapp/compare/v1.7.3...v1.7.4) (2024-03-20)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **picker:** 修复model-value值污染问题 ([ce2e865](https://github.com/nutui-uniapp/nutui-uniapp/commit/ce2e86546b4c2a03f2f5235899071b00dff98d7f))
|
||||
|
||||
### Features
|
||||
|
||||
* 新增clone、equal工具方法 ([ef1611c](https://github.com/nutui-uniapp/nutui-uniapp/commit/ef1611c44e7382f5a58fbc154de513c15f1a1e59))
|
||||
|
||||
## [1.7.3](https://github.com/nutui-uniapp/nutui-uniapp/compare/v1.7.2...v1.7.3) (2024-03-09)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **elevator:** 修复app端点击索引无效,并且滚动页面索引无法跟随切换问题 ([#237](https://github.com/nutui-uniapp/nutui-uniapp/issues/237)) ([e149974](https://github.com/nutui-uniapp/nutui-uniapp/commit/e1499747ae9997bda7a5679259c211935e4d1713))
|
||||
* **picker:** 修复微信小程序暗黑模式picker背景颜色显示错误的问题 ([#236](https://github.com/nutui-uniapp/nutui-uniapp/issues/236)) ([fa425f7](https://github.com/nutui-uniapp/nutui-uniapp/commit/fa425f72584ff515b4b0a2f5810ca21f4f5760f6))
|
||||
* **picker:** 修复ts类型错误 ([#241](https://github.com/nutui-uniapp/nutui-uniapp/issues/241)) ([1e83035](https://github.com/nutui-uniapp/nutui-uniapp/commit/1e83035dab748c311ae2d207950bf219da2bcb11))
|
||||
* **sku:** 修复默认底部按钮不显示的问题 ([#238](https://github.com/nutui-uniapp/nutui-uniapp/issues/238)) ([6e2c296](https://github.com/nutui-uniapp/nutui-uniapp/commit/6e2c296ad8c2e77a84f504aa5ffe756002f98061))
|
||||
* **sku:** 修复组件内容超出,出现横向滚动条的问题 ([#240](https://github.com/nutui-uniapp/nutui-uniapp/issues/240)) ([c0bf4ab](https://github.com/nutui-uniapp/nutui-uniapp/commit/c0bf4ab341a4adbb142da6d282146825af14601d))
|
||||
* **uploader:** 修复禁用时能删除文件的问题 ([#234](https://github.com/nutui-uniapp/nutui-uniapp/issues/234)) ([834f9bc](https://github.com/nutui-uniapp/nutui-uniapp/commit/834f9bc7b00eaa0e9ebde33d9c56e1c9ed990d54))
|
||||
|
||||
### Features
|
||||
|
||||
* **input,textarea:** 新增输入框单独控制样式属性 ([#233](https://github.com/nutui-uniapp/nutui-uniapp/issues/233)) ([e9749b0](https://github.com/nutui-uniapp/nutui-uniapp/commit/e9749b0faa64d026c4c53e1faf349e1c42154a28))
|
||||
|
||||
## [1.7.2](https://github.com/nutui-uniapp/nutui-uniapp/compare/v1.7.1...v1.7.2) (2024-03-04)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **avatar-group:** 修复z-index无效 ([#215](https://github.com/nutui-uniapp/nutui-uniapp/issues/215)) ([1b64ffc](https://github.com/nutui-uniapp/nutui-uniapp/commit/1b64ffc8be7923ed742cf7e0a7225738657dd81c))
|
||||
* **avatar:** 修复size、shape无效以及无法正确从group继承 ([13ce31a](https://github.com/nutui-uniapp/nutui-uniapp/commit/13ce31a8a80bd6895919d2313869c2dcf2491be7))
|
||||
* **cascader:** 修复leaf等于false并且children为空时可以选择成功的问题 ([#212](https://github.com/nutui-uniapp/nutui-uniapp/issues/212)) ([a3f27f6](https://github.com/nutui-uniapp/nutui-uniapp/commit/a3f27f6cebf36cd549f4ddab4c155efba2a8533a))
|
||||
* **input,textarea:** 修复input事件返回值错误 ([745a19c](https://github.com/nutui-uniapp/nutui-uniapp/commit/745a19c51359d827cc28307f942b6425e02e275f))
|
||||
* **textarea:** 修复类型导出缺失 ([1fa9f29](https://github.com/nutui-uniapp/nutui-uniapp/commit/1fa9f2942256bbb975e2dbf276c7c17d2aa096cb))
|
||||
* **textarea:** 修复autosize类型错误 ([7c2d56a](https://github.com/nutui-uniapp/nutui-uniapp/commit/7c2d56a3a4c962e5b2b6024e1ab68a75032af240))
|
||||
* **uploader:** 修复beforeDelete默认值无效以及不支持promise ([54a9b0a](https://github.com/nutui-uniapp/nutui-uniapp/commit/54a9b0acde8590996729fbf2d750ec6271eb10a6))
|
||||
|
||||
### Features
|
||||
|
||||
* **textarea:** 新增input事件 ([2a8a898](https://github.com/nutui-uniapp/nutui-uniapp/commit/2a8a8984318c71bcd24ab1a06ca5dd134a8afe2c))
|
||||
* **uploader:** 新增accept配置 ([#217](https://github.com/nutui-uniapp/nutui-uniapp/issues/217)) ([a6ba625](https://github.com/nutui-uniapp/nutui-uniapp/commit/a6ba625f0638fdcba1cdd9a34ac0c61dbdb5e095))
|
||||
|
||||
### Reverts
|
||||
|
||||
* **textarea:** 修复autosize类型错误 ([bc02ceb](https://github.com/nutui-uniapp/nutui-uniapp/commit/bc02ceb8a7882ea363b655cec06aba3268e01b45))
|
||||
|
||||
## [1.7.1](https://github.com/nutui-uniapp/nutui-uniapp/compare/v1.7.0...v1.7.1) (2024-02-29)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **form:** 修复prop类型为数组时required校验无效的问题 ([00453d6](https://github.com/nutui-uniapp/nutui-uniapp/commit/00453d68c21973215b57e5248050da4dc19647a2))
|
||||
* **menu:** 偏移位置错误, app 端v-show 无效 ([#197](https://github.com/nutui-uniapp/nutui-uniapp/issues/197)) ([2fba3fe](https://github.com/nutui-uniapp/nutui-uniapp/commit/2fba3fe91b751d63b0aa007442d1ffb94516fd93))
|
||||
* **popup,menu:** 修复 popup组件update:visible事件重复调用与menu 组件close 事件在小程序中无法触发 ([#205](https://github.com/nutui-uniapp/nutui-uniapp/issues/205)) ([0ce7d69](https://github.com/nutui-uniapp/nutui-uniapp/commit/0ce7d690b6a401428ab083eba81887e7e4767178)), closes [#196](https://github.com/nutui-uniapp/nutui-uniapp/issues/196)
|
||||
* **textarea:** 修复ConfirmType导出类型名称重复以及默认值错误 ([748a849](https://github.com/nutui-uniapp/nutui-uniapp/commit/748a8498df99a543d28b37307f877ececa0fe1b4))
|
||||
* **transition:** app端destroyOnClose属性无效 ([1238ef6](https://github.com/nutui-uniapp/nutui-uniapp/commit/1238ef6cd4dd7fe59250d47752e00349f56292a0))
|
||||
|
||||
### Features
|
||||
|
||||
* 新增辅助样式 ([ae3775b](https://github.com/nutui-uniapp/nutui-uniapp/commit/ae3775b501b78ed8af955aa6a3b57c444d49356c))
|
||||
|
||||
# [1.7.0](https://github.com/nutui-uniapp/nutui-uniapp/compare/v1.6.9...v1.7.0) (2024-02-22)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **input:** 修复readonly无效 ([f1547bd](https://github.com/nutui-uniapp/nutui-uniapp/commit/f1547bdb455771926c5384979ab5132d4d6df5ab))
|
||||
* **tabs:** align属性类型错误 ([f9b0eb4](https://github.com/nutui-uniapp/nutui-uniapp/commit/f9b0eb453dc7ce7114c784bb2cc69b5a29eb5d12))
|
||||
|
||||
## [1.6.9](https://github.com/nutui-uniapp/nutui-uniapp/compare/v1.6.8...v1.6.9) (2024-02-22)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **navbar:** 修复在APP上顶部安全距离无效的问题 ([#186](https://github.com/nutui-uniapp/nutui-uniapp/issues/186)) ([e475e7e](https://github.com/nutui-uniapp/nutui-uniapp/commit/e475e7e449e2bddb50484efe0ee8ee03f1febc72))
|
||||
* **navbar:** 修复占位元素重复计算状态栏高度的问题 ([#187](https://github.com/nutui-uniapp/nutui-uniapp/issues/187)) ([e35083c](https://github.com/nutui-uniapp/nutui-uniapp/commit/e35083cda311201492719b0deb64e3097dc2dec0))
|
||||
|
||||
### Features
|
||||
|
||||
* **textarea:** 新增placeholder-style、placeholder-class属性 ([ada249f](https://github.com/nutui-uniapp/nutui-uniapp/commit/ada249ff100f60c2217adb31ab59692a6299f924))
|
||||
|
||||
## [1.6.8](https://github.com/nutui-uniapp/nutui-uniapp/compare/v1.6.7...v1.6.8) (2024-02-20)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **ellipsis:** symbol属性默认值丢失 ([b685f40](https://github.com/nutui-uniapp/nutui-uniapp/commit/b685f40a7192d80e279f91c2784dc76cd0c5327f))
|
||||
* **picker:** className option ([60caafb](https://github.com/nutui-uniapp/nutui-uniapp/commit/60caafb83ee9194cdae85333ae80d4b8864c1c87))
|
||||
* randomid生成错误 ([2556b99](https://github.com/nutui-uniapp/nutui-uniapp/commit/2556b998452bc75597e5fe5d7b1d3ed870ad1cf3))
|
||||
|
||||
### Features
|
||||
|
||||
* **form:** 新增 disabled 属性,支持禁用 form 下全部数据录入组件 ([#184](https://github.com/nutui-uniapp/nutui-uniapp/issues/184)) ([ecd03a3](https://github.com/nutui-uniapp/nutui-uniapp/commit/ecd03a3f3d75aa8281d926d270fa5612d02e360e))
|
||||
* **marquee:** 新增disabled属性 ([#183](https://github.com/nutui-uniapp/nutui-uniapp/issues/183)) ([d64eca2](https://github.com/nutui-uniapp/nutui-uniapp/commit/d64eca23c4124138ccc8a7dc36fe5439a5e8458c))
|
||||
|
||||
## [1.6.7](https://github.com/nutui-uniapp/nutui-uniapp/compare/v1.6.6...v1.6.7) (2024-02-06)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **input:** 移除InputType重复类型 ([15ac2cd](https://github.com/nutui-uniapp/nutui-uniapp/commit/15ac2cd1dd9955feec57df21e8c3b33126e1ab94))
|
||||
* **inputnumber:** 组件输入内容不会触发change事件 ([9d97d4e](https://github.com/nutui-uniapp/nutui-uniapp/commit/9d97d4e6b90d1cd35815328f2b6b66be94b6a806))
|
||||
* **navbar:** 修复zIndex无效问题 ([ecfbd16](https://github.com/nutui-uniapp/nutui-uniapp/commit/ecfbd168a7d392604739e6db6bf811e96c7b3dff))
|
||||
* **noticebar:** remove blank element of left-icon ([160bc37](https://github.com/nutui-uniapp/nutui-uniapp/commit/160bc3763eb0a1b193c914b967814a0b2a728bed))
|
||||
* **table:** data属性ts类型问题 ([91c434c](https://github.com/nutui-uniapp/nutui-uniapp/commit/91c434c669a9a2fed5762150c3aa641c4a61db0a))
|
||||
|
||||
### Features
|
||||
|
||||
* **button:** 新增hover-class相关属性 ([987185c](https://github.com/nutui-uniapp/nutui-uniapp/commit/987185cf8d93f474de5a0df4a34b908575ef2dc7))
|
||||
* **input:** 新增input事件 ([494f10a](https://github.com/nutui-uniapp/nutui-uniapp/commit/494f10ada6143930e421f1a773599cb0fe1f8761))
|
||||
|
||||
## [1.6.6](https://github.com/nutui-uniapp/nutui-uniapp/compare/v1.6.5...v1.6.6) (2024-01-26)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **button:** disabled下 open-type 仍有效 ([af5f5ac](https://github.com/nutui-uniapp/nutui-uniapp/commit/af5f5ac34d9187ac2f0b955935e97d3f815df546)), closes [#173](https://github.com/nutui-uniapp/nutui-uniapp/issues/173)
|
||||
|
||||
## [1.6.5](https://github.com/nutui-uniapp/nutui-uniapp/compare/v1.6.4...v1.6.5) (2024-01-23)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **form:** 修复搭配 input 使用时的样式问题 ([2ce7c20](https://github.com/nutui-uniapp/nutui-uniapp/commit/2ce7c20523694cb8a04dd22bd7a4c533e9ebd297)), closes [#165](https://github.com/nutui-uniapp/nutui-uniapp/issues/165) [#167](https://github.com/nutui-uniapp/nutui-uniapp/issues/167)
|
||||
|
||||
### Features
|
||||
|
||||
* 添加web-types以提升WebStorm开发体验 ([18795e1](https://github.com/nutui-uniapp/nutui-uniapp/commit/18795e1ad86267cca09ff4abc28f0f9b61aaea7e))
|
||||
|
||||
## [1.6.4](https://github.com/nutui-uniapp/nutui-uniapp/compare/v1.6.3...v1.6.4) (2024-01-15)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **calendar:** 优化初次打开默认值定位问题 ([35931ae](https://github.com/nutui-uniapp/nutui-uniapp/commit/35931ae28c60cd9c0502e3a2e65d154b5fa6c793))
|
||||
* **categorypane:** 商品分类面板组件默认插槽不生效 ([1d263e1](https://github.com/nutui-uniapp/nutui-uniapp/commit/1d263e1492e5f831a218d9f9cf57f90466b372a4))
|
||||
* **date-picker:** 修复 hour-minute 类型下选项刷新问题 ([291e721](https://github.com/nutui-uniapp/nutui-uniapp/commit/291e72151a66fb68b0e3968252620ce11d8f0209))
|
||||
* **date-picker:** 修复数据联动更新问题 ([e4d2d03](https://github.com/nutui-uniapp/nutui-uniapp/commit/e4d2d03ffb56da500d0dd2e1d573775a829512d8))
|
||||
* **dialog:** 修复 cancel 按钮的样式问题 ([bb62248](https://github.com/nutui-uniapp/nutui-uniapp/commit/bb62248bedaacc3cbcb3c92f414bcb6dacfaf59e))
|
||||
* **menuitem:** close open 事件无效 ([15b73c6](https://github.com/nutui-uniapp/nutui-uniapp/commit/15b73c6a1a2c28a3f688ea9ada38687ad905be8e)), closes [#163](https://github.com/nutui-uniapp/nutui-uniapp/issues/163)
|
||||
* **picker:** 修复 Safari 下遮罩层级样式问题 ([1d0149e](https://github.com/nutui-uniapp/nutui-uniapp/commit/1d0149e7a5b56aac62ed12a00f91c3f11e57085c))
|
||||
* **picker:** 优化 select、cursor 样式 ([a1ad4b0](https://github.com/nutui-uniapp/nutui-uniapp/commit/a1ad4b0ffd01ac92d99fa397b7ac271099af15b9))
|
||||
|
||||
### Features
|
||||
|
||||
* **card:** 商品卡片组件增加shopName插槽,用于店铺名称自定义 ([996726d](https://github.com/nutui-uniapp/nutui-uniapp/commit/996726ddc73d97994a394999a7484b858e9e6f00))
|
||||
|
||||
## [1.6.3](https://github.com/nutui-uniapp/nutui-uniapp/compare/v1.6.2...v1.6.3) (2024-01-08)
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* **input:** 使用computed代替函数调用 ([374e24c](https://github.com/nutui-uniapp/nutui-uniapp/commit/374e24c34c667a72ca754b84a9c73b7850a57771))
|
||||
|
||||
## [1.6.2](https://github.com/nutui-uniapp/nutui-uniapp/compare/v1.6.1...v1.6.2) (2024-01-08)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **calendar:** 日历组件在微信小程序弹窗展示时,无法滚动到默认日期 ([8f3b3aa](https://github.com/nutui-uniapp/nutui-uniapp/commit/8f3b3aa4d157db1fc842825598441ec78d2723ae))
|
||||
* **calendar:** 日历组件在微信小程序中无法显示今天标记 ([15aadca](https://github.com/nutui-uniapp/nutui-uniapp/commit/15aadcae48b112eee323b2593e616ccb935e8c77))
|
||||
* **calendar:** 优化初始滚动判断条件 ([5961f8e](https://github.com/nutui-uniapp/nutui-uniapp/commit/5961f8edee6ade9835329306a7543f3fcd29cece))
|
||||
* **input:** modelValue为空时错误调用方法 ([782676b](https://github.com/nutui-uniapp/nutui-uniapp/commit/782676bfa69c9c82b1a3483f48a5264ee5e09d8a)), closes [#155](https://github.com/nutui-uniapp/nutui-uniapp/issues/155)
|
||||
|
||||
## [1.6.1](https://github.com/nutui-uniapp/nutui-uniapp/compare/v1.6.0...v1.6.1) (2024-01-04)
|
||||
|
||||
### Features
|
||||
|
||||
* **ecard:** 支持配置是否显示自定与步进 ([584f091](https://github.com/nutui-uniapp/nutui-uniapp/commit/584f091d35e78a2ec02e72bd8578ca091628d666))
|
||||
|
||||
# [1.6.0](https://github.com/nutui-uniapp/nutui-uniapp/compare/v1.5.10...v1.6.0) (2024-01-04)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **input:** form下 input 样式错误 ([4c0f900](https://github.com/nutui-uniapp/nutui-uniapp/commit/4c0f9009a7d2543933644b209d223fab4a10217f)), closes [#148](https://github.com/nutui-uniapp/nutui-uniapp/issues/148)
|
||||
* **tabs:** 修复 placeholder 展示错误 ([754633e](https://github.com/nutui-uniapp/nutui-uniapp/commit/754633e18bef14c37c13068cda9bcb205f1164e7))
|
||||
* **tabs:** 夜间模式样式错误 ([32c679a](https://github.com/nutui-uniapp/nutui-uniapp/commit/32c679a2e716a0f82ddc09d34a4b785ae21192ee))
|
||||
|
||||
### Features
|
||||
|
||||
* **swiper:** 支持修改未选中时的分页指示器的颜色 ([e9c4058](https://github.com/nutui-uniapp/nutui-uniapp/commit/e9c4058baf0f5bccbfd74c3f6e905ba359109eaa))
|
||||
* **tabs:** 开启 title-scroll 时不再需要设置 name ([4085996](https://github.com/nutui-uniapp/nutui-uniapp/commit/4085996b2daf0981e72c97dda5eab1678d1dbda6))
|
||||
* **tabs:** 支持配置标题对齐方式 ([d46c8ff](https://github.com/nutui-uniapp/nutui-uniapp/commit/d46c8fffd9840405a8327abc85edfc3fe5f66b5c))
|
||||
* **watermark:** 支持多行文字水印 ([8b42b97](https://github.com/nutui-uniapp/nutui-uniapp/commit/8b42b97237f9cbc92e99fb12c807c4cdfee1e687))
|
||||
|
||||
## [1.5.10](https://github.com/nutui-uniapp/nutui-uniapp/compare/v1.5.9...v1.5.10) (2023-12-31)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **popup:** app端show属性无效以及destroy-on-close逻辑错误 ([522f620](https://github.com/nutui-uniapp/nutui-uniapp/commit/522f6200e48cdf67ccd1767eb2aa89c697f03a49))
|
||||
* **popup:** destroy-on-close逻辑错误 ([6d9d12c](https://github.com/nutui-uniapp/nutui-uniapp/commit/6d9d12cbf5dd082806b14bb783ce690c1cafc76c))
|
||||
* **sku:** 修复SkuStepper依赖循环,样式错误 ([2575b24](https://github.com/nutui-uniapp/nutui-uniapp/commit/2575b24fb2f92d0d0ee41edbb0174f9528b2ce61))
|
||||
* textarea启用autosize时readonly样式不统一 ([d6c161b](https://github.com/nutui-uniapp/nutui-uniapp/commit/d6c161bce9e8af50dfa162faa9d4b3be8d31a82f))
|
||||
|
||||
## [1.5.9](https://github.com/nutui-uniapp/nutui-uniapp/compare/v1.5.8...v1.5.9) (2023-12-18)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **navbar:** 修复标题居中等样式问题 ([b5ddff1](https://github.com/nutui-uniapp/nutui-uniapp/commit/b5ddff18ebfbc00137dc88d8993d8d6928c2d3c3))
|
||||
* **sku:** 修复小程序下选项内容的滚动问题 ([715a4db](https://github.com/nutui-uniapp/nutui-uniapp/commit/715a4db500fe303ccd5eafc8140b0adb328a3ebb))
|
||||
* **tabs:** 修复嵌套使用时的样式错乱问题 ([a8c9822](https://github.com/nutui-uniapp/nutui-uniapp/commit/a8c98229836318b50aa4c7cae83aedf31949eae3))
|
||||
|
||||
### Features
|
||||
|
||||
* **radio:** 组件设置 button 形状时支持改变大小 ([ff808c0](https://github.com/nutui-uniapp/nutui-uniapp/commit/ff808c06ac6713b69e0383fb4b5b9f6a0544fa3b))
|
||||
|
||||
## [1.5.8](https://github.com/nutui-uniapp/nutui-uniapp/compare/v1.5.7...v1.5.8) (2023-12-14)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **menu:** 去除无效输出 ([3f36ff8](https://github.com/nutui-uniapp/nutui-uniapp/commit/3f36ff8535f662bf90fa09e0d9c97c1aa1775a12))
|
||||
* **menu:** 修复menu闪屏动画错误以及位置偏移 ([48c85f5](https://github.com/nutui-uniapp/nutui-uniapp/commit/48c85f5246f1de10bcf3535e31ef202ee8e28d9f)), closes [#138](https://github.com/nutui-uniapp/nutui-uniapp/issues/138)
|
||||
* **popup:** 修复destroy-on-close无效 ([0e64ee2](https://github.com/nutui-uniapp/nutui-uniapp/commit/0e64ee2c1f89eba08d9438c5e1378d0ba49f495c)), closes [#136](https://github.com/nutui-uniapp/nutui-uniapp/issues/136)
|
||||
|
||||
### Features
|
||||
|
||||
* **safearea:** 新增SafeArea组件 ([e346920](https://github.com/nutui-uniapp/nutui-uniapp/commit/e346920108081d0857143d28dbfbd495351c996e))
|
||||
|
||||
### Reverts
|
||||
|
||||
* Revert "chore: release v1.5.8" ([8c0f8e0](https://github.com/nutui-uniapp/nutui-uniapp/commit/8c0f8e0dfd034a84996b10bafa8c5e1afbbb4535))
|
||||
* Revert "docs: update changelog" ([108a458](https://github.com/nutui-uniapp/nutui-uniapp/commit/108a458dea38728bba55d9b36946cc3720260e14))
|
||||
|
||||
## [1.5.7](https://github.com/yang1206/uniapp-nutui/compare/v1.5.6...v1.5.7) (2023-12-09)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **range:** 优化滑动事件处理逻辑 ([be20418](https://github.com/yang1206/uniapp-nutui/commit/be2041839d2c449e9cf22b0029d437eebb4c68fa))
|
||||
|
||||
### Features
|
||||
|
||||
* **calendar:** 新增 btn-slot属性控制是否使用btn 插槽 ([3b66919](https://github.com/yang1206/uniapp-nutui/commit/3b66919bfd86c0857ce1a8ea265ae5fa9e3f0b9f)), closes [#131](https://github.com/yang1206/uniapp-nutui/issues/131)
|
||||
* **form:** 支持配置表单布局 ([882dce4](https://github.com/yang1206/uniapp-nutui/commit/882dce417ad6799cf18f31e43a6734f5cc7cfea7))
|
||||
|
||||
## [1.5.6](https://github.com/yang1206/uniapp-nutui/compare/v1.5.5...v1.5.6) (2023-11-29)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **avatar:** 修复maxCount属性无效 ([341f488](https://github.com/yang1206/uniapp-nutui/commit/341f48806545f1f7f84cd208d6790f73858d749e))
|
||||
* **numberkeyboard:** 类名错误 ([6a15803](https://github.com/yang1206/uniapp-nutui/commit/6a15803a4e8875fadf9172b2d388a732f2fc3e71))
|
||||
* **utils:** isPromise 判断错误 ([42820e2](https://github.com/yang1206/uniapp-nutui/commit/42820e2ad20c571ea00afc588254ae4dbe5df1b7))
|
||||
|
||||
## [1.5.5](https://github.com/yang1206/uniapp-nutui/compare/v1.5.4...v1.5.5) (2023-11-25)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **formitem:** 提高传入 required 的优先级 ([300ef79](https://github.com/yang1206/uniapp-nutui/commit/300ef797e05c2cacbe2e144bafd1e7402116fc83))
|
||||
* **inputnumber:** 修复步进按钮禁用功能无效 ([75fc3cc](https://github.com/yang1206/uniapp-nutui/commit/75fc3cc78a7dd6154b5418db0306f4ce1dcfc25b))
|
||||
* **swiper:** 修复 swiper-item 宽高错误问题 ([0840982](https://github.com/yang1206/uniapp-nutui/commit/084098288d3511f43db2f4db0b4c723ddc53c3a5))
|
||||
|
||||
## [1.5.4](https://github.com/yang1206/uniapp-nutui/compare/v1.5.3...v1.5.4) (2023-11-23)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **address:** 选择最后一级时 onChange 缺少 value 字段 ([04d9262](https://github.com/yang1206/uniapp-nutui/commit/04d92627b6b08e9f439208a24c627c8975f91906))
|
||||
* **badge:** 修复微信小程序 props编译丢失 ([e95ff0d](https://github.com/yang1206/uniapp-nutui/commit/e95ff0df44abe6cc1a99db1b21b6696135d0c9d6))
|
||||
* **elevator:** 修复滚动后无法点击问题 ([6956662](https://github.com/yang1206/uniapp-nutui/commit/695666260596b9b5e5925451de74bd23556b0af0))
|
||||
* **picker:** 调整 columns 类型定义 ([42f1b9c](https://github.com/yang1206/uniapp-nutui/commit/42f1b9c3b7fc4f6d434c63791b20b84b09d0bcb0))
|
||||
|
||||
### Features
|
||||
|
||||
* **cascader:** 新增title插槽 ([80b2f29](https://github.com/yang1206/uniapp-nutui/commit/80b2f291d66920d990dc15a21752400b35fdd4e4)), closes [#119](https://github.com/yang1206/uniapp-nutui/issues/119)
|
||||
* **form:** formItem的required的星号支持从rules中自动判断是否显示 ([1ddda92](https://github.com/yang1206/uniapp-nutui/commit/1ddda92b4a6c4e312a502be9d10d6db64da80e31))
|
||||
* **input:** modelValue的类型支持number ([2e308e7](https://github.com/yang1206/uniapp-nutui/commit/2e308e741f7c888b5bb96d19b8d2fd02f627db5a))
|
||||
|
||||
## [1.5.3](https://github.com/yang1206/uniapp-nutui/compare/v1.5.2...v1.5.3) (2023-11-22)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复支付宝小程序编译错误 ([203f3bd](https://github.com/yang1206/uniapp-nutui/commit/203f3bdd0c164faad471a74ce9eb4bffc7fb29ea)), closes [#117](https://github.com/yang1206/uniapp-nutui/issues/117)
|
||||
|
||||
## [1.5.2](https://github.com/yang1206/uniapp-nutui/compare/v1.5.1...v1.5.2) (2023-11-20)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **menu:** 修复自定义选中颜色失效 ([b441e89](https://github.com/yang1206/uniapp-nutui/commit/b441e89113c25f62eb3a42f75c822eaee6d0a149))
|
||||
* **notify:** 修复重复点击偶尔无法触发 ([d044ac9](https://github.com/yang1206/uniapp-nutui/commit/d044ac9592e27dc25ca7453e9579593a188e8337))
|
||||
* **rate:** 修复无法自定义图标大小 ([1d38d71](https://github.com/yang1206/uniapp-nutui/commit/1d38d712fa92bd9181f6d4302df547bd090af896)), closes [#111](https://github.com/yang1206/uniapp-nutui/issues/111)
|
||||
* **tabs:** 修复h5端滑动切换卡顿 ([a17e35e](https://github.com/yang1206/uniapp-nutui/commit/a17e35edf132488b0b1c8098f87e23d89e30120a))
|
||||
* **toast:** 修复重复点击造成卡死 ([4847ae9](https://github.com/yang1206/uniapp-nutui/commit/4847ae964ecd50fd466b336ec92a0121fd589bc9))
|
||||
|
||||
### Features
|
||||
|
||||
* 全部组件均已支持自定义类名与style ([5a29a83](https://github.com/yang1206/uniapp-nutui/commit/5a29a837a6ba3b7b1ca04edcbf76027877839a8c))
|
||||
* **noticebar:** 新增`field-name`属性,支持传入数组对象 ([3f95345](https://github.com/yang1206/uniapp-nutui/commit/3f95345f2d97d9ce9dbb2e2004e269b1cf986d05)), closes [#109](https://github.com/yang1206/uniapp-nutui/issues/109)
|
||||
|
||||
## [1.5.1](https://github.com/yang1206/uniapp-nutui/compare/v1.5.0...v1.5.1) (2023-11-17)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **calendar:** 过期时间未被禁用 ([5b767b8](https://github.com/yang1206/uniapp-nutui/commit/5b767b88be6c32b8c8edf1212eedec2c60929a57))
|
||||
* **radio:** 修复`text-position`属性失效 ([1eadf1a](https://github.com/yang1206/uniapp-nutui/commit/1eadf1ac25087fc259d40c30723918e61b818c86))
|
||||
|
||||
### Features
|
||||
|
||||
* **(signature):** signature组件小程序端支持导出图片 ([2992d39](https://github.com/yang1206/uniapp-nutui/commit/2992d39522e18e5f6dd51f82f237171fdb789d2b))
|
||||
|
||||
# [1.5.0](https://github.com/yang1206/uniapp-nutui/compare/v1.4.1...v1.5.0) (2023-11-15)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **cascader:** 级联二级切换无法被监听到 ([c78f382](https://github.com/yang1206/uniapp-nutui/commit/c78f382c02f76241919037b35c1834a73aeb6337)), closes [#105](https://github.com/yang1206/uniapp-nutui/issues/105)
|
||||
* textarea的autosize表现异常 ([d14813d](https://github.com/yang1206/uniapp-nutui/commit/d14813d6b32e2131a48abc7a8d6ec668c89023ce))
|
||||
|
||||
* fix(overlay)!: 小程序与 APP平台不再支持`lock-scroll`属性 ([cd3a740](https://github.com/yang1206/uniapp-nutui/commit/cd3a74066829123299915787feaf2d675a8a487c)), closes [#103](https://github.com/yang1206/uniapp-nutui/issues/103)
|
||||
|
||||
### Features
|
||||
|
||||
* 组件props增加注释说明 ([a585716](https://github.com/yang1206/uniapp-nutui/commit/a585716ca5dc71b9577a491fc15d696a69aed0fb)), closes [#100](https://github.com/yang1206/uniapp-nutui/issues/100)
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* 小程序移除`lock-scroll`属性支持
|
||||
|
||||
所有依赖overlay的组件的`lock-scroll`属性同理也将不再支持小程序与 APP 平台,禁用滚动穿透方法可参考文档
|
||||
|
||||
## [1.4.1](https://github.com/yang1206/uniapp-nutui/compare/v1.4.0...v1.4.1) (2023-11-08)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **badge:** badge的top、right不支持rpx单位 ([964e4ea](https://github.com/yang1206/uniapp-nutui/commit/964e4ea6e472889c7f43bc70491d3e3ac613cf31))
|
||||
|
||||
# [1.4.0](https://github.com/yang1206/uniapp-nutui/compare/v1.3.5...v1.4.0) (2023-11-08)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **address:** backIcon会一直存在 ([3819937](https://github.com/yang1206/uniapp-nutui/commit/3819937ec0b59d6d869a986a24e1449fd7dd93f1))
|
||||
* **card:** price颜色未覆盖 ([2665bd1](https://github.com/yang1206/uniapp-nutui/commit/2665bd137c5d27f57b614ebe70e25299e89ff481))
|
||||
* **checkbox:** 修复样式对齐问题 ([155e25b](https://github.com/yang1206/uniapp-nutui/commit/155e25ba84676d1c5b816541da622f4c893db857))
|
||||
* **comment:** label 样式无法正确省略 ([5e56649](https://github.com/yang1206/uniapp-nutui/commit/5e56649f4f87d9e3f914c3923a9d68a4d724ae26))
|
||||
* **progress:** icon 颜色错误 ([18662d5](https://github.com/yang1206/uniapp-nutui/commit/18662d57046acfb6e5c57e7bef214dde1ce52c88))
|
||||
* **uploader:** h5进度遮罩样式错误 ([e4ccf4a](https://github.com/yang1206/uniapp-nutui/commit/e4ccf4abdfeb74141a703cda8685e1576590ae8d))
|
||||
|
||||
* fix!: 短横线命名slot无法被正确替换 ([52587ed](https://github.com/yang1206/uniapp-nutui/commit/52587ed78feae3e35cd5f38e92cd6878f4c6c715)), closes [#96](https://github.com/yang1206/uniapp-nutui/issues/96)
|
||||
|
||||
### Features
|
||||
|
||||
* **calendar:** 增加自定义禁用函数disabled-date ([2b0363f](https://github.com/yang1206/uniapp-nutui/commit/2b0363fc58ca0087768a141b3f9dcfd4e4f15d44))
|
||||
* **calendar:** 增加footerSlot属性判断是否使用 footer 插槽 ([6e53d43](https://github.com/yang1206/uniapp-nutui/commit/6e53d435d179d7865087d03e1ecfd323d58f0162))
|
||||
* **image-preview:** 新增 long-press 事件 ([42893a5](https://github.com/yang1206/uniapp-nutui/commit/42893a50e1c5d59ec8611d0092f97c92f3797a6e))
|
||||
* popup 关联组件增加 z-index 属性 ([fe52d5f](https://github.com/yang1206/uniapp-nutui/commit/fe52d5fa7b05fdfec711b9eff7750cd51c291cf3))
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* 组件的slot名称由短横线替换为驼峰格式
|
||||
|
||||
受到影响的组件有: address , addresslist , calendar , card , comment , inputnumber , navbar , noticebar ,
|
||||
pagination , progress , sku , trendarrow , uploader
|
||||
|
||||
## [1.3.5](https://github.com/yang1206/uniapp-nutui/compare/v1.3.4...v1.3.5) (2023-11-07)
|
||||
|
||||
## [1.3.4](https://github.com/yang1206/uniapp-nutui/compare/v1.3.3...v1.3.4) (2023-11-06)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **watermark:** props丢失响应性 ([21984f5](https://github.com/yang1206/uniapp-nutui/commit/21984f551c77027fa4913200a6d72976cb5e9035)), closes [#95](https://github.com/yang1206/uniapp-nutui/issues/95)
|
||||
|
||||
## [1.3.3](https://github.com/yang1206/uniapp-nutui/compare/v1.3.2...v1.3.3) (2023-11-05)
|
||||
|
||||
## [1.3.2](https://github.com/yang1206/uniapp-nutui/compare/v1.3.1...v1.3.2) (2023-11-03)
|
||||
|
||||
### Features
|
||||
|
||||
* **price:** 支持customClass与 customStyle ([43516f4](https://github.com/yang1206/uniapp-nutui/commit/43516f43f33540684ed98a4878b4fbd4925116dd))
|
||||
|
||||
## [1.3.1](https://github.com/yang1206/uniapp-nutui/compare/v1.3.0...v1.3.1) (2023-11-01)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **sku:** 小程序真机调试错误 ([1bb8380](https://github.com/yang1206/uniapp-nutui/commit/1bb8380f4c1cabb443938f31dd6e0d1aa84ea104))
|
||||
|
||||
# [1.3.0](https://github.com/yang1206/uniapp-nutui/compare/v1.2.4...v1.3.0) (2023-10-31)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **circle-progress:** 修复数值变化在 iOS 下的 border 闪烁问题 ([f3f3934](https://github.com/yang1206/uniapp-nutui/commit/f3f3934292a11362c7c4e47cf450a7581e92efab))
|
||||
* **input:** 优化input属性问题 ([3fe20c7](https://github.com/yang1206/uniapp-nutui/commit/3fe20c76404a1dafc9fe2e66ffb0ea3419c1f07d))
|
||||
* **inputnumber:** 修复点击icon后超过min-max范围问题 ([17d8f7e](https://github.com/yang1206/uniapp-nutui/commit/17d8f7e6cb29500934c1d800e59b970390cb7b72))
|
||||
* **picker:** 去除columns有值才触发watch监听 ([6e39bc3](https://github.com/yang1206/uniapp-nutui/commit/6e39bc319d6892460a982e01e020435649554e47))
|
||||
* **swiper:** 修复动态修改 height 后组件视图未更新问题 ([b9bac98](https://github.com/yang1206/uniapp-nutui/commit/b9bac98a30215a847b536bfa252f5901d21b523d))
|
||||
* **swiper:** 修复页面中未设置 z-index 的 fixed 元素无法覆盖 swiper 问题 ([08e559b](https://github.com/yang1206/uniapp-nutui/commit/08e559bf2bc4101732067f5bce8231017aa9f788))
|
||||
|
||||
### Features
|
||||
|
||||
* **collapse:** 增加自定义插槽icon ([539ba35](https://github.com/yang1206/uniapp-nutui/commit/539ba35fd48506a21faa84af9b30b115f2870485))
|
||||
* **comment:** 追评中若是有图片的话,增加响应事件 ([767cea6](https://github.com/yang1206/uniapp-nutui/commit/767cea6f7208a314e6fad0e98be73e4441aa223e))
|
||||
* **dialog:** add ok-auto-close ([2957f5a](https://github.com/yang1206/uniapp-nutui/commit/2957f5a98c9f11513c92a511b26bf7afa8807d43))
|
||||
* **navbar:** 适配小程序状态栏高度,优化代码 ([4ee0118](https://github.com/yang1206/uniapp-nutui/commit/4ee01186d6ac60ce211464dcb2e833b9eff7933f))
|
||||
|
||||
## [1.2.4](https://github.com/yang1206/uniapp-nutui/compare/v1.2.3...v1.2.4) (2023-10-27)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* type error ([0b8ad0f](https://github.com/yang1206/uniapp-nutui/commit/0b8ad0f96d2d7b403023622ea0794e4b141466a7))
|
||||
* type error ([8498e02](https://github.com/yang1206/uniapp-nutui/commit/8498e02011786a9638ee2ebbb9a237de7eb5e449))
|
||||
|
||||
### Features
|
||||
|
||||
* **input:** 新增 placeholder-style 和 placeholder-class 属性 ([1409880](https://github.com/yang1206/uniapp-nutui/commit/14098807bdb90026d179eb24c739b4f854eeaf72))
|
||||
* 补充input、textarea部分属性 - by @xiaohe0601 [<samp>(b5bc4cf)</samp>](https://github.com/nutui-uniapp/nutui-uniapp/commit/b5bc4cf)
|
||||
|
||||
## [1.2.3](https://github.com/yang1206/uniapp-nutui/compare/v1.2.2...v1.2.3) (2023-10-23)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* (searchbar) autofocus无效 ([f467a11](https://github.com/yang1206/uniapp-nutui/commit/f467a111337bafcc4963d5fa7a4238de797f46e0))
|
||||
|
||||
## [1.2.2](https://github.com/yang1206/uniapp-nutui/compare/v1.2.1...v1.2.2) (2023-10-23)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* (input) 修复 input 事件缺失 ([a34e543](https://github.com/yang1206/uniapp-nutui/commit/a34e543a7ae37ff1d0fad1cee06e79be8f868ecf)), closes [#79](https://github.com/yang1206/uniapp-nutui/issues/79)
|
||||
* 修复lockScroll无效 ([520d693](https://github.com/yang1206/uniapp-nutui/commit/520d6939639d76871255b1953ecbbf8045d2318d))
|
||||
|
||||
### Features
|
||||
|
||||
* (imagepreview) 支持双指缩放图片 ([c7a9a90](https://github.com/yang1206/uniapp-nutui/commit/c7a9a9073fdc017ce93ddc47825c2290b53e34e7))
|
||||
|
||||
## [1.2.1](https://github.com/yang1206/uniapp-nutui/compare/v1.2.0...v1.2.1) (2023-10-18)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* (number-keyboard) type="rightColumn"时,某些版本安卓机无法正常显示 ([a5307ba](https://github.com/yang1206/uniapp-nutui/commit/a5307bae5cc65cb0695823646a0b767edde4f399)), closes [#78](https://github.com/yang1206/uniapp-nutui/issues/78)
|
||||
|
||||
# [1.2.0](https://github.com/yang1206/uniapp-nutui/compare/v1.1.10...v1.2.0) (2023-10-18)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* (input) clear 事件回调参数错误 ([2d506ba](https://github.com/yang1206/uniapp-nutui/commit/2d506ba86430ed0b46ed223251e959e37ef598e3))
|
||||
* (radio) 修复按钮类型激活背景边框样式 ([8736e91](https://github.com/yang1206/uniapp-nutui/commit/8736e9191643c741b20a24ba2fa7ad21640ecc6e))
|
||||
* (radio) 修复radio-group的textPosition属性没有响应式 ([85b2fa4](https://github.com/yang1206/uniapp-nutui/commit/85b2fa4ef24beccc3ecdfc55667214351ddb822a))
|
||||
* (tabs) 修复item设置disabled时仍可以滑动过去 ([3b7c77a](https://github.com/yang1206/uniapp-nutui/commit/3b7c77ab19fc56231444f5ee87305eb55356ba46))
|
||||
* (textarea) 修复readonly 属性失效与无法渲染换行 ([b8d37d4](https://github.com/yang1206/uniapp-nutui/commit/b8d37d40029fe484cd67e482efea00b704d9549f))
|
||||
|
||||
### Features
|
||||
|
||||
* (searchbar) 增加cursor-spacing属性 ([42393ac](https://github.com/yang1206/uniapp-nutui/commit/42393acf21303d4b8553df8adf5dd9dbd2d0ac3d))
|
||||
* (sticky) 重构sticky组件 ([bb9457b](https://github.com/yang1206/uniapp-nutui/commit/bb9457b2ee7d2b90cf1f8fcdcf9b870c84b280a0))
|
||||
* 基础组件最外层元素 flex -> inline-flex ([e065f9f](https://github.com/yang1206/uniapp-nutui/commit/e065f9f034d0da2deed659c1a55cef93efde17b0))
|
||||
|
||||
## [1.1.10](https://github.com/yang1206/uniapp-nutui/compare/v1.1.9...v1.1.10) (2023-10-12)
|
||||
|
||||
## [1.1.9](https://github.com/yang1206/uniapp-nutui/compare/v1.1.8...v1.1.9) (2023-10-10)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* (address-list) 小程序点击事件无法阻止冒泡 ([38616a9](https://github.com/yang1206/uniapp-nutui/commit/38616a967ea792c99cd5ef12a866132467eff88b))
|
||||
|
||||
## [1.1.8](https://github.com/yang1206/uniapp-nutui/compare/v1.1.7...v1.1.8) (2023-09-22)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* type error ([85f9daf](https://github.com/yang1206/uniapp-nutui/commit/85f9daf3526233eeba6e3170554e4116886a95d5)), closes [#62](https://github.com/yang1206/uniapp-nutui/issues/62) [#62](https://github.com/yang1206/uniapp-nutui/issues/62)
|
||||
|
||||
## [1.1.7](https://github.com/yang1206/uniapp-nutui/compare/v1.1.6...v1.1.7) (2023-09-20)
|
||||
|
||||
### Features
|
||||
|
||||
* (cascader) 增加标题配置项 ([d1fde7b](https://github.com/yang1206/uniapp-nutui/commit/d1fde7b09c209778f114bccc7a130c4762eb6c04))
|
||||
|
||||
## [1.1.6](https://github.com/yang1206/uniapp-nutui/compare/v1.1.5...v1.1.6) (2023-09-20)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* (searchbar) 修复抖音小程序searchbar输入抖动的问题 ([d634587](https://github.com/yang1206/uniapp-nutui/commit/d6345871aeb1a19244b87ca278d9dec4bd936e37))
|
||||
|
||||
### Features
|
||||
|
||||
* (calendar) 日历底部增加插槽 ([ff956dc](https://github.com/yang1206/uniapp-nutui/commit/ff956dc4bfe67cb57b6bd556efe814dc5f8080f2))
|
||||
* (cell) add desc slot ([eb0facf](https://github.com/yang1206/uniapp-nutui/commit/eb0facfe84e1ee7848053c198683d9f63ea73377))
|
||||
* (searchbar) 增加safe-area-inset-bottom 属性 ([8fc2907](https://github.com/yang1206/uniapp-nutui/commit/8fc2907ec987bdc2952d7f94c72996a5ba3eb5f9))
|
||||
|
||||
## [1.1.5](https://github.com/yang1206/uniapp-nutui/compare/v1.1.4...v1.1.5) (2023-09-18)
|
||||
|
||||
### Features
|
||||
|
||||
* (button) 支持 open-type 等开放能力属性 ([eb72466](https://github.com/yang1206/uniapp-nutui/commit/eb724660ca3e163387863fc0260155004c832feb)), closes [#58](https://github.com/yang1206/uniapp-nutui/issues/58) [#58](https://github.com/yang1206/uniapp-nutui/issues/58)
|
||||
|
||||
## [1.1.4](https://github.com/yang1206/uniapp-nutui/compare/v1.1.3...v1.1.4) (2023-09-14)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* (cascader) 动态加载标题无法正确显示 ([a3d7594](https://github.com/yang1206/uniapp-nutui/commit/a3d7594510bdf5088646b0dd3cfb8ed62eb70b2c))
|
||||
|
||||
## [1.1.3](https://github.com/yang1206/uniapp-nutui/compare/v1.1.2...v1.1.3) (2023-09-08)
|
||||
|
||||
## [1.1.2](https://github.com/yang1206/uniapp-nutui/compare/v1.1.1...v1.1.2) (2023-09-08)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复支付宝小程序编译失败 ([d814b4f](https://github.com/yang1206/uniapp-nutui/commit/d814b4ff073bc1f11bd971df8d035f0924ff16fe))
|
||||
|
||||
## [1.1.1](https://github.com/yang1206/uniapp-nutui/compare/v1.1.0...v1.1.1) (2023-09-07)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* (input) 自动聚焦无效 ([8744788](https://github.com/yang1206/uniapp-nutui/commit/874478877e38272c51b437cf17d20c2f272d8876)), closes [#39](https://github.com/yang1206/uniapp-nutui/issues/39)
|
||||
|
||||
# [1.1.0](https://github.com/yang1206/uniapp-nutui/compare/v1.0.1...v1.1.0) (2023-09-07)
|
||||
|
||||
### Features
|
||||
|
||||
* :sparkles: 新增Input组件customStyle自定义属性 ([c42a5fe](https://github.com/yang1206/uniapp-nutui/commit/c42a5fe0fc6ecb4bf89ff9a7213529055c2c8670))
|
||||
|
||||
## [1.0.1](https://github.com/yang1206/uniapp-nutui/compare/v1.0.0...v1.0.1) (2023-09-05)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* :bug: 修改inputnumber组件失去焦点以及change的时候展示错误 ([54d7e50](https://github.com/yang1206/uniapp-nutui/commit/54d7e5036af02f171c0e3dc2e12fb9db20f1bd80))
|
||||
* (picker)修复在非h5环境下多余引入导致无法真机调试 ([c9c8236](https://github.com/yang1206/uniapp-nutui/commit/c9c8236376cf190d20708202deb2c4b81cb7b691))
|
||||
* **input:** 修复type值number与digit两个校验逻辑反了 ([5c6c3a4](https://github.com/yang1206/uniapp-nutui/commit/5c6c3a4c1dcf24450f966988d93baf293fe4c720))
|
||||
|
||||
### Features
|
||||
|
||||
* :sparkles: 地址列表组件新增index索引回调 ([3f3bbc0](https://github.com/yang1206/uniapp-nutui/commit/3f3bbc00c74f6673322dc6cfa509f297dacc8299))
|
||||
|
||||
# [1.0.0](https://github.com/yang1206/uniapp-nutui/compare/v0.3.1...v1.0.0) (2023-08-30)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* :bug: 解决因组件使用img标签导致微信小程序编译出错问题 ([856f317](https://github.com/yang1206/uniapp-nutui/commit/856f3177ea4a34a6473c51d864324b2b314d5927))
|
||||
|
||||
## [0.3.1](https://github.com/yang1206/uniapp-nutui/compare/v0.3.0...v0.3.1) (2023-08-29)
|
||||
|
||||
### Features
|
||||
|
||||
* (card) 不填写价格则不展示 ([5f3bf98](https://github.com/yang1206/uniapp-nutui/commit/5f3bf98257d58ec530a853c8217b0fa4c6117084))
|
||||
|
||||
# [0.3.0](https://github.com/yang1206/uniapp-nutui/compare/v0.2.5...v0.3.0) (2023-08-28)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* (actionsheet) 修复 close-abled 失效问题 ([fabe18d](https://github.com/yang1206/uniapp-nutui/commit/fabe18dca78f30b4146d2fefebb053be1fa85882))
|
||||
* (countdown) millisecond format ([086c2a9](https://github.com/yang1206/uniapp-nutui/commit/086c2a93736a5fb8b763447a41275522b3c15f7b))
|
||||
* (picker) 小程序环境切换选项无响应 ([06ca0e8](https://github.com/yang1206/uniapp-nutui/commit/06ca0e86c990aa8caec56c1edbdc6217f576a664))
|
||||
* (picker) 修复 field-names 在级联模式下的问题 ([a5fa8cf](https://github.com/yang1206/uniapp-nutui/commit/a5fa8cf18fabc526156ff7aec0207b79bc6fc811))
|
||||
|
||||
### Features
|
||||
|
||||
* (picker) support custom column field names ([808482d](https://github.com/yang1206/uniapp-nutui/commit/808482dbf9b50d7559bbef9c4977fbb6d7a1445e))
|
||||
|
||||
## [0.2.5](https://github.com/yang1206/uniapp-nutui/compare/v0.2.4...v0.2.5) (2023-08-24)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* (form) 样式未正确覆盖 ([838db2a](https://github.com/yang1206/uniapp-nutui/commit/838db2ab3af66b79355392e3038cc4c9388168c5)), closes [#28](https://github.com/yang1206/uniapp-nutui/issues/28)
|
||||
* (dialog) 修复通过ref调用对话框时,noCancelBtn属性无效的bug ([6f7d516](https://github.com/yang1206/uniapp-nutui/commit/6f7d516320044ca8d96104531725eb8556732367))
|
||||
* (input) 修复左右插件默认文字无法显示 ([8b59931](https://github.com/yang1206/uniapp-nutui/commit/8b59931b4c1f6080ee320af9fa0a4072ec03c235))
|
||||
|
||||
## [0.2.4](https://github.com/yang1206/uniapp-nutui/compare/v0.2.3...v0.2.4) (2023-08-18)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* (radio) 自定义图标 slot丢失 ([7ac6772](https://github.com/yang1206/uniapp-nutui/commit/7ac6772e0ea1775ff739803386cff1d2a8b17617)), closes [#15](https://github.com/yang1206/uniapp-nutui/issues/15)
|
||||
|
||||
## [0.2.3](https://github.com/yang1206/uniapp-nutui/compare/v0.2.2...v0.2.3) (2023-08-18)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* (configProvider) 修复取值错误问题 ([3a5d5fb](https://github.com/yang1206/uniapp-nutui/commit/3a5d5fb36c33ec35394b74aa443c62ad753ec992))
|
||||
|
||||
## [0.2.2](https://github.com/yang1206/uniapp-nutui/compare/v0.2.1...v0.2.2) (2023-08-09)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* (form) 调整 label 中 min-width 样式权重 ([c368e2c](https://github.com/yang1206/uniapp-nutui/commit/c368e2c37dae7425b400b20399b0c2fe53403f75))
|
||||
* (input) 小程序部分type 属性失效 ([a88fa69](https://github.com/yang1206/uniapp-nutui/commit/a88fa69346dd0ce9b68c6d33b94f59915ab09ae5))
|
||||
* (tabbar) 切换事件回调参数丢失 ([9979494](https://github.com/yang1206/uniapp-nutui/commit/9979494fbb4c35b73ced81baee8ba94ea44761dc)), closes [#10](https://github.com/yang1206/uniapp-nutui/issues/10) [#10](https://github.com/yang1206/uniapp-nutui/issues/10)
|
||||
|
||||
## [0.2.1](https://github.com/yang1206/uniapp-nutui/compare/v0.2.0...v0.2.1) (2023-08-05)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* (cascader) 修复在 Popup 中的滑动问题 ([302cd53](https://github.com/yang1206/uniapp-nutui/commit/302cd531426f892425810989b73a8376d0231175))
|
||||
* (input) 修复空白节点导致的样式对齐问题 ([11b1d87](https://github.com/yang1206/uniapp-nutui/commit/11b1d870030a3a9b4ba9037c2d14735186b65ab0))
|
||||
* (uploader) name参数无效 ([b21acd0](https://github.com/yang1206/uniapp-nutui/commit/b21acd0c1b61fae6a6cdd6ecb1d8db7084820bbc)), closes [#9](https://github.com/yang1206/uniapp-nutui/issues/9)
|
||||
|
||||
# [0.2.0](https://github.com/yang1206/uniapp-nutui/compare/v0.1.8...v0.2.0) (2023-07-30)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* (animate) 小程序触发动画失效 ([a152121](https://github.com/yang1206/uniapp-nutui/commit/a15212198653e7b8315aa4562663f88fbb91e02b))
|
||||
* (calendar) 修复自定义起始日高亮列错误问题 ([dfeaebc](https://github.com/yang1206/uniapp-nutui/commit/dfeaebc7a79102765f7d0cca01f098716137fe71))
|
||||
* (countdown) 方法调用错误 ([6aac458](https://github.com/yang1206/uniapp-nutui/commit/6aac458bc5b3599b8f40a6da59539a208e14239d))
|
||||
* (form) 小程序验证失效 ([9067f4d](https://github.com/yang1206/uniapp-nutui/commit/9067f4dc167ecc13587bc7714810ae4fea0eb0d4))
|
||||
* (price) 修复小程序下符号转义丢失问题 ([9194c4c](https://github.com/yang1206/uniapp-nutui/commit/9194c4c59dab0a64383d5b86eb78473c43e4c019))
|
||||
|
||||
## [0.1.8](https://github.com/yang1206/uniapp-nutui/compare/v0.1.7...v0.1.8) (2023-07-24)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* (codeInput) 双向绑定失效 ([b9ef603](https://github.com/yang1206/uniapp-nutui/commit/b9ef6034a79b043991b63e032f2a24212d8bb46a))
|
||||
|
||||
## [0.1.7](https://github.com/yang1206/uniapp-nutui/compare/v0.1.6...v0.1.7) (2023-07-22)
|
||||
|
||||
### Features
|
||||
|
||||
* 同步修复 ([dad75c8](https://github.com/yang1206/uniapp-nutui/commit/dad75c84907a6f9464db94581223d73108af6aab))
|
||||
|
||||
## [0.1.6](https://github.com/yang1206/uniapp-nutui/compare/v0.1.5...v0.1.6) (2023-07-15)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 同步nutui的修复 ([94b3b27](https://github.com/yang1206/uniapp-nutui/commit/94b3b27b7cf097be660e0d254bb6001cca577a07))
|
||||
|
||||
## [0.1.5](https://github.com/yang1206/uniapp-nutui/compare/v0.1.4...v0.1.5) (2023-07-13)
|
||||
|
||||
### Features
|
||||
|
||||
* 新增 codeInput组件 ([9b9516b](https://github.com/yang1206/uniapp-nutui/commit/9b9516ba7ce4a147c731a04f25532dfdaef730b6))
|
||||
* 新增loadingpage组件 ([4ae2c12](https://github.com/yang1206/uniapp-nutui/commit/4ae2c125240e933e5b95209411904d4854a89413))
|
||||
* 移植nutbingo的部分抽奖组件 ([6e19b1a](https://github.com/yang1206/uniapp-nutui/commit/6e19b1a9c62dd878bf77a32a90fce17b35d83afb))
|
||||
|
||||
## [0.1.4](https://github.com/yang1206/uniapp-nutui/compare/v0.1.3...v0.1.4) (2023-07-09)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复elevator在小程序环境文字不显示 ([87fc4e9](https://github.com/yang1206/uniapp-nutui/commit/87fc4e91222e3052d1c0ee849d8b7715d8768177))
|
||||
|
||||
### Features
|
||||
|
||||
* 增加组件全局类型定义文件 ([c84da47](https://github.com/yang1206/uniapp-nutui/commit/c84da47d8ad9a414965f8bc9f8d033f5bbe90435))
|
||||
|
||||
## [0.1.3](https://github.com/yang1206/uniapp-nutui/compare/v0.1.2...v0.1.3) (2023-07-09)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复h5部分组件样式错误 ([cfb1e4b](https://github.com/yang1206/uniapp-nutui/commit/cfb1e4b4a88a8a6d3f674fb0bacf9c5283caabe9))
|
||||
|
||||
### Features
|
||||
|
||||
* 新增uni_modules安装方式 ([9df523b](https://github.com/yang1206/uniapp-nutui/commit/9df523bd65fcab1b3c57a1686381a0df278855a9))
|
||||
|
||||
## [0.1.2](https://github.com/yang1206/uniapp-nutui/compare/v0.1.1...v0.1.2) (2023-07-08)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复运行时环境判断错误 ([ce3b0c7](https://github.com/yang1206/uniapp-nutui/commit/ce3b0c70e18826defb87074117294326240f42ec))
|
||||
|
||||
## [0.1.1](https://github.com/yang1206/uniapp-nutui/compare/v0.1.0...v0.1.1) (2023-07-07)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复picker 在h5中适配错误 ([cdd9aa4](https://github.com/yang1206/uniapp-nutui/commit/cdd9aa4da991145776b10c4f76ae02dbc156110b))
|
||||
* 修复uploader组件自定义上传错误 ([af87d68](https://github.com/yang1206/uniapp-nutui/commit/af87d6852dca14f7f212141bfbb80f247bcb8cbf))
|
||||
|
||||
# [0.1.0](https://github.com/yang1206/uniapp-nutui/compare/v0.0.6...v0.1.0) (2023-07-07)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复小程序暗黑模式失效 ([a445f04](https://github.com/yang1206/uniapp-nutui/commit/a445f042bd8ab1d0ecdc9f738a9eff704a299fe7))
|
||||
* 修复支付宝小程序不支持编译错误 ([2c8e0dd](https://github.com/yang1206/uniapp-nutui/commit/2c8e0dda43ad9687211d8f939a6ad20230c4d486))
|
||||
* 修复支付宝小程序中部分兼容问题 ([6fa5132](https://github.com/yang1206/uniapp-nutui/commit/6fa513211ffdf60bf9592be198befb2e5c690122))
|
||||
* 修复h5组件之间引用样式丢失 ([d884e28](https://github.com/yang1206/uniapp-nutui/commit/d884e28d8210308bed1aeb4a488bc3f9f0963aed))
|
||||
|
||||
## [0.0.9](https://github.com/yang1206/uniapp-nutui/compare/v0.0.6...v0.0.9) (2023-07-06)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复小程序暗黑模式失效 ([a445f04](https://github.com/yang1206/uniapp-nutui/commit/a445f042bd8ab1d0ecdc9f738a9eff704a299fe7))
|
||||
* 修复支付宝小程序不支持编译错误 ([2c8e0dd](https://github.com/yang1206/uniapp-nutui/commit/2c8e0dda43ad9687211d8f939a6ad20230c4d486))
|
||||
* 修复支付宝小程序中部分兼容问题 ([6fa5132](https://github.com/yang1206/uniapp-nutui/commit/6fa513211ffdf60bf9592be198befb2e5c690122))
|
||||
* 修复h5组件之间引用样式丢失 ([d884e28](https://github.com/yang1206/uniapp-nutui/commit/d884e28d8210308bed1aeb4a488bc3f9f0963aed))
|
||||
|
||||
## [0.0.8](https://github.com/yang1206/uniapp-nutui/compare/v0.0.7...v0.0.8) (2023-07-05)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复支付宝小程序编译错误 ([bdd6540](https://github.com/yang1206/uniapp-nutui/commit/bdd65408d77aff73509da7d18a2d04d11b105904))
|
||||
* 修复h5组件之间引用样式丢失 ([b784635](https://github.com/yang1206/uniapp-nutui/commit/b7846354ffb7355a1328f804aa689b1e85487807))
|
||||
|
||||
## [0.0.7](https://github.com/yang1206/uniapp-nutui/compare/v0.0.6...v0.0.7) (2023-07-04)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复小程序暗黑模式失效 ([753f87a](https://github.com/yang1206/uniapp-nutui/commit/753f87ae5c7a9f69e789e18b346ac1b4393f581d))
|
||||
|
||||
## [0.0.6](https://github.com/yang1206/uniapp-nutui/compare/v0.0.5...v0.0.6) (2023-07-03)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* sku 样式错乱 ([ebdd0df](https://github.com/yang1206/uniapp-nutui/commit/ebdd0dfeba50ba53a6126e8e3dc12b0f69c39f6c))
|
||||
|
||||
## [0.0.5](https://github.com/yang1206/uniapp-nutui/compare/v0.0.4...v0.0.5) (2023-07-03)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复addresslist 样式丢失 ([a36e7e2](https://github.com/yang1206/uniapp-nutui/commit/a36e7e248bc5071978574a016b1e6a967053f690))
|
||||
|
||||
## [0.0.4](https://github.com/yang1206/uniapp-nutui/compare/v0.0.3...v0.0.4) (2023-07-03)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 微信小程序打包生成无意义的index.js文件 ([9c9804d](https://github.com/yang1206/uniapp-nutui/commit/9c9804dea5646a74da163feb176058129d0e2e34))
|
||||
* 修复steps 在小程序样式混乱 ([72bbb19](https://github.com/yang1206/uniapp-nutui/commit/72bbb19f34d93598fb40f50e3d54c3b5257825ff))
|
||||
|
||||
## [0.0.1](https://github.com/yang1206/uniapp-nutui/compare/89499ddbcc62fc62228a0f18ce076511287b5dd5...v0.0.1) (2023-07-02)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 解决一些已知问题 ([b00a7c4](https://github.com/yang1206/uniapp-nutui/commit/b00a7c416b1f8077968b6e599637b92814481926))
|
||||
|
||||
### Features
|
||||
|
||||
* 国际化 ([17166d9](https://github.com/yang1206/uniapp-nutui/commit/17166d916e3aa473d463083ba535713b32212f82))
|
||||
* addresslist, barrage, card, category, comment, ecard, invoice, timeselect ([a7d1b61](https://github.com/yang1206/uniapp-nutui/commit/a7d1b610c55070c5b8dac02a06fe1b00c128021b))
|
||||
* backtop, drag , swipe , switch ([5f8270d](https://github.com/yang1206/uniapp-nutui/commit/5f8270d6e9930199f1a5ac7bc4fe6cd06727ce11))
|
||||
* calendar,cascader ([a1f2d76](https://github.com/yang1206/uniapp-nutui/commit/a1f2d76ba520c1833cdab154f3012f319484fceb))
|
||||
* cell, overlay , transition ([2fe21ca](https://github.com/yang1206/uniapp-nutui/commit/2fe21cab85d9eb0a25effe2e12befee4cda4c3f5))
|
||||
* checkbox, picker , datepicker ([55c72cc](https://github.com/yang1206/uniapp-nutui/commit/55c72cc1165a0940d7e9827644ac7caafa337a41))
|
||||
* circleprogress, collapse, animate, countup ([4720c1b](https://github.com/yang1206/uniapp-nutui/commit/4720c1b4af45da56589d1d8e56410014df938944))
|
||||
* countdown ([89499dd](https://github.com/yang1206/uniapp-nutui/commit/89499ddbcc62fc62228a0f18ce076511287b5dd5))
|
||||
* dark mode ([d275211](https://github.com/yang1206/uniapp-nutui/commit/d275211c6d6c8948423396c8d63d65acccac711d))
|
||||
* dialog ([80494b3](https://github.com/yang1206/uniapp-nutui/commit/80494b3fd4bf6c4bbabe1a933de1f5f8b683aaf5))
|
||||
* elevator, fixednav, indicator ([0ce90ef](https://github.com/yang1206/uniapp-nutui/commit/0ce90efe571c826c73168e57f374ccf1674ae219))
|
||||
* ellipsis, empty, imagepreview, list, swiper ([d20c07f](https://github.com/yang1206/uniapp-nutui/commit/d20c07f4c045dcc9e301bc79e1e913a77315c19e))
|
||||
* infiniteloading, divider, grid, layout, sticky ([889d930](https://github.com/yang1206/uniapp-nutui/commit/889d930c81b21c7e766b7f6352814386476b7bbc))
|
||||
* input,textarea,inputnumber,numberkeyboard,radio,radiogroup ([97a1c4c](https://github.com/yang1206/uniapp-nutui/commit/97a1c4c1407b3a664245be5b32c9660368e37008))
|
||||
* menu ([3ddf33e](https://github.com/yang1206/uniapp-nutui/commit/3ddf33eab33f90c69bbdf336dda7a1c1951e1ba2))
|
||||
* noticebar, popover, price, skeleton, steps ([3211db9](https://github.com/yang1206/uniapp-nutui/commit/3211db9431d51c2dbf68e960de6b6e807ee458f0))
|
||||
* range, rate , searchbar, shortpassword, uploader, form, progress ([c8595fe](https://github.com/yang1206/uniapp-nutui/commit/c8595fe070dc631d6514e898f9a0fa90d692d8f4))
|
||||
* sidenavbar, pagination, tabbar , badge, avatar ([312acf8](https://github.com/yang1206/uniapp-nutui/commit/312acf85c46a770a163fdfadb9da00c0515e9f70))
|
||||
* table, tag, tour, trendarrow, watermark, address ([2cb9e78](https://github.com/yang1206/uniapp-nutui/commit/2cb9e78d047e6fee3fcb1a3c2c727865b238229f))
|
||||
* tabs ([e31e9f5](https://github.com/yang1206/uniapp-nutui/commit/e31e9f546c2054c2f196212ca2e03d4bba40168d))
|
||||
* toast ([281f225](https://github.com/yang1206/uniapp-nutui/commit/281f225ba296b463e124dce51f69689a0bdb3c23))
|
||||
* transition 支持自定义动画 ([4fd9314](https://github.com/yang1206/uniapp-nutui/commit/4fd931469a5aa867a040cc625a5b6226b5cb5c77))
|
||||
18
uni_modules/nutui-uni/components/_constants/event.ts
Normal file
18
uni_modules/nutui-uni/components/_constants/event.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
export const UPDATE_MODEL_EVENT = 'update:modelValue'
|
||||
export const UPDATE_VISIBLE_EVENT = 'update:visible'
|
||||
export const CHANGE_EVENT = 'change'
|
||||
export const INPUT_EVENT = 'input'
|
||||
export const CLICK_EVENT = 'click'
|
||||
export const OPEN_EVENT = 'open'
|
||||
export const CLOSE_EVENT = 'close'
|
||||
export const OPENED_EVENT = 'opened'
|
||||
export const CLOSED_EVENT = 'closed'
|
||||
export const FOCUS_EVENT = 'focus'
|
||||
export const BLUR_EVENT = 'blur'
|
||||
export const CONFIRM_EVENT = 'confirm'
|
||||
export const SEARCH_EVENT = 'search'
|
||||
export const CLEAR_EVENT = 'clear'
|
||||
export const CANCEL_EVENT = 'cancel'
|
||||
export const CHOOSE_EVENT = 'choose'
|
||||
export const SELECT_EVENT = 'select'
|
||||
export const SELECTED_EVENT = 'selected'
|
||||
3
uni_modules/nutui-uni/components/_constants/index.ts
Normal file
3
uni_modules/nutui-uni/components/_constants/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from './event'
|
||||
export * from './prefix'
|
||||
export * from './types'
|
||||
1
uni_modules/nutui-uni/components/_constants/prefix.ts
Normal file
1
uni_modules/nutui-uni/components/_constants/prefix.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const PREFIX = 'nut'
|
||||
11
uni_modules/nutui-uni/components/_constants/types.ts
Normal file
11
uni_modules/nutui-uni/components/_constants/types.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import type { NutAnimationName } from '../transition'
|
||||
|
||||
export type Position = 'center' | 'top' | 'bottom' | 'left' | 'right'
|
||||
|
||||
export const animationName: Record<Position, NutAnimationName> = {
|
||||
center: 'fade',
|
||||
top: 'slide-down',
|
||||
bottom: 'slide-up',
|
||||
left: 'slide-left',
|
||||
right: 'slide-right',
|
||||
}
|
||||
11
uni_modules/nutui-uni/components/_hooks/index.ts
Normal file
11
uni_modules/nutui-uni/components/_hooks/index.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export * from './useExpose'
|
||||
export * from './useGlobalZIndex'
|
||||
export * from './useInject'
|
||||
export * from './useLockScroll'
|
||||
export * from './useProvide'
|
||||
export * from './useRect'
|
||||
export * from './useRelation'
|
||||
export * from './useRouter'
|
||||
export * from './useSelectorQuery'
|
||||
export * from './useStyle'
|
||||
export * from './useTouch'
|
||||
8
uni_modules/nutui-uni/components/_hooks/useExpose.ts
Normal file
8
uni_modules/nutui-uni/components/_hooks/useExpose.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { ComponentPublicInstance } from 'vue'
|
||||
import { getCurrentInstance } from 'vue'
|
||||
|
||||
export function useExpose(apis: Record<string, any>) {
|
||||
const instance = getCurrentInstance()
|
||||
if (instance)
|
||||
Object.assign(instance.proxy as ComponentPublicInstance, apis)
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
let globalZIndex = 2000
|
||||
|
||||
export function useGlobalZIndex() {
|
||||
return ++globalZIndex
|
||||
}
|
||||
|
||||
export function setGlobalZIndex(value: number) {
|
||||
globalZIndex = value
|
||||
}
|
||||
32
uni_modules/nutui-uni/components/_hooks/useInject.ts
Normal file
32
uni_modules/nutui-uni/components/_hooks/useInject.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { computed, getCurrentInstance, inject, onUnmounted, ref } from 'vue'
|
||||
import type { ComponentInternalInstance, InjectionKey } from 'vue'
|
||||
|
||||
type ParentProvide<T> = T & {
|
||||
add: (child: ComponentInternalInstance) => void
|
||||
remove: (child: ComponentInternalInstance) => void
|
||||
internalChildren: ComponentInternalInstance[]
|
||||
}
|
||||
|
||||
export function useInject<T>(key: InjectionKey<ParentProvide<T>>) {
|
||||
const parent = inject(key, null)
|
||||
|
||||
if (parent) {
|
||||
const instance = getCurrentInstance()!
|
||||
const { add, remove, internalChildren } = parent
|
||||
|
||||
add(instance)
|
||||
onUnmounted(() => remove(instance))
|
||||
|
||||
const index = computed(() => internalChildren.indexOf(instance))
|
||||
|
||||
return {
|
||||
parent,
|
||||
index,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
parent: null,
|
||||
index: ref(-1),
|
||||
}
|
||||
}
|
||||
31
uni_modules/nutui-uni/components/_hooks/useLockScroll.ts
Normal file
31
uni_modules/nutui-uni/components/_hooks/useLockScroll.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
let count = 0
|
||||
|
||||
const CLSNAME = 'nut-overflow-hidden'
|
||||
|
||||
export function useLockScroll(isLock: () => boolean) {
|
||||
const lock = () => {
|
||||
if (isLock()) {
|
||||
try {
|
||||
!count && document.body.classList.add(CLSNAME)
|
||||
count++
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const unlock = () => {
|
||||
if (isLock() && count) {
|
||||
try {
|
||||
count--
|
||||
!count && document.body.classList.remove(CLSNAME)
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [lock, unlock]
|
||||
}
|
||||
93
uni_modules/nutui-uni/components/_hooks/useProvide.ts
Normal file
93
uni_modules/nutui-uni/components/_hooks/useProvide.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
import { getCurrentInstance, markRaw, provide, shallowReactive } from 'vue'
|
||||
import type {
|
||||
ComponentInternalInstance,
|
||||
ConcreteComponent,
|
||||
InjectionKey,
|
||||
VNode,
|
||||
VNodeNormalizedChildren,
|
||||
} from 'vue'
|
||||
|
||||
// TODO: uniapp 不支持 vue 直接导出的 isVNode
|
||||
export function isVNode(value: any): value is VNode {
|
||||
return value ? value.__v_isVNode === true : false
|
||||
}
|
||||
|
||||
export function flattenVNodes(shouldTraverseChildren: VNodeNormalizedChildren, childName?: string) {
|
||||
const result: VNode[] = []
|
||||
|
||||
const traverse = (children: VNodeNormalizedChildren) => {
|
||||
if (!Array.isArray(children))
|
||||
return
|
||||
children.forEach((child) => {
|
||||
if (!isVNode(child))
|
||||
return
|
||||
|
||||
if (childName) {
|
||||
if (child.type && (child.type as ConcreteComponent).name === childName) {
|
||||
result.push(child)
|
||||
return
|
||||
}
|
||||
}
|
||||
else {
|
||||
result.push(child)
|
||||
}
|
||||
|
||||
if (child.component?.subTree)
|
||||
traverse(child.component.subTree.children)
|
||||
|
||||
if (child.children)
|
||||
traverse(child.children)
|
||||
})
|
||||
}
|
||||
|
||||
traverse(shouldTraverseChildren)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
export function sortChildren(
|
||||
parent: ComponentInternalInstance,
|
||||
internalChildren: ComponentInternalInstance[],
|
||||
childName?: string,
|
||||
) {
|
||||
const vnodes = flattenVNodes(parent && parent.subTree && parent.subTree.children, childName)
|
||||
internalChildren.sort((a, b) => {
|
||||
return vnodes.indexOf(a.vnode) - vnodes.indexOf(b.vnode)
|
||||
})
|
||||
}
|
||||
|
||||
// 如果指定组件名称,则只查找此组件并且查到后结束。也就是不关心此组件下的内容,在大部分场景下节省查找消耗。
|
||||
export function useProvide<ProvideValue>(key: InjectionKey<ProvideValue>, childName?: string) {
|
||||
const internalChildren: ComponentInternalInstance[] = shallowReactive([])
|
||||
const publicChildren = shallowReactive<any[]>([])
|
||||
const parent = getCurrentInstance()!
|
||||
|
||||
const add = (child: ComponentInternalInstance) => {
|
||||
if (!child.proxy)
|
||||
return
|
||||
internalChildren.push(markRaw(child))
|
||||
publicChildren.push(markRaw(child.proxy))
|
||||
sortChildren(parent, internalChildren, childName)
|
||||
}
|
||||
|
||||
const remove = (child: ComponentInternalInstance) => {
|
||||
if (child.proxy) {
|
||||
internalChildren.splice(internalChildren.indexOf(markRaw(child)), 1)
|
||||
publicChildren.splice(publicChildren.indexOf(markRaw(child.proxy)), 1)
|
||||
}
|
||||
}
|
||||
|
||||
return (value?: ProvideValue) => {
|
||||
provide(key, {
|
||||
add,
|
||||
remove,
|
||||
internalChildren,
|
||||
...value,
|
||||
} as any)
|
||||
|
||||
return {
|
||||
internalChildren,
|
||||
children: publicChildren,
|
||||
}
|
||||
}
|
||||
}
|
||||
7
uni_modules/nutui-uni/components/_hooks/useRect.ts
Normal file
7
uni_modules/nutui-uni/components/_hooks/useRect.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import type { ComponentInternalInstance } from 'vue'
|
||||
import { useSelectorQuery } from './useSelectorQuery'
|
||||
|
||||
export function useRect(id: string, instance?: ComponentInternalInstance): Promise<UniApp.NodeInfo> {
|
||||
const { getSelectorNodeInfo } = useSelectorQuery(instance)
|
||||
return getSelectorNodeInfo(`#${id}`)
|
||||
}
|
||||
8
uni_modules/nutui-uni/components/_hooks/useRelation.ts
Normal file
8
uni_modules/nutui-uni/components/_hooks/useRelation.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { ComponentPublicInstance } from 'vue'
|
||||
import { getCurrentInstance } from 'vue'
|
||||
|
||||
export function useExtend<T>(apis: T) {
|
||||
const instance = getCurrentInstance()
|
||||
if (instance)
|
||||
Object.assign(instance.proxy as ComponentPublicInstance, apis)
|
||||
}
|
||||
28
uni_modules/nutui-uni/components/_hooks/useRouter.ts
Normal file
28
uni_modules/nutui-uni/components/_hooks/useRouter.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
export type NavigateToOptions = string | UniApp.NavigateToOptions
|
||||
export type RedirectToOptions = string | UniApp.RedirectToOptions
|
||||
|
||||
export type RouterOptions = UniApp.NavigateToOptions | UniApp.RedirectToOptions
|
||||
|
||||
export function useRouter() {
|
||||
const push = (options: NavigateToOptions) => {
|
||||
if (typeof options === 'string') {
|
||||
uni.navigateTo({ url: options })
|
||||
return
|
||||
}
|
||||
|
||||
uni.navigateTo(options)
|
||||
}
|
||||
const replace = (options: RedirectToOptions) => {
|
||||
if (typeof options === 'string') {
|
||||
uni.redirectTo({ url: options })
|
||||
return
|
||||
}
|
||||
|
||||
uni.redirectTo(options)
|
||||
}
|
||||
|
||||
return {
|
||||
push,
|
||||
replace,
|
||||
}
|
||||
}
|
||||
67
uni_modules/nutui-uni/components/_hooks/useSelectorQuery.ts
Normal file
67
uni_modules/nutui-uni/components/_hooks/useSelectorQuery.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import { getCurrentInstance } from 'vue'
|
||||
import type { ComponentInternalInstance } from 'vue'
|
||||
|
||||
export function useSelectorQuery(instance?: ComponentInternalInstance | null) {
|
||||
let query: UniApp.SelectorQuery | null = null
|
||||
|
||||
if (!instance)
|
||||
instance = getCurrentInstance()
|
||||
|
||||
if (!instance)
|
||||
console.warn('useSelectorQuery', 'useSelectorQuery必须在setup函数中使用')
|
||||
|
||||
// #ifndef MP-ALIPAY
|
||||
query = uni.createSelectorQuery().in(instance)
|
||||
// #endif
|
||||
// #ifdef MP-ALIPAY
|
||||
query = uni.createSelectorQuery().in(null)
|
||||
// #endif
|
||||
|
||||
const getSelectorNodeInfo = (selector: string): Promise<UniApp.NodeInfo> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (query) {
|
||||
query
|
||||
.select(selector)
|
||||
.boundingClientRect((res) => {
|
||||
const selectRes: UniApp.NodeInfo = res as UniApp.NodeInfo
|
||||
if (selectRes)
|
||||
resolve(selectRes)
|
||||
else
|
||||
reject(new Error(`未找到对应节点: ${selector}`))
|
||||
})
|
||||
.exec()
|
||||
}
|
||||
else {
|
||||
reject(new Error('未找到对应的SelectorQuery实例'))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const getSelectorNodeInfos = (
|
||||
selector: string,
|
||||
): Promise<UniApp.NodeInfo[]> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (query) {
|
||||
query
|
||||
.selectAll(selector)
|
||||
.boundingClientRect((res) => {
|
||||
const selectRes: UniApp.NodeInfo[] = res as UniApp.NodeInfo[]
|
||||
if (selectRes && selectRes.length > 0)
|
||||
resolve(selectRes)
|
||||
else
|
||||
reject(new Error(`未找到对应节点: ${selector}`))
|
||||
})
|
||||
.exec()
|
||||
}
|
||||
else {
|
||||
reject(new Error('未找到对应的SelectorQuery实例'))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
query,
|
||||
getSelectorNodeInfo,
|
||||
getSelectorNodeInfos,
|
||||
}
|
||||
}
|
||||
29
uni_modules/nutui-uni/components/_hooks/useStyle.ts
Normal file
29
uni_modules/nutui-uni/components/_hooks/useStyle.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { computed, normalizeClass, normalizeStyle } from 'vue'
|
||||
import { stringifyStyle } from '../_utils'
|
||||
|
||||
export function useStyleContext(props: any, componentName: string) {
|
||||
const mainClass = computed(() => {
|
||||
const cls = normalizeClass([props.customClass, { [componentName]: true }])
|
||||
|
||||
return cls
|
||||
})
|
||||
|
||||
const mainStyle = computed(() => {
|
||||
return stringifyStyle(normalizeStyle(props.customStyle))
|
||||
})
|
||||
|
||||
const getMainClass = (cls: unknown) => {
|
||||
return normalizeClass([props.customClass, { [componentName]: true }, cls])
|
||||
}
|
||||
|
||||
const getMainStyle = (style: unknown) => {
|
||||
return stringifyStyle(normalizeStyle([props.customStyle, style]))
|
||||
}
|
||||
|
||||
return {
|
||||
mainClass,
|
||||
mainStyle,
|
||||
getMainClass,
|
||||
getMainStyle,
|
||||
}
|
||||
}
|
||||
74
uni_modules/nutui-uni/components/_hooks/useTouch.ts
Normal file
74
uni_modules/nutui-uni/components/_hooks/useTouch.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import { ref } from 'vue'
|
||||
|
||||
const MIN_DISTANCE = 10
|
||||
|
||||
type Direction = '' | 'vertical' | 'horizontal'
|
||||
|
||||
function getDirection(x: number, y: number) {
|
||||
if (x > y && x > MIN_DISTANCE)
|
||||
return 'horizontal'
|
||||
|
||||
if (y > x && y > MIN_DISTANCE)
|
||||
return 'vertical'
|
||||
|
||||
return ''
|
||||
}
|
||||
|
||||
export function useTouch() {
|
||||
const startX = ref(0)
|
||||
const startY = ref(0)
|
||||
const moveX = ref(0)
|
||||
const moveY = ref(0)
|
||||
const deltaX = ref(0)
|
||||
const deltaY = ref(0)
|
||||
const offsetX = ref(0)
|
||||
const offsetY = ref(0)
|
||||
const direction = ref<Direction>('')
|
||||
|
||||
const isVertical = () => direction.value === 'vertical'
|
||||
const isHorizontal = () => direction.value === 'horizontal'
|
||||
|
||||
const reset = () => {
|
||||
deltaX.value = 0
|
||||
deltaY.value = 0
|
||||
offsetX.value = 0
|
||||
offsetY.value = 0
|
||||
direction.value = ''
|
||||
}
|
||||
|
||||
const start = ((event: TouchEvent) => {
|
||||
reset()
|
||||
startX.value = event.touches[0].clientX
|
||||
startY.value = event.touches[0].clientY
|
||||
}) as EventListener
|
||||
|
||||
const move = ((event: TouchEvent) => {
|
||||
const touch = event.touches[0]
|
||||
deltaX.value = touch.clientX - startX.value
|
||||
deltaY.value = touch.clientY - startY.value
|
||||
moveX.value = touch.clientX
|
||||
moveY.value = touch.clientY
|
||||
offsetX.value = Math.abs(deltaX.value)
|
||||
offsetY.value = Math.abs(deltaY.value)
|
||||
|
||||
if (!direction.value)
|
||||
direction.value = getDirection(offsetX.value, offsetY.value)
|
||||
}) as EventListener
|
||||
|
||||
return {
|
||||
move,
|
||||
start,
|
||||
reset,
|
||||
startX,
|
||||
startY,
|
||||
moveX,
|
||||
moveY,
|
||||
deltaX,
|
||||
deltaY,
|
||||
offsetX,
|
||||
offsetY,
|
||||
direction,
|
||||
isVertical,
|
||||
isHorizontal,
|
||||
}
|
||||
}
|
||||
277
uni_modules/nutui-uni/components/_utils/common.ts
Normal file
277
uni_modules/nutui-uni/components/_utils/common.ts
Normal file
@@ -0,0 +1,277 @@
|
||||
import { isArray, isDef, isObject } from './is'
|
||||
|
||||
// 变量类型判断
|
||||
export function TypeOfFun(value: any) {
|
||||
if (value === null)
|
||||
return 'null'
|
||||
|
||||
const type = typeof value
|
||||
if (type === 'undefined' || type === 'string')
|
||||
return type
|
||||
|
||||
const typeString = toString.call(value)
|
||||
switch (typeString) {
|
||||
case '[object Array]':
|
||||
return 'array'
|
||||
case '[object Date]':
|
||||
return 'date'
|
||||
case '[object Boolean]':
|
||||
return 'boolean'
|
||||
case '[object Number]':
|
||||
return 'number'
|
||||
case '[object Function]':
|
||||
return 'function'
|
||||
case '[object RegExp]':
|
||||
return 'regexp'
|
||||
case '[object Object]':
|
||||
if (undefined !== value.nodeType) {
|
||||
if (value.nodeType === 3)
|
||||
return /\S/.test(value.nodeValue) ? 'textnode' : 'whitespace'
|
||||
else
|
||||
return 'element'
|
||||
}
|
||||
else {
|
||||
return 'object'
|
||||
}
|
||||
default:
|
||||
return 'unknow'
|
||||
}
|
||||
}
|
||||
//
|
||||
export const objectToString = Object.prototype.toString
|
||||
export const toTypeString = (value: unknown): string => objectToString.call(value)
|
||||
|
||||
export function toRawType(value: unknown): string {
|
||||
// extract "RawType" from strings like "[object RawType]"
|
||||
return toTypeString(value).slice(8, -1)
|
||||
}
|
||||
|
||||
export const win = window
|
||||
|
||||
export const docu = document
|
||||
|
||||
export const body = docu.body
|
||||
|
||||
export function getPropByPath(obj: any, keyPath: string) {
|
||||
try {
|
||||
return keyPath.split('.').reduce((prev, curr) => prev[curr], obj)
|
||||
}
|
||||
// eslint-disable-next-line unused-imports/no-unused-vars
|
||||
catch (error) {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
export function floatData(format: any, dataOp: any, mapOps: any) {
|
||||
const mergeFormat = Object.assign({}, format)
|
||||
const mergeMapOps = Object.assign({}, mapOps)
|
||||
|
||||
if (Object.keys(dataOp).length > 0) {
|
||||
Object.keys(mergeFormat).forEach((keys) => {
|
||||
if (Object.prototype.hasOwnProperty.call(mergeMapOps, keys)) {
|
||||
const tof = TypeOfFun(mergeMapOps[keys])
|
||||
if (tof === 'function')
|
||||
mergeFormat[keys] = mergeMapOps[keys](dataOp)
|
||||
|
||||
if (tof === 'string')
|
||||
mergeFormat[keys] = dataOp[mergeMapOps[keys]]
|
||||
}
|
||||
else {
|
||||
if (dataOp[keys])
|
||||
mergeFormat[keys] = dataOp[keys]
|
||||
}
|
||||
})
|
||||
return mergeFormat
|
||||
}
|
||||
|
||||
return format
|
||||
}
|
||||
|
||||
export function myFixed(num: any, digit = 2) {
|
||||
if (Object.is(Number.parseFloat(num), Number.NaN))
|
||||
return console.warn(`传入的值:${num}不是一个数字`)
|
||||
|
||||
num = Number.parseFloat(num)
|
||||
return (Math.round((num + Number.EPSILON) * 10 ** digit) / 10 ** digit).toFixed(digit)
|
||||
}
|
||||
|
||||
export function preventDefault(event: Event, isStopPropagation?: boolean) {
|
||||
if (typeof event.cancelable !== 'boolean' || event.cancelable)
|
||||
event.preventDefault()
|
||||
|
||||
if (isStopPropagation)
|
||||
event.stopPropagation()
|
||||
}
|
||||
|
||||
function cacheStringFunction<T extends (str: string) => string>(fn: T): T {
|
||||
const cache: Record<string, string> = Object.create(null)
|
||||
return ((str: string) => {
|
||||
const hit = cache[str]
|
||||
|
||||
return hit || (cache[str] = fn(str))
|
||||
}) as T
|
||||
}
|
||||
|
||||
const hyphenateRE = /\B([A-Z])/g
|
||||
export const hyphenate = cacheStringFunction((str: string) =>
|
||||
str.replace(hyphenateRE, '-$1').toLowerCase(),
|
||||
)
|
||||
|
||||
export function padZero(num: number | string, length = 2): string {
|
||||
num += ''
|
||||
while ((num as string).length < length)
|
||||
num = `0${num}`
|
||||
|
||||
return num.toString()
|
||||
}
|
||||
|
||||
export const clamp = (num: number, min: number, max: number): number => Math.min(Math.max(num, min), max)
|
||||
|
||||
export function getScrollTopRoot(): number {
|
||||
return window.scrollY || document.documentElement.scrollTop || document.body.scrollTop || 0
|
||||
}
|
||||
|
||||
type ObjectIndex = Record<string, unknown>
|
||||
|
||||
const { hasOwnProperty } = Object.prototype
|
||||
|
||||
function assignKey(to: ObjectIndex, from: ObjectIndex, key: string) {
|
||||
const val = from[key]
|
||||
|
||||
if (!isDef(val))
|
||||
return
|
||||
|
||||
if (!hasOwnProperty.call(to, key) || !isObject(val))
|
||||
to[key] = val
|
||||
|
||||
else
|
||||
// eslint-disable-next-line unicorn/new-for-builtins
|
||||
to[key] = deepAssign(Object(to[key]), val)
|
||||
}
|
||||
|
||||
export function deepAssign(to: ObjectIndex, from: ObjectIndex): ObjectIndex {
|
||||
Object.keys(from).forEach((key) => {
|
||||
assignKey(to, from, key)
|
||||
})
|
||||
|
||||
return to
|
||||
}
|
||||
|
||||
export function omit(obj: Record<string, unknown>, keys: string[]) {
|
||||
if (Object.prototype.toString.call(obj) === '[object Object]')
|
||||
return obj
|
||||
|
||||
return Object.keys(obj).reduce((prev, key) => {
|
||||
if (!keys.includes(key))
|
||||
prev[key] = obj[key]
|
||||
|
||||
return prev
|
||||
}, {} as Record<string, unknown>)
|
||||
}
|
||||
|
||||
export interface Deferred<T> extends Promise<T> {
|
||||
resolve: (value?: T) => void
|
||||
reject: (value?: any) => void
|
||||
}
|
||||
|
||||
export function createDeferred<T>(): Deferred<T> {
|
||||
let resolve: Deferred<T>['resolve'] = noop
|
||||
let reject: Deferred<T>['reject'] = noop
|
||||
const promise = new Promise((_resolve, _reject) => {
|
||||
resolve = _resolve
|
||||
reject = _reject
|
||||
}) as unknown as Deferred<T>
|
||||
|
||||
promise.resolve = resolve
|
||||
promise.reject = reject
|
||||
return promise
|
||||
}
|
||||
|
||||
export function toArray<T>(value?: T | T[]): T[] {
|
||||
if (!value)
|
||||
return []
|
||||
return Array.isArray(value) ? value : [value]
|
||||
}
|
||||
|
||||
export function noop() { }
|
||||
|
||||
export function getRandomId() {
|
||||
return Math.random().toString(36).slice(-8)
|
||||
}
|
||||
|
||||
export function isLooseEqual(a: any, b: any): boolean {
|
||||
if (a === b)
|
||||
return true
|
||||
|
||||
const isObjectA = isObject(a)
|
||||
const isObjectB = isObject(b)
|
||||
|
||||
if (isObjectA && isObjectB)
|
||||
return JSON.stringify(a) === JSON.stringify(b)
|
||||
else if (!isObjectA && !isObjectB)
|
||||
return String(a) === String(b)
|
||||
else
|
||||
return false
|
||||
}
|
||||
|
||||
export function isEqualArray(a: any, b: any): boolean {
|
||||
if (a === b)
|
||||
return true
|
||||
|
||||
if (!isArray(a) || !isArray(b))
|
||||
return false
|
||||
|
||||
if (a.length !== b.length)
|
||||
return false
|
||||
|
||||
for (let i = 0; i < a.length; i++) {
|
||||
if (!isLooseEqual(a[i], b[i]))
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
export function isEqualValue(a: any, b: any): boolean {
|
||||
if (a === b)
|
||||
return true
|
||||
|
||||
if (isArray(a) && isArray(b))
|
||||
return isEqualArray(a, b)
|
||||
|
||||
return isLooseEqual(a, b)
|
||||
}
|
||||
|
||||
export function cloneDeep<T = any>(obj: T, cache = new WeakMap()): T {
|
||||
if (obj === null || typeof obj !== 'object')
|
||||
return obj
|
||||
if (cache.has(obj))
|
||||
return cache.get(obj)
|
||||
let clone
|
||||
if (obj instanceof Date) {
|
||||
clone = new Date(obj.getTime())
|
||||
}
|
||||
else if (obj instanceof RegExp) {
|
||||
clone = new RegExp(obj)
|
||||
}
|
||||
else if (obj instanceof Map) {
|
||||
clone = new Map(Array.from(obj, ([key, value]) => [key, cloneDeep(value, cache)]))
|
||||
}
|
||||
else if (obj instanceof Set) {
|
||||
clone = new Set(Array.from(obj, value => cloneDeep(value, cache)))
|
||||
}
|
||||
else if (Array.isArray(obj)) {
|
||||
clone = obj.map(value => cloneDeep(value, cache))
|
||||
}
|
||||
else if (Object.prototype.toString.call(obj) === '[object Object]') {
|
||||
clone = Object.create(Object.getPrototypeOf(obj))
|
||||
cache.set(obj, clone)
|
||||
for (const [key, value] of Object.entries(obj))
|
||||
clone[key] = cloneDeep(value, cache)
|
||||
}
|
||||
else {
|
||||
clone = Object.assign({}, obj)
|
||||
}
|
||||
cache.set(obj, clone)
|
||||
return clone
|
||||
}
|
||||
167
uni_modules/nutui-uni/components/_utils/date.ts
Normal file
167
uni_modules/nutui-uni/components/_utils/date.ts
Normal file
@@ -0,0 +1,167 @@
|
||||
/**
|
||||
* 时间戳转换 或 获取当前时间的时间戳
|
||||
*/
|
||||
export function getTimeStamp(timeStr?: string | number) {
|
||||
if (!timeStr)
|
||||
return Date.now()
|
||||
let t = timeStr
|
||||
t = (t as number > 0) ? +t : t.toString().replace(/-/g, '/')
|
||||
return new Date(t).getTime()
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为闫年
|
||||
* @return {Boolse} true|false
|
||||
*/
|
||||
export function isLeapYear(y: number): boolean {
|
||||
return (y % 4 === 0 && y % 100 !== 0) || y % 400 === 0
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回星期数
|
||||
* @return {string}
|
||||
*/
|
||||
export function getWhatDay(year: number, month: number, day: number): string {
|
||||
const date = new Date(`${year}/${month}/${day}`)
|
||||
const index = date.getDay()
|
||||
const dayNames = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六']
|
||||
return dayNames[index]
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回星期数
|
||||
* @return {number}
|
||||
*/
|
||||
export function getMonthPreDay(year: number, month: number): number {
|
||||
const date = new Date(`${year}/${month}/01`)
|
||||
let day = date.getDay()
|
||||
if (day === 0)
|
||||
day = 7
|
||||
|
||||
return day
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回月份天数
|
||||
* @return {number}
|
||||
*/
|
||||
export function getMonthDays(year: string, month: string): number {
|
||||
if (month.startsWith('0'))
|
||||
month = month.split('')[1]
|
||||
|
||||
return ([0, 31, isLeapYear(Number(year)) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] as number[])[
|
||||
month as any
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* 补齐数字位数
|
||||
* @return {string}
|
||||
*/
|
||||
export function getNumTwoBit(n: number): string {
|
||||
n = Number(n)
|
||||
return (n > 9 ? '' : '0') + n
|
||||
}
|
||||
|
||||
/**
|
||||
* 日期对象转成字符串
|
||||
* @return {string}
|
||||
*/
|
||||
export function date2Str(date: Date, split?: string): string {
|
||||
split = split || '-'
|
||||
const y = date.getFullYear()
|
||||
const m = getNumTwoBit(date.getMonth() + 1)
|
||||
const d = getNumTwoBit(date.getDate())
|
||||
return [y, m, d].join(split)
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回日期格式字符串
|
||||
* @param i 0返回今天的日期、1返回明天的日期,2返回后天得日期,依次类推
|
||||
* @return {string} '2014-12-31'
|
||||
*/
|
||||
export function getDay(i: number): string {
|
||||
i = i || 0
|
||||
let date = new Date()
|
||||
const diff = i * (1000 * 60 * 60 * 24)
|
||||
date = new Date(date.getTime() + diff)
|
||||
return date2Str(date)
|
||||
}
|
||||
|
||||
/**
|
||||
* 时间比较
|
||||
* @return {boolean}
|
||||
*/
|
||||
export function compareDate(date1: string, date2: string): boolean {
|
||||
const startTime = new Date(date1.replace('-', '/').replace('-', '/'))
|
||||
const endTime = new Date(date2.replace('-', '/').replace('-', '/'))
|
||||
if (startTime >= endTime)
|
||||
return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* 时间是否相等
|
||||
* @return {boolean}
|
||||
*/
|
||||
export function isEqual(date1: string, date2: string): boolean {
|
||||
const startTime = new Date(date1).getTime()
|
||||
const endTime = new Date(date2).getTime()
|
||||
if (startTime === endTime)
|
||||
return true
|
||||
|
||||
return false
|
||||
}
|
||||
export function getMonthWeek(year: string, month: string, date: string, firstDayOfWeek = 0): number {
|
||||
const dateNow = new Date(Number(year), Number.parseInt(month) - 1, Number(date))
|
||||
let w = dateNow.getDay() // 星期数
|
||||
const d = dateNow.getDate()
|
||||
let remainder = 6 - w
|
||||
if (firstDayOfWeek !== 0) {
|
||||
w = w === 0 ? 7 : w
|
||||
remainder = 7 - w
|
||||
}
|
||||
return Math.ceil((d + remainder) / 7)
|
||||
}
|
||||
export function getYearWeek(year: string, month: string, date: string): number {
|
||||
const dateNow = new Date(Number(year), Number.parseInt(month) - 1, Number(date))
|
||||
const dateFirst = new Date(Number(year), 0, 1)
|
||||
const dataNumber = Math.round((dateNow.valueOf() - dateFirst.valueOf()) / 86400000)
|
||||
return Math.ceil((dataNumber + (dateFirst.getDay() + 1 - 1)) / 7)
|
||||
}
|
||||
export function getWeekDate(year: string, month: string, date: string, firstDayOfWeek = 0): string[] {
|
||||
const dateNow = new Date(Number(year), Number.parseInt(month) - 1, Number(date))
|
||||
const nowTime = dateNow.getTime()
|
||||
let day = dateNow.getDay()
|
||||
if (firstDayOfWeek === 0) {
|
||||
const oneDayTime = 24 * 60 * 60 * 1000
|
||||
// 显示周日
|
||||
const SundayTime = nowTime - day * oneDayTime // 本周的周日
|
||||
// 显示周六
|
||||
const SaturdayTime = nowTime + (6 - day) * oneDayTime // 本周的周六
|
||||
|
||||
const sunday = date2Str(new Date(SundayTime))
|
||||
const saturday = date2Str(new Date(SaturdayTime))
|
||||
return [sunday, saturday]
|
||||
}
|
||||
else {
|
||||
day = day === 0 ? 7 : day
|
||||
const oneDayTime = 24 * 60 * 60 * 1000
|
||||
// 显示周一
|
||||
const MondayTime = nowTime - (day - 1) * oneDayTime // 本周的周一
|
||||
// 显示周日
|
||||
const SundayTime = nowTime + (7 - day) * oneDayTime // 本周的周日
|
||||
|
||||
const monday = date2Str(new Date(MondayTime))
|
||||
const sunday = date2Str(new Date(SundayTime))
|
||||
return [monday, sunday]
|
||||
}
|
||||
}
|
||||
export function formatResultDate(date: string) {
|
||||
const days = [...date.split('-')]
|
||||
days[2] = getNumTwoBit(Number(days[2]))
|
||||
days[3] = `${days[0]}-${days[1]}-${days[2]}`
|
||||
days[4] = getWhatDay(+days[0], +days[1], +days[2])
|
||||
return days
|
||||
}
|
||||
100
uni_modules/nutui-uni/components/_utils/env.ts
Normal file
100
uni_modules/nutui-uni/components/_utils/env.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
/** 枚举EPlatform */
|
||||
enum EPlatform {
|
||||
/** App */
|
||||
AppPlus = 'APP-PLUS',
|
||||
/** App nvue */
|
||||
AppPlusNvue = 'APP-PLUS-NVUE',
|
||||
/** H5 */
|
||||
H5 = 'H5',
|
||||
/** 微信小程序 */
|
||||
MpWeixin = 'MP-WEIXIN',
|
||||
/** 支付宝小程序 */
|
||||
MpAlipay = 'MP-ALIPAY',
|
||||
/** 百度小程序 */
|
||||
MpBaidu = 'MP-BAIDU',
|
||||
/** 字节跳动小程序 */
|
||||
MpToutiao = 'MP-TOUTIAO',
|
||||
/** QQ小程序 */
|
||||
MpQq = 'MP-QQ',
|
||||
/** 360小程序 */
|
||||
Mp360 = 'MP-360',
|
||||
/** 微信小程序/支付宝小程序/百度小程序/字节跳动小程序/QQ小程序/360小程序 */
|
||||
Mp = 'MP',
|
||||
/** 快应用通用(包含联盟、华为) */
|
||||
QuickappWebview = 'quickapp-webview',
|
||||
/** 快应用联盟 */
|
||||
QuickappWebviewUnion = 'quickapp-webview-union',
|
||||
/** 快应用华为 */
|
||||
QuickappWebviewHuawei = 'quickapp-webview-huawei',
|
||||
}
|
||||
|
||||
/** 使用条件编译获取平台信息 */
|
||||
function ifDefPlatform(): EPlatform {
|
||||
let platform: EPlatform
|
||||
// #ifdef APP-PLUS
|
||||
platform = EPlatform.AppPlus
|
||||
// #endif
|
||||
// #ifdef APP-PLUS-NVUE
|
||||
platform = EPlatform.AppPlusNvue
|
||||
// #endif
|
||||
// #ifdef H5
|
||||
platform = EPlatform.H5
|
||||
// #endif
|
||||
// #ifdef MP-WEIXIN
|
||||
platform = EPlatform.MpWeixin
|
||||
// #endif
|
||||
// #ifdef MP-ALIPAY
|
||||
platform = EPlatform.MpAlipay
|
||||
// #endif
|
||||
// #ifdef MP-BAIDU
|
||||
platform = EPlatform.MpBaidu
|
||||
// #endif
|
||||
// #ifdef MP-TOUTIAO
|
||||
platform = EPlatform.MpToutiao
|
||||
// #endif
|
||||
// #ifdef MP-QQ
|
||||
platform = EPlatform.MpQq
|
||||
// #endif
|
||||
// #ifdef MP-360
|
||||
platform = EPlatform.Mp360
|
||||
// #endif
|
||||
// #ifdef MP
|
||||
platform = EPlatform.Mp
|
||||
// #endif
|
||||
// #ifdef quickapp-webview
|
||||
platform = EPlatform.QuickappWebview
|
||||
// #endif
|
||||
// #ifdef quickapp-webview-union
|
||||
platform = EPlatform.QuickappWebviewUnion
|
||||
// #endif
|
||||
// #ifdef quickapp-webview-huawei
|
||||
platform = EPlatform.QuickappWebviewHuawei
|
||||
// #endif
|
||||
return platform
|
||||
}
|
||||
|
||||
/** 平台类型 */
|
||||
export const platform: EPlatform = ifDefPlatform()
|
||||
|
||||
/** H5 */
|
||||
export const isH5 = platform === EPlatform.H5
|
||||
/** 微信小程序 */
|
||||
export const isMpWeixin = platform === EPlatform.MpWeixin
|
||||
/** 支付宝小程序 */
|
||||
export const isMpAlipay = platform === EPlatform.MpAlipay
|
||||
/** 百度小程序 */
|
||||
export const isMpBaidu = platform === EPlatform.MpBaidu
|
||||
/** 字节跳动小程序 */
|
||||
export const isMpToutiao = platform === EPlatform.MpToutiao
|
||||
/** QQ小程序 */
|
||||
export const isMpQq = platform === EPlatform.MpQq
|
||||
/** 360小程序 */
|
||||
export const isMp360 = platform === EPlatform.Mp360
|
||||
/** 微信小程序/支付宝小程序/百度小程序/字节跳动小程序/QQ小程序/360小程序 */
|
||||
export const isMp = platform === EPlatform.Mp
|
||||
/** 快应用通用(包含联盟、华为) */
|
||||
export const isQuickappWebview = platform === EPlatform.QuickappWebview
|
||||
/** 快应用联盟 */
|
||||
export const isQuickappWebviewUnion = platform === EPlatform.QuickappWebviewUnion
|
||||
/** 快应用华为 */
|
||||
export const isQuickappWebviewHuawei = platform === EPlatform.QuickappWebviewHuawei
|
||||
9
uni_modules/nutui-uni/components/_utils/index.ts
Normal file
9
uni_modules/nutui-uni/components/_utils/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export * from './common'
|
||||
export * from './date'
|
||||
export * from './env'
|
||||
export * from './interceptor'
|
||||
export * from './is'
|
||||
export * from './props'
|
||||
export * from './pxCheck'
|
||||
export * from './raf'
|
||||
export * from './style'
|
||||
37
uni_modules/nutui-uni/components/_utils/interceptor.ts
Normal file
37
uni_modules/nutui-uni/components/_utils/interceptor.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { isPromise } from './is'
|
||||
|
||||
export type Interceptor = (...args: any[]) => Promise<boolean> | boolean | undefined | void
|
||||
|
||||
export function funInterceptor(interceptor: Interceptor | undefined, {
|
||||
args = [],
|
||||
done,
|
||||
canceled,
|
||||
}: {
|
||||
args?: unknown[]
|
||||
done: (val?: any) => void
|
||||
canceled?: () => void
|
||||
}) {
|
||||
if (interceptor) {
|
||||
const returnVal = interceptor(null, ...args)
|
||||
|
||||
if (isPromise(returnVal)) {
|
||||
returnVal
|
||||
.then((value) => {
|
||||
if (value)
|
||||
done(value)
|
||||
else if (canceled)
|
||||
canceled()
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
else if (returnVal) {
|
||||
done()
|
||||
}
|
||||
else if (canceled) {
|
||||
canceled()
|
||||
}
|
||||
}
|
||||
else {
|
||||
done()
|
||||
}
|
||||
}
|
||||
96
uni_modules/nutui-uni/components/_utils/is.ts
Normal file
96
uni_modules/nutui-uni/components/_utils/is.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
const toString = Object.prototype.toString
|
||||
|
||||
export function is(val: unknown, type: string) {
|
||||
return toString.call(val) === `[object ${type}]`
|
||||
}
|
||||
|
||||
export function isDef<T = unknown>(val?: T): val is T {
|
||||
return typeof val !== 'undefined'
|
||||
}
|
||||
|
||||
export function isUnDef<T = unknown>(val?: T): val is T {
|
||||
return !isDef(val)
|
||||
}
|
||||
|
||||
export function isObject(val: any): val is Record<any, any> {
|
||||
return val !== null && is(val, 'Object')
|
||||
}
|
||||
|
||||
export function isEmpty<T = unknown>(val: T): val is T {
|
||||
if (isArray(val) || isString(val))
|
||||
return val.length === 0
|
||||
|
||||
if (val instanceof Map || val instanceof Set)
|
||||
return val.size === 0
|
||||
|
||||
if (isObject(val))
|
||||
return Object.keys(val).length === 0
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
export function isDate(val: unknown): val is Date {
|
||||
return is(val, 'Date')
|
||||
}
|
||||
|
||||
export function isNull(val: unknown): val is null {
|
||||
return val === null
|
||||
}
|
||||
|
||||
export function isNullAndUnDef(val: unknown): val is null | undefined {
|
||||
return isUnDef(val) && isNull(val)
|
||||
}
|
||||
|
||||
export function isNullOrUnDef(val: unknown): val is null | undefined {
|
||||
return isUnDef(val) || isNull(val)
|
||||
}
|
||||
|
||||
export function isNumber(val: unknown): val is number {
|
||||
return is(val, 'Number')
|
||||
}
|
||||
|
||||
export function isPromise<T = any>(val: unknown): val is Promise<T> {
|
||||
return (
|
||||
is(val, 'Promise')
|
||||
|| ((isObject(val) || isFunction(val))
|
||||
&& isFunction((val as any).then)
|
||||
&& isFunction((val as any).catch))
|
||||
)
|
||||
}
|
||||
|
||||
export function isString(val: unknown): val is string {
|
||||
return is(val, 'String')
|
||||
}
|
||||
|
||||
export function isFunction(val: unknown): val is () => void {
|
||||
return typeof val === 'function'
|
||||
}
|
||||
|
||||
export function isBoolean(val: unknown): val is boolean {
|
||||
return is(val, 'Boolean')
|
||||
}
|
||||
|
||||
export function isRegExp(val: unknown): val is RegExp {
|
||||
return is(val, 'RegExp')
|
||||
}
|
||||
|
||||
export function isArray(val: any): val is Array<any> {
|
||||
return val && Array.isArray(val)
|
||||
}
|
||||
|
||||
export function isWindow(val: any): val is Window {
|
||||
return typeof window !== 'undefined' && is(val, 'Window')
|
||||
}
|
||||
|
||||
export function isElement(val: unknown): val is Element {
|
||||
return isObject(val) && !!val.tagName
|
||||
}
|
||||
|
||||
export function isMap(val: unknown): val is Map<any, any> {
|
||||
return is(val, 'Map')
|
||||
}
|
||||
|
||||
export function isUrl(path: string): boolean {
|
||||
const reg = /^http(s)?:\/\/([\w-]+\.)+[\w-]+(\/[\w- ./?%&=]*)?/
|
||||
return reg.test(path)
|
||||
}
|
||||
83
uni_modules/nutui-uni/components/_utils/props.ts
Normal file
83
uni_modules/nutui-uni/components/_utils/props.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* prop type helpers
|
||||
* help us to write less code and reduce bundle size
|
||||
* copy from https://github.com/youzan/vant/blob/main/packages/vant/src/utils/props.ts
|
||||
*/
|
||||
import type { ExtractPropTypes, PropType, StyleValue } from 'vue'
|
||||
|
||||
export const unknownProp = null as unknown as PropType<unknown>
|
||||
|
||||
export const numericProp = [Number, String]
|
||||
|
||||
export const truthProp = {
|
||||
type: Boolean,
|
||||
default: true as const,
|
||||
}
|
||||
|
||||
export const nullableBooleanProp = {
|
||||
type: Boolean as PropType<boolean | undefined>,
|
||||
default: undefined,
|
||||
}
|
||||
|
||||
export function makeRequiredProp<T>(type: T) {
|
||||
return {
|
||||
type,
|
||||
required: true as const,
|
||||
}
|
||||
}
|
||||
|
||||
export function makeArrayProp<T>(defaultVal: T[] = []) {
|
||||
return {
|
||||
type: Array as PropType<T[]>,
|
||||
default: () => defaultVal,
|
||||
}
|
||||
}
|
||||
|
||||
export function makeObjectProp<T>(defaultVal: T) {
|
||||
return {
|
||||
type: Object as PropType<T>,
|
||||
default: () => defaultVal,
|
||||
}
|
||||
}
|
||||
|
||||
export function makeNumberProp<T>(defaultVal: T) {
|
||||
return {
|
||||
type: Number,
|
||||
default: defaultVal,
|
||||
}
|
||||
}
|
||||
|
||||
export function makeNumericProp<T>(defaultVal: T) {
|
||||
return {
|
||||
type: numericProp,
|
||||
default: defaultVal,
|
||||
}
|
||||
}
|
||||
|
||||
export function makeStringProp<T>(defaultVal: T) {
|
||||
return {
|
||||
type: String as unknown as PropType<T>,
|
||||
default: defaultVal,
|
||||
}
|
||||
}
|
||||
|
||||
export type ClassType = string | object | Array<ClassType>
|
||||
|
||||
export const commonProps = {
|
||||
/**
|
||||
* @description 自定义类名
|
||||
*/
|
||||
customClass: {
|
||||
type: [String, Object, Array] as PropType<ClassType>,
|
||||
default: '',
|
||||
},
|
||||
/**
|
||||
* @description 自定义样式
|
||||
*/
|
||||
customStyle: {
|
||||
type: [String, Object, Array] as PropType<StyleValue>,
|
||||
default: '',
|
||||
},
|
||||
}
|
||||
|
||||
export type CommonProps = ExtractPropTypes<typeof commonProps>
|
||||
3
uni_modules/nutui-uni/components/_utils/pxCheck.ts
Normal file
3
uni_modules/nutui-uni/components/_utils/pxCheck.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export function pxCheck(value: string | number): string {
|
||||
return Number.isNaN(Number(value)) ? String(value) : `${value}px`
|
||||
}
|
||||
30
uni_modules/nutui-uni/components/_utils/raf.ts
Normal file
30
uni_modules/nutui-uni/components/_utils/raf.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
const _window = window as any
|
||||
|
||||
export const inBrowser = typeof window !== 'undefined'
|
||||
|
||||
function requestAniFrame() {
|
||||
if (typeof _window !== 'undefined') {
|
||||
return (
|
||||
_window.requestAnimationFrame
|
||||
|| _window.webkitRequestAnimationFrame
|
||||
|| function (callback: () => void) {
|
||||
_window.setTimeout(callback, 1000 / 60)
|
||||
}
|
||||
)
|
||||
}
|
||||
else {
|
||||
return function (callback: () => void) {
|
||||
setTimeout(callback, 1000 / 60)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function cancelRaf(id: number) {
|
||||
if (inBrowser)
|
||||
cancelAnimationFrame(id)
|
||||
|
||||
else
|
||||
clearTimeout(id)
|
||||
}
|
||||
|
||||
export default requestAniFrame()
|
||||
167
uni_modules/nutui-uni/components/_utils/style.ts
Normal file
167
uni_modules/nutui-uni/components/_utils/style.ts
Normal file
@@ -0,0 +1,167 @@
|
||||
import type { CSSProperties } from 'vue'
|
||||
import { hyphenate } from './common'
|
||||
import { isArray, isEmpty, isNumber, isObject, isString } from './is'
|
||||
import type { CommonProps } from './props'
|
||||
|
||||
export type NormalizedStyle = Record<string, string | number>
|
||||
|
||||
const listDelimiterRE = /;(?![^(]*\))/g
|
||||
const propertyDelimiterRE = /:([\s\S]+)/
|
||||
const styleCommentRE = /\/\*.*?\*\//g
|
||||
|
||||
export function parseStringStyle(cssText: string): NormalizedStyle {
|
||||
const ret: NormalizedStyle = {}
|
||||
cssText
|
||||
.replace(styleCommentRE, '')
|
||||
.split(listDelimiterRE)
|
||||
.forEach((item) => {
|
||||
if (item) {
|
||||
const tmp = item.split(propertyDelimiterRE)
|
||||
tmp.length > 1 && (ret[tmp[0].trim()] = tmp[1].trim())
|
||||
}
|
||||
})
|
||||
return ret
|
||||
}
|
||||
|
||||
export function stringifyStyle(styles: NormalizedStyle | string | undefined): string {
|
||||
let ret = ''
|
||||
if (!styles || isString(styles))
|
||||
return ret
|
||||
|
||||
for (const key in styles) {
|
||||
const value = styles[key]
|
||||
const normalizedKey = key.startsWith('--') ? key : hyphenate(key)
|
||||
if (isString(value) || typeof value === 'number') {
|
||||
// only render valid values
|
||||
ret += `${normalizedKey}:${value};`
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
export function getPx(value: string | number, unit = false) {
|
||||
if (isNumber(value))
|
||||
return unit ? `${value}px` : Number(value)
|
||||
|
||||
return unit ? `${Number.parseInt(value)}px` : Number.parseInt(value)
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 样式转换
|
||||
* 对象转字符串,或者字符串转对象
|
||||
* @param {object | string} customStyle 需要转换的目标
|
||||
* @param {string} target 转换的目的,object-转为对象,string-转为字符串
|
||||
*/
|
||||
export function addStyle(customStyle: string | object, target = 'object') {
|
||||
// 字符串转字符串,对象转对象情形,直接返回
|
||||
if (
|
||||
isEmpty(customStyle)
|
||||
|| (typeof customStyle === 'object' && target === 'object')
|
||||
|| (target === 'string' && typeof customStyle === 'string')
|
||||
) {
|
||||
return customStyle
|
||||
}
|
||||
|
||||
// 字符串转对象
|
||||
if (target === 'object') {
|
||||
// 去除字符串样式中的两端空格(中间的空格不能去掉,比如padding: 20px 0如果去掉了就错了),空格是无用的
|
||||
customStyle = trim(customStyle)
|
||||
// 根据";"将字符串转为数组形式
|
||||
const styleArray = customStyle.split(';')
|
||||
const style: any = {}
|
||||
// 历遍数组,拼接成对象
|
||||
for (let i = 0; i < styleArray.length; i++) {
|
||||
// 'font-size:20px;color:red;',如此最后字符串有";"的话,会导致styleArray最后一个元素为空字符串,这里需要过滤
|
||||
if (styleArray[i]) {
|
||||
const item = styleArray[i].split(':')
|
||||
style[trim(item[0])] = trim(item[1])
|
||||
}
|
||||
}
|
||||
return style
|
||||
}
|
||||
// 这里为对象转字符串形式
|
||||
let string = ''
|
||||
for (const i in customStyle as any) {
|
||||
// 驼峰转为中划线的形式,否则css内联样式,无法识别驼峰样式属性名
|
||||
const key = i.replace(/([A-Z])/g, '-$1').toLowerCase()
|
||||
string += `${key}:${customStyle[i]};`
|
||||
}
|
||||
// 去除两端空格
|
||||
return trim(string)
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 去除空格
|
||||
* @param str 需要去除空格的字符串
|
||||
* @param pos both(左右)|left|right|all 默认both
|
||||
*/
|
||||
export function trim(str: string, pos = 'both') {
|
||||
str = String(str)
|
||||
if (pos === 'both')
|
||||
return str.replace(/^\s+|\s+$/g, '')
|
||||
|
||||
if (pos === 'left')
|
||||
return str.replace(/^\s*/, '')
|
||||
|
||||
if (pos === 'right')
|
||||
return str.replace(/(\s*$)/g, '')
|
||||
|
||||
if (pos === 'all')
|
||||
return str.replace(/\s+/g, '')
|
||||
|
||||
return str
|
||||
}
|
||||
|
||||
export function normalizeStyle(value: unknown): NormalizedStyle | string | undefined {
|
||||
if (isArray(value)) {
|
||||
const res: NormalizedStyle = {}
|
||||
for (let i = 0; i < value.length; i++) {
|
||||
const item = value[i]
|
||||
const normalized = isString(item)
|
||||
? parseStringStyle(item)
|
||||
: (normalizeStyle(item) as NormalizedStyle)
|
||||
if (normalized) {
|
||||
for (const key in normalized) {
|
||||
if (!isEmpty(normalized[key]))
|
||||
res[key] = normalized[key]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
if (isString(value))
|
||||
return value
|
||||
|
||||
if (isObject(value))
|
||||
return value
|
||||
}
|
||||
|
||||
export function normalizeClass(value: unknown): string {
|
||||
let res = ''
|
||||
if (isString(value)) {
|
||||
res = value
|
||||
}
|
||||
else if (isArray(value)) {
|
||||
for (let i = 0; i < value.length; i++) {
|
||||
const normalized = normalizeClass(value[i])
|
||||
if (normalized)
|
||||
res += `${normalized} `
|
||||
}
|
||||
}
|
||||
else if (isObject(value)) {
|
||||
for (const name in value) {
|
||||
if (value[name])
|
||||
res += `${name} `
|
||||
}
|
||||
}
|
||||
return res.trim()
|
||||
}
|
||||
|
||||
export function getMainClass(props: CommonProps, componentName: string, cls?: object) {
|
||||
return normalizeClass([props.customClass, { [componentName]: true }, cls])
|
||||
}
|
||||
|
||||
export function getMainStyle(props: CommonProps, style?: CSSProperties) {
|
||||
return stringifyStyle(normalizeStyle([props.customStyle, style]))
|
||||
}
|
||||
82
uni_modules/nutui-uni/components/actionsheet/actionsheet.ts
Normal file
82
uni_modules/nutui-uni/components/actionsheet/actionsheet.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import type { CSSProperties, ExtractPropTypes, PropType } from 'vue'
|
||||
import { CANCEL_EVENT, CHOOSE_EVENT, CLOSE_EVENT, UPDATE_VISIBLE_EVENT } from '../_constants'
|
||||
import { commonProps, isBoolean, isNumber, makeArrayProp, makeStringProp, truthProp } from '../_utils'
|
||||
import { popupProps } from '../popup/popup'
|
||||
|
||||
export interface ActionSheetOption {
|
||||
disable?: boolean
|
||||
loading?: boolean
|
||||
color?: string
|
||||
name: string
|
||||
subname?: string
|
||||
}
|
||||
|
||||
export const actionsheetProps = {
|
||||
...popupProps,
|
||||
...commonProps,
|
||||
/**
|
||||
* @description 是否显示圆角
|
||||
*/
|
||||
round: truthProp,
|
||||
/**
|
||||
* @description 是否开启 iPhone 系列全面屏底部安全区适配,仅当 `position` 为 `bottom` 时有效
|
||||
*/
|
||||
safeAreaInsetBottom: truthProp,
|
||||
/**
|
||||
* @description 遮罩显示时的背景是否锁定
|
||||
*/
|
||||
lockScroll: truthProp,
|
||||
/**
|
||||
* @description 自定义 popup 弹框样式
|
||||
*/
|
||||
popStyle: {
|
||||
type: Object as PropType<CSSProperties>,
|
||||
},
|
||||
/**
|
||||
* @description 取消文案
|
||||
*/
|
||||
cancelTxt: makeStringProp(''),
|
||||
/**
|
||||
* @description 设置列表项标题展示使用参数
|
||||
*/
|
||||
optionTag: makeStringProp<keyof ActionSheetOption>('name'),
|
||||
/**
|
||||
* @description 设置列表项二级标题展示使用参数
|
||||
*/
|
||||
optionSubTag: makeStringProp<keyof ActionSheetOption>('subname'),
|
||||
/**
|
||||
* @description 设置选中项的值,和 'option-tag' 的值对应
|
||||
*/
|
||||
chooseTagValue: makeStringProp(''),
|
||||
/**
|
||||
* @description 设置列表项标题
|
||||
*/
|
||||
title: makeStringProp(''),
|
||||
/**
|
||||
* @description 选中项颜色,当 choose-tag-value == option-tag 的值 生效
|
||||
*/
|
||||
customColor: makeStringProp('#ee0a24'),
|
||||
/**
|
||||
* @description 设置列表项副标题/描述
|
||||
*/
|
||||
description: makeStringProp(''),
|
||||
/**
|
||||
* @description 列表项
|
||||
*/
|
||||
menuItems: makeArrayProp<ActionSheetOption>([]),
|
||||
/**
|
||||
* @description 遮罩层是否可关闭
|
||||
*/
|
||||
closeAbled: truthProp,
|
||||
}
|
||||
|
||||
export type ActionsheetProps = ExtractPropTypes<typeof actionsheetProps>
|
||||
|
||||
export const actionsheetEmits = {
|
||||
[CLOSE_EVENT]: () => true,
|
||||
[UPDATE_VISIBLE_EVENT]: (val: boolean) => isBoolean(val),
|
||||
[CANCEL_EVENT]: () => true,
|
||||
[CHOOSE_EVENT]: (item: ActionSheetOption, index: number) => item instanceof Object && isNumber(index),
|
||||
}
|
||||
|
||||
export type ActionsheetEmits = typeof actionsheetEmits
|
||||
120
uni_modules/nutui-uni/components/actionsheet/actionsheet.vue
Normal file
120
uni_modules/nutui-uni/components/actionsheet/actionsheet.vue
Normal file
@@ -0,0 +1,120 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, defineComponent, useSlots } from 'vue'
|
||||
import { CANCEL_EVENT, CHOOSE_EVENT, CLOSE_EVENT, PREFIX, UPDATE_VISIBLE_EVENT } from '../_constants'
|
||||
import { getMainClass } from '../_utils'
|
||||
import NutIcon from '../icon/icon.vue'
|
||||
import NutPopup from '../popup/popup.vue'
|
||||
import type { ActionSheetOption } from './actionsheet'
|
||||
import { actionsheetEmits, actionsheetProps } from './actionsheet'
|
||||
|
||||
const props = defineProps(actionsheetProps)
|
||||
|
||||
const emit = defineEmits(actionsheetEmits)
|
||||
|
||||
const slotDefault = !!useSlots().default
|
||||
|
||||
const classes = computed(() => {
|
||||
return getMainClass(props, componentName)
|
||||
})
|
||||
|
||||
function isHighlight(item: ActionSheetOption) {
|
||||
return props.chooseTagValue && props.chooseTagValue === item[props.optionTag] ? props.customColor : ''
|
||||
}
|
||||
|
||||
function cancelActionSheet() {
|
||||
emit(CANCEL_EVENT)
|
||||
emit(UPDATE_VISIBLE_EVENT, false)
|
||||
}
|
||||
|
||||
function chooseItem(item: ActionSheetOption, index: number) {
|
||||
if (!item.disable && !item.loading) {
|
||||
emit(CHOOSE_EVENT, item, index)
|
||||
emit(UPDATE_VISIBLE_EVENT, false)
|
||||
}
|
||||
}
|
||||
|
||||
function close() {
|
||||
if (props.closeAbled) {
|
||||
emit(CLOSE_EVENT)
|
||||
emit(UPDATE_VISIBLE_EVENT, false)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
const componentName = `${PREFIX}-action-sheet`
|
||||
|
||||
export default defineComponent({
|
||||
name: componentName,
|
||||
options: {
|
||||
virtualHost: true,
|
||||
addGlobalClass: true,
|
||||
styleIsolation: 'shared',
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NutPopup
|
||||
:pop-class="props.popClass"
|
||||
:custom-style="props.popStyle"
|
||||
:visible="props.visible"
|
||||
position="bottom"
|
||||
:overlay="props.overlay"
|
||||
:round="props.round"
|
||||
:safe-area-inset-bottom="props.safeAreaInsetBottom"
|
||||
:z-index="props.zIndex"
|
||||
:duration="props.duration"
|
||||
:overlay-class="props.overlayClass"
|
||||
:overlay-style="props.overlayStyle"
|
||||
:lock-scroll="props.lockScroll"
|
||||
:close-on-click-overlay="props.closeAbled"
|
||||
@click-overlay="close"
|
||||
>
|
||||
<view :class="classes" :style="props.customStyle">
|
||||
<view v-if="props.title" class="nut-action-sheet__title">
|
||||
{{ props.title }}
|
||||
</view>
|
||||
|
||||
<slot />
|
||||
|
||||
<view v-if="!slotDefault">
|
||||
<view v-if="props.description" class="nut-action-sheet__item nut-action-sheet__desc">
|
||||
{{ props.description }}
|
||||
</view>
|
||||
|
||||
<view v-if="props.menuItems.length" class="nut-action-sheet__menu">
|
||||
<view
|
||||
v-for="(item, index) of props.menuItems"
|
||||
:key="index"
|
||||
class="nut-action-sheet__item"
|
||||
:class="{
|
||||
'nut-action-sheet__item--disabled': item.disable,
|
||||
'nut-action-sheet__item--loading': item.loading,
|
||||
}"
|
||||
:style="{ color: isHighlight(item) || item.color }"
|
||||
@click="chooseItem(item, index)"
|
||||
>
|
||||
<NutIcon v-if="item.loading" name="loading" />
|
||||
|
||||
<view v-else>
|
||||
{{ item[props.optionTag] }}
|
||||
</view>
|
||||
|
||||
<view class="nut-action-sheet__subdesc">
|
||||
{{ item[props.optionSubTag] }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="props.cancelTxt" class="nut-action-sheet__cancel" @click="cancelActionSheet">
|
||||
{{ props.cancelTxt }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</NutPopup>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import './index';
|
||||
</style>
|
||||
82
uni_modules/nutui-uni/components/actionsheet/index.scss
Normal file
82
uni_modules/nutui-uni/components/actionsheet/index.scss
Normal file
@@ -0,0 +1,82 @@
|
||||
@import "../popup/index";
|
||||
|
||||
.nut-theme-dark {
|
||||
.nut-action-sheet {
|
||||
.nut-action-sheet__cancel {
|
||||
border-top: 1px solid $dark-background2;
|
||||
}
|
||||
|
||||
.nut-action-sheet__title {
|
||||
border-bottom: 1px solid $dark-background2;
|
||||
}
|
||||
|
||||
.nut-action-sheet__cancel,
|
||||
.nut-action-sheet__item,
|
||||
.nut-action-sheet__title {
|
||||
color: $dark-color;
|
||||
background: $dark-background;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nut-action-sheet {
|
||||
display: block;
|
||||
|
||||
.nut-action-sheet__title {
|
||||
display: block;
|
||||
padding: 10px;
|
||||
margin: 0;
|
||||
font-size: $font-size-base;
|
||||
color: $title-color;
|
||||
text-align: center;
|
||||
background-color: $white;
|
||||
border-bottom: 1px solid $actionsheet-light-color;
|
||||
}
|
||||
|
||||
.nut-action-sheet__menu {
|
||||
display: block;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.nut-action-sheet__cancel,
|
||||
.nut-action-sheet__item {
|
||||
display: block;
|
||||
padding: 10px;
|
||||
font-size: $actionsheet-item-font-size;
|
||||
line-height: $actionsheet-item-line-height;
|
||||
color: $actionsheet-item-font-color;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
background-color: #fff;
|
||||
border-bottom: $actionsheet-item-border-bottom;
|
||||
}
|
||||
|
||||
.nut-action-sheet__desc {
|
||||
font-size: $actionsheet-item-font-size;
|
||||
color: #999;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.nut-action-sheet__subdesc {
|
||||
display: block;
|
||||
font-size: $actionsheet-item-subdesc-font-size;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.nut-action-sheet__item--disabled {
|
||||
color: #e1e1e1 !important;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.nut-action-sheet__item--loading {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.nut-action-sheet__cancel {
|
||||
margin-top: 5px;
|
||||
border-top: $actionsheet-item-cancel-border-top;
|
||||
}
|
||||
}
|
||||
|
||||
1
uni_modules/nutui-uni/components/actionsheet/index.ts
Normal file
1
uni_modules/nutui-uni/components/actionsheet/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './actionsheet'
|
||||
104
uni_modules/nutui-uni/components/address/address.ts
Normal file
104
uni_modules/nutui-uni/components/address/address.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import type { ExtractPropTypes } from 'vue'
|
||||
import { CHANGE_EVENT, CLOSE_EVENT, SELECTED_EVENT, UPDATE_MODEL_EVENT, UPDATE_VISIBLE_EVENT } from '../_constants'
|
||||
import { commonProps, isBoolean, makeArrayProp, makeNumericProp, makeStringProp, truthProp } from '../_utils'
|
||||
import { popupProps } from '../popup'
|
||||
import type { AddressExistRegionData, AddressRegionData, AddressType } from './type'
|
||||
|
||||
export const addressProps = {
|
||||
...popupProps,
|
||||
...commonProps,
|
||||
/**
|
||||
* @description 设置默认选中值
|
||||
*/
|
||||
modelValue: makeArrayProp<any>([]),
|
||||
|
||||
/**
|
||||
* @description 是否打开地址选择
|
||||
*/
|
||||
visible: Boolean,
|
||||
|
||||
/**
|
||||
* @description 地址选择类型:'exist' | 'custom' | 'custom2'
|
||||
*/
|
||||
type: makeStringProp<AddressType>('custom'),
|
||||
|
||||
/**
|
||||
* @description 自定义地址选择标题
|
||||
*/
|
||||
customAddressTitle: makeStringProp(''),
|
||||
|
||||
/**
|
||||
* @description 省份数据
|
||||
*/
|
||||
province: makeArrayProp<AddressRegionData>([]),
|
||||
|
||||
/**
|
||||
* @description 城市数据
|
||||
*/
|
||||
city: makeArrayProp<AddressRegionData>([]),
|
||||
|
||||
/**
|
||||
* @description 县区数据
|
||||
*/
|
||||
country: makeArrayProp<AddressRegionData>([]),
|
||||
|
||||
/**
|
||||
* @description 乡镇数据
|
||||
*/
|
||||
town: makeArrayProp<AddressRegionData>([]),
|
||||
|
||||
/**
|
||||
* @description 是否显示 '选择其他地区' 按钮。仅在类型为 'exist' 时生效
|
||||
*/
|
||||
isShowCustomAddress: truthProp,
|
||||
|
||||
/**
|
||||
* @description 现存地址列表
|
||||
*/
|
||||
existAddress: makeArrayProp<AddressExistRegionData>([]),
|
||||
|
||||
/**
|
||||
* @description 已有地址标题
|
||||
*/
|
||||
existAddressTitle: makeStringProp(''),
|
||||
|
||||
/**
|
||||
* @description 切换自定义地址和已有地址的按钮标题
|
||||
*/
|
||||
customAndExistTitle: makeStringProp(''),
|
||||
|
||||
/**
|
||||
* @description 弹层中内容容器的高度
|
||||
*/
|
||||
height: makeNumericProp('200'),
|
||||
|
||||
/**
|
||||
* @description 列提示文字
|
||||
*/
|
||||
columnsPlaceholder: {
|
||||
type: [String, Array],
|
||||
default: '',
|
||||
},
|
||||
}
|
||||
|
||||
export type AddressProps = ExtractPropTypes<typeof addressProps>
|
||||
|
||||
export const addressEmits = {
|
||||
[UPDATE_VISIBLE_EVENT]: (val: boolean) => isBoolean(val),
|
||||
[UPDATE_MODEL_EVENT]: () => true,
|
||||
[CLOSE_EVENT]: (val: {
|
||||
data: any
|
||||
type: string
|
||||
}) => val instanceof Object,
|
||||
[CHANGE_EVENT]: (val: {
|
||||
next?: string
|
||||
value?: AddressRegionData
|
||||
custom: string
|
||||
}) => val instanceof Object,
|
||||
switchModule: (val: { type: AddressType }) => val instanceof Object,
|
||||
closeMask: (val: { closeWay: 'self' | 'mask' | 'cross' }) => val instanceof Object,
|
||||
[SELECTED_EVENT]: (prevExistAdd: AddressExistRegionData, item: AddressExistRegionData, copyExistAdd: AddressExistRegionData[]) => prevExistAdd instanceof Object && item instanceof Object && copyExistAdd instanceof Object,
|
||||
|
||||
}
|
||||
|
||||
export type AddressEmits = typeof addressEmits
|
||||
443
uni_modules/nutui-uni/components/address/address.vue
Normal file
443
uni_modules/nutui-uni/components/address/address.vue
Normal file
@@ -0,0 +1,443 @@
|
||||
<script setup lang="ts">
|
||||
import type { ScrollViewOnScrollEvent } from '@uni-helper/uni-app-types'
|
||||
import { computed, defineComponent, reactive, ref, watch } from 'vue'
|
||||
import { CHANGE_EVENT, CLOSE_EVENT, PREFIX, SELECTED_EVENT, UPDATE_MODEL_EVENT, UPDATE_VISIBLE_EVENT } from '../_constants'
|
||||
import { getMainClass } from '../_utils'
|
||||
import requestAniFrame from '../_utils/raf'
|
||||
import { useTranslate } from '../../locale'
|
||||
import NutElevator from '../elevator/elevator.vue'
|
||||
import NutIcon from '../icon/icon.vue'
|
||||
import NutPopup from '../popup/popup.vue'
|
||||
import { addressEmits, addressProps } from './address'
|
||||
import type { AddressExistRegionData, AddressRegionData, CustomRegionData } from './type'
|
||||
|
||||
const props = defineProps(addressProps)
|
||||
const emit = defineEmits(addressEmits)
|
||||
|
||||
const classes = computed(() => {
|
||||
return getMainClass(props, componentName)
|
||||
})
|
||||
|
||||
const showPopup = ref(props.visible)
|
||||
const privateType = ref(props.type)
|
||||
const tabIndex = ref(0)
|
||||
const prevTabIndex = ref(0)
|
||||
const tabName = ref(['province', 'city', 'country', 'town'])
|
||||
const scrollDis = ref([0, 0, 0, 0])
|
||||
const scrollTop = ref(0)
|
||||
const regionData = reactive<Array<AddressRegionData[]>>([])
|
||||
|
||||
const regionList = computed(() => {
|
||||
switch (tabIndex.value) {
|
||||
case 0:
|
||||
return props.province
|
||||
case 1:
|
||||
return props.city
|
||||
case 2:
|
||||
return props.country
|
||||
default:
|
||||
return props.town
|
||||
}
|
||||
})
|
||||
|
||||
function transformData(data: AddressRegionData[]) {
|
||||
if (!Array.isArray(data))
|
||||
throw new TypeError('params muse be array.')
|
||||
|
||||
if (!data.length)
|
||||
return []
|
||||
|
||||
data.forEach((item: AddressRegionData) => {
|
||||
if (!item.title)
|
||||
console.warn('[NutUI] <Address> 请检查数组选项的 title 值是否有设置 ,title 为必填项 .')
|
||||
})
|
||||
|
||||
const newData: CustomRegionData[] = []
|
||||
|
||||
data = data.sort((a: AddressRegionData, b: AddressRegionData) => {
|
||||
return a.title.localeCompare(b.title)
|
||||
})
|
||||
|
||||
data.forEach((item: AddressRegionData) => {
|
||||
const index = newData.findIndex((value: CustomRegionData) => value.title === item.title)
|
||||
if (index <= -1) {
|
||||
newData.push({
|
||||
title: item.title,
|
||||
list: ([] as any).concat(item),
|
||||
})
|
||||
}
|
||||
else {
|
||||
newData[index].list.push(item)
|
||||
}
|
||||
})
|
||||
|
||||
return newData
|
||||
}
|
||||
|
||||
const selectedRegion = ref<AddressRegionData[]>([])
|
||||
|
||||
let selectedExistAddress = reactive({}) // 当前选择的地址
|
||||
|
||||
const closeWay = ref<'self' | 'mask' | 'cross'>('self')
|
||||
|
||||
// 设置选中省市县
|
||||
function initCustomSelected() {
|
||||
regionData[0] = props.province || []
|
||||
regionData[1] = props.city || []
|
||||
regionData[2] = props.country || []
|
||||
regionData[3] = props.town || []
|
||||
|
||||
const defaultValue = props.modelValue
|
||||
const num = defaultValue.length
|
||||
if (num > 0) {
|
||||
tabIndex.value = num - 1
|
||||
if (regionList.value.length === 0) {
|
||||
tabIndex.value = 0
|
||||
return
|
||||
}
|
||||
for (let index = 0; index < num; index++) {
|
||||
const arr: AddressRegionData[] = regionData[index]
|
||||
selectedRegion.value[index] = arr.filter((item: AddressRegionData) => item.id === defaultValue[index])[0]
|
||||
}
|
||||
scrollTo()
|
||||
}
|
||||
}
|
||||
|
||||
function getTabName(item: AddressRegionData | null, index: number) {
|
||||
if (item && item.name)
|
||||
return item.name
|
||||
if (tabIndex.value < index && item)
|
||||
return item.name
|
||||
|
||||
else
|
||||
return props.columnsPlaceholder[index] || translate('select')
|
||||
}
|
||||
|
||||
// 手动关闭 点击叉号(cross),或者蒙层(mask)
|
||||
function handClose(type = 'self') {
|
||||
closeWay.value = type === 'cross' ? 'cross' : 'self'
|
||||
|
||||
showPopup.value = false
|
||||
}
|
||||
|
||||
// 点击遮罩层关闭
|
||||
function clickOverlay() {
|
||||
closeWay.value = 'mask'
|
||||
}
|
||||
|
||||
// 切换下一级列表
|
||||
function nextAreaList(item: AddressRegionData) {
|
||||
const tab = tabIndex.value
|
||||
prevTabIndex.value = tabIndex.value
|
||||
const callBackParams: {
|
||||
next?: string
|
||||
value?: AddressRegionData
|
||||
custom: string
|
||||
} = {
|
||||
custom: tabName.value[tab],
|
||||
}
|
||||
|
||||
selectedRegion.value[tab] = item
|
||||
|
||||
// 删除右边已选择数据
|
||||
selectedRegion.value.splice(tab + 1, selectedRegion.value.length - (tab + 1))
|
||||
|
||||
callBackParams.value = item
|
||||
|
||||
if (regionData[tab + 1]?.length > 0) {
|
||||
tabIndex.value = tab + 1
|
||||
|
||||
callBackParams.next = tabName.value[tabIndex.value]
|
||||
|
||||
scrollToTop()
|
||||
}
|
||||
else {
|
||||
handClose()
|
||||
emit(UPDATE_MODEL_EVENT)
|
||||
}
|
||||
emit(CHANGE_EVENT, callBackParams)
|
||||
}
|
||||
// 切换地区Tab
|
||||
function changeRegionTab(item: AddressRegionData, index: number) {
|
||||
prevTabIndex.value = tabIndex.value
|
||||
if (getTabName(item, index)) {
|
||||
tabIndex.value = index
|
||||
scrollTo()
|
||||
}
|
||||
}
|
||||
|
||||
function scrollChange(e: ScrollViewOnScrollEvent) {
|
||||
scrollDis.value[tabIndex.value] = e.detail.scrollTop
|
||||
}
|
||||
|
||||
function scrollToTop() {
|
||||
// scrollTop 不会实时变更。当再次赋值时,scrollTop无变化时,不会触发滚动
|
||||
scrollTop.value += 1
|
||||
requestAniFrame(() => {
|
||||
setTimeout(() => {
|
||||
// 直接设置为0无效
|
||||
scrollTop.value = 0.01
|
||||
}, 100)
|
||||
})
|
||||
}
|
||||
|
||||
function scrollTo() {
|
||||
// scrollTop 不会实时变更。当再次赋值时,scrollTop无变化时,不会触发滚动
|
||||
scrollTop.value += 1
|
||||
requestAniFrame(() => {
|
||||
setTimeout(() => {
|
||||
scrollTop.value = scrollDis.value[tabIndex.value]
|
||||
}, 10)
|
||||
})
|
||||
}
|
||||
|
||||
// 选择现有地址
|
||||
function selectedExist(item: AddressExistRegionData) {
|
||||
const copyExistAdd = props.existAddress
|
||||
let prevExistAdd: AddressExistRegionData = {} as AddressExistRegionData
|
||||
|
||||
copyExistAdd.forEach((list: AddressExistRegionData) => {
|
||||
if (list && list.selectedAddress)
|
||||
prevExistAdd = list
|
||||
list.selectedAddress = false
|
||||
})
|
||||
|
||||
item.selectedAddress = true
|
||||
|
||||
selectedExistAddress = item
|
||||
|
||||
emit(SELECTED_EVENT, prevExistAdd, item, copyExistAdd)
|
||||
|
||||
handClose()
|
||||
}
|
||||
// 初始化
|
||||
function initAddress() {
|
||||
selectedRegion.value = []
|
||||
tabIndex.value = 0
|
||||
scrollTo()
|
||||
}
|
||||
|
||||
// 关闭
|
||||
function close() {
|
||||
const data = {
|
||||
addressIdStr: '',
|
||||
addressStr: '',
|
||||
province: selectedRegion.value[0],
|
||||
city: selectedRegion.value[1],
|
||||
country: selectedRegion.value[2],
|
||||
town: selectedRegion.value[3],
|
||||
}
|
||||
|
||||
const callBackParams = {
|
||||
data: {},
|
||||
type: privateType.value,
|
||||
}
|
||||
|
||||
if (['custom', 'custom2'].includes(privateType.value)) {
|
||||
[0, 1, 2, 3].forEach((i) => {
|
||||
const item = selectedRegion.value[i]
|
||||
data.addressIdStr += `${i ? '_' : ''}${(item && item.id) || 0}`
|
||||
data.addressStr += (item && item.name) || ''
|
||||
})
|
||||
|
||||
callBackParams.data = data
|
||||
}
|
||||
else {
|
||||
callBackParams.data = selectedExistAddress
|
||||
}
|
||||
|
||||
initAddress()
|
||||
|
||||
if (closeWay.value === 'self')
|
||||
emit(CLOSE_EVENT, callBackParams)
|
||||
else
|
||||
emit('closeMask', { closeWay: closeWay.value })
|
||||
|
||||
emit(UPDATE_VISIBLE_EVENT, false)
|
||||
}
|
||||
|
||||
// 选择其他地址
|
||||
function switchModule() {
|
||||
const type = privateType.value
|
||||
privateType.value = type === 'exist' ? 'custom' : 'exist'
|
||||
initAddress()
|
||||
emit('switchModule', { type: privateType.value })
|
||||
}
|
||||
|
||||
function handleElevatorItem(key: string, item: AddressRegionData) {
|
||||
nextAreaList(item)
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(value) => {
|
||||
showPopup.value = value
|
||||
},
|
||||
)
|
||||
|
||||
watch(
|
||||
() => showPopup.value,
|
||||
(value) => {
|
||||
if (value)
|
||||
initCustomSelected()
|
||||
},
|
||||
)
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
const componentName = `${PREFIX}-address`
|
||||
const { translate } = useTranslate(componentName)
|
||||
|
||||
export default defineComponent({
|
||||
name: componentName,
|
||||
options: {
|
||||
virtualHost: true,
|
||||
addGlobalClass: true,
|
||||
styleIsolation: 'shared',
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NutPopup
|
||||
v-model:visible="showPopup"
|
||||
:z-index="zIndex"
|
||||
position="bottom"
|
||||
:lock-scroll="lockScroll"
|
||||
:round="round"
|
||||
@close="close"
|
||||
@click-overlay="clickOverlay"
|
||||
@open="closeWay = 'self'"
|
||||
>
|
||||
<view :class="classes" :style="customStyle">
|
||||
<view class="nut-address__header">
|
||||
<view class="nut-address__header-back" @click="switchModule">
|
||||
<slot v-if="type === 'exist' && privateType === 'custom'" name="backIcon">
|
||||
<NutIcon name="left" size="14px" />
|
||||
</slot>
|
||||
</view>
|
||||
|
||||
<view class="nut-address__header__title">
|
||||
{{
|
||||
privateType === 'custom'
|
||||
? customAddressTitle || translate('selectRegion')
|
||||
: existAddressTitle || translate('deliveryTo')
|
||||
}}
|
||||
</view>
|
||||
|
||||
<view class="nut-address__header-close" @click="handClose('cross')">
|
||||
<slot name="closeIcon">
|
||||
<NutIcon name="close" custom-color="#cccccc" size="14px" />
|
||||
</slot>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 请选择 -->
|
||||
<view v-if="['custom', 'custom2'].includes(privateType)" class="nut-address__custom">
|
||||
<view class="nut-address__region">
|
||||
<view
|
||||
v-for="(item, index) in selectedRegion"
|
||||
:key="index"
|
||||
class="nut-address__region-item "
|
||||
:class="[index === tabIndex ? 'active' : '']"
|
||||
@click="changeRegionTab(item, index)"
|
||||
>
|
||||
<view>{{ getTabName(item, index) }} </view>
|
||||
<view class="nut-address__region-line--mini" :class="{ active: index === tabIndex }" />
|
||||
</view>
|
||||
<view v-if="tabIndex === selectedRegion.length" class="active nut-address__region-item">
|
||||
<view>{{ getTabName(null, selectedRegion.length) }} </view>
|
||||
<view class="nut-address__region-line--mini active" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="privateType === 'custom'" class="nut-address__detail">
|
||||
<div class="nut-address__detail-list">
|
||||
<scroll-view
|
||||
:scroll-y="true"
|
||||
:style="{ height: '100%' }"
|
||||
:scroll-top="scrollTop"
|
||||
@scroll="scrollChange"
|
||||
>
|
||||
<div
|
||||
v-for="(item, index) in regionList"
|
||||
:key="index"
|
||||
class="nut-address__detail-item"
|
||||
:class="[selectedRegion[tabIndex]?.id === item.id ? 'active' : '']"
|
||||
@click="nextAreaList(item)"
|
||||
>
|
||||
<view>
|
||||
<slot v-if="selectedRegion[tabIndex]?.id === item.id" name="icon">
|
||||
<NutIcon name="Check" custom-class="nut-address-select-icon" width="13px" />
|
||||
</slot>{{ item.name }}
|
||||
</view>
|
||||
</div>
|
||||
</scroll-view>
|
||||
</div>
|
||||
</view>
|
||||
|
||||
<view v-else class="nut-address__elevator-group">
|
||||
<NutElevator
|
||||
:height="height"
|
||||
:index-list="transformData(regionList)"
|
||||
@click-item="handleElevatorItem"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 配送至 -->
|
||||
<view v-else-if="privateType === 'exist'" class="nut-address__exist">
|
||||
<div class="nut-address__exist-group">
|
||||
<ul class="nut-address__exist-group-list">
|
||||
<li
|
||||
v-for="(item, index) in existAddress"
|
||||
:key="index"
|
||||
class="nut-address__exist-group-item"
|
||||
:class="[item.selectedAddress ? 'active' : '']"
|
||||
@click="selectedExist(item)"
|
||||
>
|
||||
<slot v-if="!item.selectedAddress" name="unselectedIcon">
|
||||
<NutIcon name="location2" custom-class="nut-address-select-icon" width="13px" />
|
||||
</slot>
|
||||
|
||||
<slot v-if="item.selectedAddress" name="icon">
|
||||
<NutIcon name="Check" custom-class="nut-address-select-icon" width="13px" />
|
||||
</slot>
|
||||
|
||||
<div class="nut-address__exist-item-info">
|
||||
<div v-if="item.name && item.phone" class="nut-address__exist-item-info-top">
|
||||
<div class="nut-address__exist-item-info-name">
|
||||
{{ item.name }}
|
||||
</div>
|
||||
<div class="nut-address__exist-item-info-phone">
|
||||
{{ item.phone }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="nut-address__exist-item-info-bottom">
|
||||
<view>
|
||||
{{ item.provinceName + item.cityName + item.countyName + item.townName + item.addressDetail }}
|
||||
</view>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div v-if="isShowCustomAddress" class="nut-address__exist-choose" @click="switchModule">
|
||||
<div class="nut-address__exist-choose-btn">
|
||||
{{
|
||||
customAndExistTitle || translate('chooseAnotherAddress')
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
<template v-if="!isShowCustomAddress">
|
||||
<slot name="bottom" />
|
||||
</template>
|
||||
</view>
|
||||
</view>
|
||||
</NutPopup>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import './index';
|
||||
</style>
|
||||
232
uni_modules/nutui-uni/components/address/index.scss
Normal file
232
uni_modules/nutui-uni/components/address/index.scss
Normal file
@@ -0,0 +1,232 @@
|
||||
@import '../popup/index';
|
||||
@import '../elevator/index';
|
||||
|
||||
.nut-theme-dark {
|
||||
.nut-address {
|
||||
&__header {
|
||||
color: $dark-color;
|
||||
|
||||
&__title {
|
||||
color: $dark-color;
|
||||
}
|
||||
}
|
||||
|
||||
.nut-address__custom {
|
||||
.nut-address__region {
|
||||
color: $dark-color;
|
||||
}
|
||||
|
||||
.nut-address__detail {
|
||||
.nut-address__detail-list {
|
||||
.nut-address__detail-item {
|
||||
color: $dark-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nut-address__exist {
|
||||
.nut-address__exist-group {
|
||||
.nut-address__exist-group-list {
|
||||
.nut-address__exist-group-item {
|
||||
color: $dark-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nut-address__exist-choose {
|
||||
border-top: 1px solid $dark-background;
|
||||
}
|
||||
}
|
||||
|
||||
&-custom-buttom {
|
||||
border-top: 1px solid $dark-background;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nut-address {
|
||||
display: block;
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 68px;
|
||||
padding: 0 20px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
text-align: center;
|
||||
|
||||
&__title {
|
||||
display: block;
|
||||
font-size: $address-header-title-font-size;
|
||||
color: $address-header-title-color;
|
||||
}
|
||||
}
|
||||
|
||||
// 请选择
|
||||
.nut-address__custom {
|
||||
display: block;
|
||||
|
||||
.nut-address__region {
|
||||
position: relative;
|
||||
display: flex;
|
||||
|
||||
// margin-top: 32px;
|
||||
padding: 0 20px;
|
||||
font-size: $address-region-tab-font-size;
|
||||
color: $address-region-tab-color;
|
||||
|
||||
.nut-address__region-item {
|
||||
position: relative;
|
||||
display: block;
|
||||
min-width: 2px;
|
||||
margin-right: 30px;
|
||||
|
||||
&.active {
|
||||
font-weight: $address-region-tab-active-item-font-weight;
|
||||
}
|
||||
|
||||
view {
|
||||
display: block;
|
||||
max-width: 100px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.nut-address__region-line--mini {
|
||||
position: absolute;
|
||||
bottom: -10px;
|
||||
left: 0;
|
||||
display: inline-block;
|
||||
width: 0;
|
||||
height: 3px;
|
||||
margin-top: 5px;
|
||||
background: $address-region-tab-line;
|
||||
transition: 0.2s all linear;
|
||||
|
||||
&.active {
|
||||
width: 26px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nut-address__region-line {
|
||||
position: absolute;
|
||||
bottom: -10px;
|
||||
left: 20px;
|
||||
display: inline-block;
|
||||
width: 26px;
|
||||
height: 3px;
|
||||
margin-top: 5px;
|
||||
background: $address-region-tab-line;
|
||||
border-radius: $address-region-tab-line-border-radius;
|
||||
opacity: $address-region-tab-line-opacity;
|
||||
transition: 0.2s all linear;
|
||||
}
|
||||
}
|
||||
|
||||
.nut-address__detail {
|
||||
display: block;
|
||||
margin: 20px 20px 0;
|
||||
|
||||
.nut-address__detail-list {
|
||||
// overflow-y: auto;
|
||||
box-sizing: border-box;
|
||||
height: 270px;
|
||||
padding: 0;
|
||||
padding-top: 15px;
|
||||
|
||||
.nut-address__detail-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: $address-region-item-font-size;
|
||||
color: $address-region-item-color;
|
||||
|
||||
&.active {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
> view {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 10px 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nut-address__elevator-group {
|
||||
display: flex;
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
// 配送至
|
||||
.nut-address__exist {
|
||||
display: block;
|
||||
margin-top: 15px;
|
||||
|
||||
.nut-address__exist-group {
|
||||
height: 279px;
|
||||
padding: 15px 20px 0;
|
||||
overflow-y: scroll;
|
||||
|
||||
.nut-address__exist-group-list {
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
|
||||
.nut-address__exist-group-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
font-size: $font-size-1;
|
||||
line-height: 14px;
|
||||
color: #333;
|
||||
|
||||
&.active {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.exist-item-icon {
|
||||
margin-right: $address-item-margin-right;
|
||||
color: $address-icon-color !important;
|
||||
}
|
||||
|
||||
// span {
|
||||
// display: inline-block;
|
||||
// flex: 1;
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nut-address__exist-choose {
|
||||
width: 100%;
|
||||
height: 54px;
|
||||
padding: 6px 0 0;
|
||||
border-top: 1px solid #f2f2f2;
|
||||
|
||||
.nut-address__exist-choose-btn {
|
||||
width: 90%;
|
||||
height: 42px;
|
||||
margin: auto;
|
||||
font-size: 15px;
|
||||
line-height: 42px;
|
||||
color: $white;
|
||||
text-align: center;
|
||||
background: $button-primary-background-color;
|
||||
border-radius: 21px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-select-icon {
|
||||
margin-right: $address-item-margin-right;
|
||||
color: $address-icon-color !important;
|
||||
}
|
||||
}
|
||||
2
uni_modules/nutui-uni/components/address/index.ts
Normal file
2
uni_modules/nutui-uni/components/address/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './address'
|
||||
export * from './type'
|
||||
23
uni_modules/nutui-uni/components/address/type.ts
Normal file
23
uni_modules/nutui-uni/components/address/type.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
export interface AddressRegionData {
|
||||
name: string
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
export interface CustomRegionData {
|
||||
title: string
|
||||
list: any[]
|
||||
}
|
||||
|
||||
export interface AddressExistRegionData {
|
||||
id?: string | number
|
||||
provinceName: string
|
||||
cityName: string
|
||||
countyName: string
|
||||
townName: string
|
||||
addressDetail: string
|
||||
selectedAddress: boolean
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
export const addressType = ['exist', 'custom', 'custom2'] as const
|
||||
export type AddressType = (typeof addressType)[number]
|
||||
42
uni_modules/nutui-uni/components/addresslist/addresslist.ts
Normal file
42
uni_modules/nutui-uni/components/addresslist/addresslist.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import type { ExtractPropTypes } from 'vue'
|
||||
import { commonProps, isNumber, isString, makeArrayProp, makeObjectProp, truthProp } from '../_utils'
|
||||
import type { AddressListOptions } from './type'
|
||||
|
||||
export const addresslistProps = {
|
||||
...commonProps,
|
||||
/**
|
||||
* @description 地址数组
|
||||
*/
|
||||
data: makeArrayProp<any>([]),
|
||||
/**
|
||||
* @description 长按功能
|
||||
*/
|
||||
longPress: Boolean,
|
||||
/**
|
||||
* @description 右滑功能
|
||||
*/
|
||||
swipeEdition: Boolean,
|
||||
/**
|
||||
* @description 是否展示底部按钮
|
||||
*/
|
||||
showBottomButton: truthProp,
|
||||
/**
|
||||
* @description 自定义 `key` 值时,设置映射关系
|
||||
*/
|
||||
options: makeObjectProp<AddressListOptions>({}),
|
||||
}
|
||||
|
||||
export type AddressListProps = ExtractPropTypes<typeof addresslistProps>
|
||||
|
||||
export const addresslistEmits = {
|
||||
delIcon: (val: Event, item: unknown, index: number | string) => (val instanceof Object) && (item instanceof Object) && (isNumber(index) || isString(index)),
|
||||
editIcon: (val: Event, item: unknown, index: number | string) => (val instanceof Object) && (item instanceof Object) && (isNumber(index) || isString(index)),
|
||||
clickItem: (val: Event, item: unknown, index: number | string) => (val instanceof Object) && (item instanceof Object) && (isNumber(index) || isString(index)),
|
||||
longCopy: (val: Event, item: unknown, index: number | string) => (val instanceof Object) && (item instanceof Object) && (isNumber(index) || isString(index)),
|
||||
longSet: (val: Event, item: unknown, index: number | string) => (val instanceof Object) && (item instanceof Object) && (isNumber(index) || isString(index)),
|
||||
longDel: (val: Event, item: unknown, index: number | string) => (val instanceof Object) && (item instanceof Object) && (isNumber(index) || isString(index)),
|
||||
swipeDel: (val: Event, item: unknown, index: number | string) => (val instanceof Object) && (item instanceof Object) && (isNumber(index) || isString(index)),
|
||||
add: (val: Event) => val instanceof Object,
|
||||
}
|
||||
|
||||
export type AddressListEmits = typeof addresslistEmits
|
||||
165
uni_modules/nutui-uni/components/addresslist/addresslist.vue
Normal file
165
uni_modules/nutui-uni/components/addresslist/addresslist.vue
Normal file
@@ -0,0 +1,165 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, defineComponent, onMounted, reactive, ref, useSlots, watch } from 'vue'
|
||||
import { PREFIX } from '../_constants'
|
||||
import { floatData, getMainClass } from '../_utils'
|
||||
import { useTranslate } from '../../locale'
|
||||
import NutButton from '../button/button.vue'
|
||||
import { addresslistEmits, addresslistProps } from './addresslist'
|
||||
import GeneralShell from './compoents/generalshell.vue'
|
||||
|
||||
const props = defineProps(addresslistProps)
|
||||
|
||||
const emit = defineEmits(addresslistEmits)
|
||||
|
||||
const slots = useSlots()
|
||||
|
||||
function hasSlot(name: string) {
|
||||
return Boolean(slots[name])
|
||||
}
|
||||
|
||||
const dataArray = ref<any[]>([])
|
||||
const dataInfo = reactive({
|
||||
id: 2,
|
||||
addressName: '姓名',
|
||||
phone: '123****4567',
|
||||
defaultAddress: false,
|
||||
fullAddress: '北京市通州区测试测试测试测试测试测试测试测试测试',
|
||||
})
|
||||
|
||||
const classes = computed(() => {
|
||||
return getMainClass(props, componentName)
|
||||
})
|
||||
|
||||
// 磨平参数差异
|
||||
function trowelData() {
|
||||
if (Object.keys(props.options).length > 0) {
|
||||
dataArray.value = props.data.map((item) => {
|
||||
return floatData(dataInfo, item, props.options)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.data,
|
||||
() => trowelData(),
|
||||
{ deep: true },
|
||||
)
|
||||
|
||||
function handleDelIconClick(event: any, item: any, index: number | string) {
|
||||
event.stopPropagation()
|
||||
|
||||
emit('delIcon', event, item, index)
|
||||
}
|
||||
|
||||
function handleEditIconClick(event: any, item: any, index: number | string) {
|
||||
event.stopPropagation()
|
||||
|
||||
emit('editIcon', event, item, index)
|
||||
}
|
||||
|
||||
function handleContentItemClick(event: any, item: any, index: number | string) {
|
||||
event.stopPropagation()
|
||||
|
||||
emit('clickItem', event, item, index)
|
||||
}
|
||||
|
||||
function handleLongCopyClick(event: any, item: any, index: number | string) {
|
||||
event.stopPropagation()
|
||||
|
||||
emit('longCopy', event, item, index)
|
||||
}
|
||||
|
||||
function handleLongSetClick(event: any, item: any, index: number | string) {
|
||||
event.stopPropagation()
|
||||
|
||||
emit('longSet', event, item, index)
|
||||
}
|
||||
|
||||
function handleLongDelClick(event: any, item: any, index: number | string) {
|
||||
event.stopPropagation()
|
||||
|
||||
emit('longDel', event, item, index)
|
||||
}
|
||||
|
||||
function handleSwipeDelClick(event: any, item: any, index: number | string) {
|
||||
event.stopPropagation()
|
||||
|
||||
emit('swipeDel', event, item, index)
|
||||
}
|
||||
|
||||
function handleAddressAdd(event: any) {
|
||||
event.stopPropagation()
|
||||
|
||||
emit('add', event)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
trowelData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
const componentName = `${PREFIX}-address-list`
|
||||
const { translate } = useTranslate(componentName)
|
||||
|
||||
export default defineComponent({
|
||||
name: componentName,
|
||||
options: {
|
||||
virtualHost: true,
|
||||
addGlobalClass: true,
|
||||
// #ifndef H5
|
||||
styleIsolation: 'shared',
|
||||
// #endif
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view :class="classes" :style="customStyle">
|
||||
<GeneralShell
|
||||
v-for="(item, index) in dataArray"
|
||||
:key="index"
|
||||
:address="item"
|
||||
:long-press="props.longPress"
|
||||
:swipe-edition="props.swipeEdition"
|
||||
:use-content-info-slot="hasSlot('itemInfos')"
|
||||
:use-content-icons-slot="hasSlot('itemIcon')"
|
||||
:use-content-addrs-slot="hasSlot('itemAddr')"
|
||||
:use-longpress-all-slot="hasSlot('longpressBtns')"
|
||||
:use-swipe-right-btn-slot="hasSlot('swipeRight')"
|
||||
@del-icon="handleDelIconClick($event, item, index)"
|
||||
@edit-icon="handleEditIconClick($event, item, index)"
|
||||
@click-item="handleContentItemClick($event, item, index)"
|
||||
@swipe-del="handleSwipeDelClick($event, item, index)"
|
||||
@long-copy="handleLongCopyClick($event, item, index)"
|
||||
@long-set="handleLongSetClick($event, item, index)"
|
||||
@long-del="handleLongDelClick($event, item, index)"
|
||||
>
|
||||
<template #content-info>
|
||||
<slot name="itemInfos" :item="item" />
|
||||
</template>
|
||||
<template #content-icons>
|
||||
<slot name="itemIcon" :item="item" />
|
||||
</template>
|
||||
<template #content-addrs>
|
||||
<slot name="itemAddr" :item="item" />
|
||||
</template>
|
||||
<template v-if="props.longPress" #longpress-all>
|
||||
<slot name="longpressBtns" :item="item" />
|
||||
</template>
|
||||
<template v-if="props.swipeEdition" #swipe-right-btn>
|
||||
<slot name="swipeRight" :item="item" />
|
||||
</template>
|
||||
</GeneralShell>
|
||||
|
||||
<view v-if="props.showBottomButton" class="nut-address-list__bottom" @click="handleAddressAdd">
|
||||
<NutButton type="danger" block>
|
||||
{{ translate('addAddress') }}
|
||||
</NutButton>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import './index';
|
||||
</style>
|
||||
@@ -0,0 +1,154 @@
|
||||
<script lang="ts" setup>
|
||||
import { defineComponent } from 'vue'
|
||||
import { PREFIX } from '../../_constants'
|
||||
import { useTranslate } from '../../../locale'
|
||||
import NutIcon from '../../icon/icon.vue'
|
||||
|
||||
const props = defineProps({
|
||||
item: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
useContentTopSlot: Boolean,
|
||||
useContentIconSlot: Boolean,
|
||||
useContentAddrSlot: Boolean,
|
||||
})
|
||||
|
||||
const emit = defineEmits(['delIcon', 'editIcon', 'clickItem'])
|
||||
|
||||
function handleDelIconClick(event: any) {
|
||||
event.stopPropagation()
|
||||
|
||||
emit('delIcon', event, props.item)
|
||||
}
|
||||
|
||||
function handleEditIconClick(event: any) {
|
||||
event.stopPropagation()
|
||||
|
||||
emit('editIcon', event, props.item)
|
||||
}
|
||||
|
||||
function handleContentsClick(event: any) {
|
||||
event.stopPropagation()
|
||||
|
||||
emit('clickItem', event, props.item)
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
const componentName = `${PREFIX}-address-list-item`
|
||||
const { translate } = useTranslate(`${PREFIX}-address-list`)
|
||||
export default defineComponent({
|
||||
name: componentName,
|
||||
options: {
|
||||
virtualHost: true,
|
||||
addGlobalClass: true,
|
||||
// #ifndef H5
|
||||
styleIsolation: 'shared',
|
||||
// #endif
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="nut-address-list-item" @click="handleContentsClick">
|
||||
<view class="nut-address-list-item__info">
|
||||
<view class="nut-address-list-item__info-contact">
|
||||
<slot v-if="props.useContentTopSlot" name="content-top" />
|
||||
|
||||
<template v-else>
|
||||
<view class="nut-address-list-item__info-contact-name">
|
||||
{{ props.item.addressName }}
|
||||
</view>
|
||||
<view class="nut-address-list-item__info-contact-tel">
|
||||
{{ props.item.phone }}
|
||||
</view>
|
||||
<view v-if="props.item.defaultAddress" class="nut-address-list-item__info-contact-default">
|
||||
{{ translate('default') }}
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
|
||||
<view class="nut-address-list-item__info-handle">
|
||||
<slot v-if="props.useContentIconSlot" name="content-icon" />
|
||||
|
||||
<template v-else>
|
||||
<NutIcon name="del" custom-class="nut-address-list-item__info-handle-del" @tap.stop="handleDelIconClick" />
|
||||
<NutIcon name="edit" custom-class="nut-address-list-item__info-handle-edit" @tap.stop="handleEditIconClick" />
|
||||
</template>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="nut-address-list-item__addr">
|
||||
<slot v-if="props.useContentAddrSlot" name="content-addr" />
|
||||
|
||||
<template v-else>
|
||||
{{ props.item.fullAddress }}
|
||||
</template>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.nut-theme-dark {
|
||||
.nut-address-list {
|
||||
&-item {
|
||||
&__addr {
|
||||
color: $dark-color-gray;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nut-address-list {
|
||||
&-item {
|
||||
width: 100%;
|
||||
|
||||
&__info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
&-contact {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
font-weight: bold;
|
||||
|
||||
&-name {
|
||||
max-width: 145px;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
&-tel {
|
||||
max-width: 110px;
|
||||
margin-left: 8px;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
&-default {
|
||||
height: 16px;
|
||||
padding: 0 6px;
|
||||
margin-left: 5px;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: $addresslist-contnts-contact-color;
|
||||
background: $addresslist-contnts-contact-default;
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
&-handle {
|
||||
&-edit {
|
||||
margin-left: 15px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__addr {
|
||||
margin-top: 5px;
|
||||
font-size: $addresslist-addr-font-size;
|
||||
color: $addresslist-addr-font-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,335 @@
|
||||
<script lang="ts" setup>
|
||||
import { defineComponent, ref } from 'vue'
|
||||
import { PREFIX } from '../../_constants'
|
||||
import NutButton from '../../button/button.vue'
|
||||
import NutSwipe from '../../swipe/swipe.vue'
|
||||
import ItemContents from './Itemcontents.vue'
|
||||
|
||||
const props = defineProps({
|
||||
address: {
|
||||
type: Object,
|
||||
},
|
||||
longPress: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
swipeEdition: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
useContentInfoSlot: Boolean,
|
||||
useContentIconsSlot: Boolean,
|
||||
useContentAddrsSlot: Boolean,
|
||||
useLongpressAllSlot: Boolean,
|
||||
useSwipeRightBtnSlot: Boolean,
|
||||
})
|
||||
|
||||
const emit = defineEmits(['delIcon', 'editIcon', 'clickItem', 'longDown', 'longCopy', 'longSet', 'longDel', 'swipeDel'])
|
||||
|
||||
const moveRef = ref<boolean>(false)
|
||||
const showMaskRef = ref<boolean>(false)
|
||||
|
||||
function handleDelIconClick(event: any) {
|
||||
event.stopPropagation()
|
||||
|
||||
emit('delIcon', event, props.address)
|
||||
}
|
||||
|
||||
function handleEditIconClick(event: any) {
|
||||
event.stopPropagation()
|
||||
|
||||
emit('editIcon', event, props.address)
|
||||
}
|
||||
|
||||
function handleItemClick(event: any) {
|
||||
event.stopPropagation()
|
||||
|
||||
if (moveRef.value)
|
||||
return
|
||||
|
||||
emit('clickItem', event, props.address)
|
||||
}
|
||||
|
||||
function handleLongDelClick(event: any) {
|
||||
event.stopPropagation()
|
||||
|
||||
emit('longDel', event, props.address)
|
||||
}
|
||||
|
||||
let timer: NodeJS.Timeout | null = null
|
||||
|
||||
function destroyTimer() {
|
||||
if (timer == null)
|
||||
return
|
||||
|
||||
clearTimeout(timer)
|
||||
timer = null
|
||||
}
|
||||
|
||||
function startTimer(event: any) {
|
||||
timer = setTimeout(() => {
|
||||
showMaskRef.value = true
|
||||
|
||||
emit('longDown', event, props.address)
|
||||
}, 300)
|
||||
}
|
||||
|
||||
// 长按功能实现
|
||||
function handleTouchStart(event: any) {
|
||||
startTimer(event)
|
||||
}
|
||||
|
||||
function handleTouchMove() {
|
||||
// 滑动不触发长按
|
||||
destroyTimer()
|
||||
}
|
||||
|
||||
function handleTouchEnd() {
|
||||
// 删除定时器,防止重复注册
|
||||
destroyTimer()
|
||||
}
|
||||
|
||||
function handleHideMaskClick() {
|
||||
showMaskRef.value = false
|
||||
}
|
||||
|
||||
function handleLongCopyClick(event: any) {
|
||||
event.stopPropagation()
|
||||
|
||||
emit('longCopy', event, props.address)
|
||||
}
|
||||
|
||||
function handleLongSetClick(event: any) {
|
||||
event.stopPropagation()
|
||||
|
||||
emit('longSet', event, props.address)
|
||||
}
|
||||
|
||||
function handleMaskClick(event: any) {
|
||||
event.stopPropagation()
|
||||
event.preventDefault()
|
||||
|
||||
if (timer != null) {
|
||||
// 排除长按时触发点击的情况
|
||||
showMaskRef.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function handleSwipeDelClick(event: any) {
|
||||
event.stopPropagation()
|
||||
|
||||
emit('swipeDel', event, props.address)
|
||||
}
|
||||
|
||||
function handleSwipeStart() {
|
||||
moveRef.value = false
|
||||
}
|
||||
|
||||
function handleSwipeMove() {
|
||||
moveRef.value = true
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
const componentName = `${PREFIX}-address-list-general`
|
||||
|
||||
export default defineComponent({
|
||||
name: componentName,
|
||||
options: {
|
||||
virtualHost: true,
|
||||
addGlobalClass: true,
|
||||
// #ifndef H5
|
||||
styleIsolation: 'shared',
|
||||
// #endif
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view v-if="!props.swipeEdition" class="nut-address-list-general">
|
||||
<ItemContents
|
||||
:item="props.address"
|
||||
:use-content-top-slot="props.useContentInfoSlot"
|
||||
:use-content-icon-slot="props.useContentIconsSlot"
|
||||
:use-content-addr-slot="props.useContentAddrsSlot"
|
||||
@del-icon="handleDelIconClick"
|
||||
@edit-icon="handleEditIconClick"
|
||||
@click-item="handleItemClick"
|
||||
@touchstart="handleTouchStart"
|
||||
@touchmove="handleTouchMove"
|
||||
@touchend="handleTouchEnd"
|
||||
>
|
||||
<template #content-top>
|
||||
<slot name="content-info" />
|
||||
</template>
|
||||
<template #content-icon>
|
||||
<slot name="content-icons" />
|
||||
</template>
|
||||
<template #content-addr>
|
||||
<slot name="content-addrs" />
|
||||
</template>
|
||||
</ItemContents>
|
||||
|
||||
<view v-if="props.longPress && showMaskRef" class="nut-address-list-general__mask" @click="handleMaskClick">
|
||||
<slot v-if="props.useLongpressAllSlot" name="longpress-all" />
|
||||
|
||||
<template v-else>
|
||||
<view class="nut-address-list-general__mask-copy" @click="handleLongCopyClick">
|
||||
复制地址
|
||||
</view>
|
||||
<view class="nut-address-list-general__mask-set" @click="handleLongSetClick">
|
||||
设置默认
|
||||
</view>
|
||||
<view class="nut-address-list-general__mask-del" @click="handleLongDelClick">
|
||||
删除地址
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
|
||||
<view v-if="showMaskRef" class="nut-address-list__mask-bottom" @click="handleHideMaskClick" />
|
||||
</view>
|
||||
|
||||
<NutSwipe v-else>
|
||||
<view class="nut-address-list-swipe">
|
||||
<ItemContents
|
||||
:item="props.address"
|
||||
:use-content-top-slot="props.useContentInfoSlot"
|
||||
:use-content-icon-slot="props.useContentIconsSlot"
|
||||
:use-content-addr-slot="props.useContentAddrsSlot"
|
||||
@del-icon="handleDelIconClick"
|
||||
@edit-icon="handleEditIconClick"
|
||||
@click-item="handleItemClick"
|
||||
@touchstart="handleSwipeStart"
|
||||
@touchmove="handleSwipeMove"
|
||||
>
|
||||
<template #content-top>
|
||||
<slot name="content-info" />
|
||||
</template>
|
||||
<template #content-icon>
|
||||
<slot name="content-icons" />
|
||||
</template>
|
||||
<template #content-addr>
|
||||
<slot name="content-addrs" />
|
||||
</template>
|
||||
</ItemContents>
|
||||
</view>
|
||||
|
||||
<template #right>
|
||||
<view style="height: 100%;">
|
||||
<slot v-if="props.useSwipeRightBtnSlot" name="swipe-right-btn" />
|
||||
|
||||
<template v-else>
|
||||
<NutButton
|
||||
shape="square"
|
||||
custom-style="height: 100%;"
|
||||
type="danger"
|
||||
@tap.stop="handleSwipeDelClick"
|
||||
>
|
||||
删除
|
||||
</NutButton>
|
||||
</template>
|
||||
</view>
|
||||
</template>
|
||||
</NutSwipe>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.nut-theme-dark {
|
||||
.nut-address-list {
|
||||
&-swipe,
|
||||
&-general {
|
||||
color: $dark-color;
|
||||
background-color: $dark-background2;
|
||||
border-bottom: 1px solid $dark-color-gray;
|
||||
|
||||
&__mask {
|
||||
background-color: $dark-color3;
|
||||
|
||||
&-copy {
|
||||
color: $dark-color-gray;
|
||||
background-color: $dark-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nut-address-list {
|
||||
&-swipe,
|
||||
&-general {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: 76px;
|
||||
padding: 5px 10px;
|
||||
font-size: $addresslist-font-size;
|
||||
color: $addresslist-font-color;
|
||||
background-color: $addresslist-bg;
|
||||
border-bottom: 1px solid $addresslist-border;
|
||||
|
||||
&__mask {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 2001;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
padding: 0 40px;
|
||||
background-color: $addresslist-mask-bg;
|
||||
|
||||
&-copy,
|
||||
&-set,
|
||||
&-del {
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 55px;
|
||||
height: 55px;
|
||||
padding: 0 10px;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
background-color: $white;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
&-set {
|
||||
color: $white;
|
||||
background-color: $addresslist-set-bg;
|
||||
}
|
||||
|
||||
&-del {
|
||||
color: $white;
|
||||
background-color: $addresslist-del-bg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-general {
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
.nut-swipe {
|
||||
&:last-of-type {
|
||||
.nut-address-list-swipe {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nut-address-list__mask-bottom {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 2000;
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
42
uni_modules/nutui-uni/components/addresslist/index.scss
Normal file
42
uni_modules/nutui-uni/components/addresslist/index.scss
Normal file
@@ -0,0 +1,42 @@
|
||||
@import "../button/index";
|
||||
@import "../swipe/index";
|
||||
|
||||
.nut-theme-dark {
|
||||
.nut-address-list {
|
||||
&__bottom {
|
||||
background-color: $dark-background2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nut-address-list {
|
||||
overflow: hidden;
|
||||
|
||||
&:last-child {
|
||||
padding-bottom: 84px;
|
||||
}
|
||||
|
||||
&__bottom {
|
||||
position: fixed;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
bottom: constant(safe-area-inset-bottom);
|
||||
bottom: env(safe-area-inset-bottom);
|
||||
left: 0;
|
||||
z-index: 100000;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
padding: 12px 18px 24px;
|
||||
background-color: $addresslist-bg;
|
||||
}
|
||||
|
||||
.nut-address-list__mask-bottom {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 2000;
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
2
uni_modules/nutui-uni/components/addresslist/index.ts
Normal file
2
uni_modules/nutui-uni/components/addresslist/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './addresslist'
|
||||
export * from './type'
|
||||
3
uni_modules/nutui-uni/components/addresslist/type.ts
Normal file
3
uni_modules/nutui-uni/components/addresslist/type.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export interface AddressListOptions {
|
||||
[key: string]: string
|
||||
}
|
||||
44
uni_modules/nutui-uni/components/animate/animate.ts
Normal file
44
uni_modules/nutui-uni/components/animate/animate.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import type { ExtractPropTypes } from 'vue'
|
||||
import { CLICK_EVENT } from '../_constants'
|
||||
import { commonProps, makeNumericProp, makeStringProp } from '../_utils'
|
||||
import type { AnimateAction, AnimateType } from './type'
|
||||
|
||||
export const animateProps = {
|
||||
...commonProps,
|
||||
/**
|
||||
* @description 控制动画,当值从 false 变为 true 时会触发一次动画
|
||||
*/
|
||||
show: Boolean,
|
||||
|
||||
/**
|
||||
* @description 动画类型
|
||||
* @values 'fade', 'slide', 'zoom', ...
|
||||
*/
|
||||
type: makeStringProp<AnimateType | ''>(''),
|
||||
|
||||
/**
|
||||
* @description 是否循环执行。`true`-循环执行; `false`-执行一次
|
||||
*/
|
||||
loop: Boolean,
|
||||
|
||||
/**
|
||||
* @description 动画时长,单位 ms
|
||||
*/
|
||||
duration: makeNumericProp(500),
|
||||
|
||||
/**
|
||||
* @description (不能与 show 同时使用)触发方式,`initial`-初始化执行; `click`-点击执行
|
||||
* @values 'initial', 'click'
|
||||
* @default initial
|
||||
*/
|
||||
action: makeStringProp<AnimateAction>('initial'),
|
||||
}
|
||||
|
||||
export type AnimateProps = ExtractPropTypes<typeof animateProps>
|
||||
|
||||
export const animateEmits = {
|
||||
[CLICK_EVENT]: (evt: MouseEvent) => evt instanceof Object,
|
||||
animate: () => true,
|
||||
}
|
||||
|
||||
export type AnimateEmits = typeof animateEmits
|
||||
82
uni_modules/nutui-uni/components/animate/animate.vue
Normal file
82
uni_modules/nutui-uni/components/animate/animate.vue
Normal file
@@ -0,0 +1,82 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, defineComponent, ref, watch } from 'vue'
|
||||
import { CLICK_EVENT, PREFIX } from '../_constants'
|
||||
import { getMainClass, getMainStyle } from '../_utils'
|
||||
import requestAniFrame from '../_utils/raf'
|
||||
import { animateEmits, animateProps } from './animate'
|
||||
|
||||
const props = defineProps(animateProps)
|
||||
const emit = defineEmits(animateEmits)
|
||||
|
||||
const animated = ref(props.action === 'initial' || props.show === true || props.loop)
|
||||
const classes = computed(() => {
|
||||
const obj = {
|
||||
[`${componentName}__container`]: true,
|
||||
[`${componentName}-${props.type}`]: animated.value,
|
||||
loop: props.loop,
|
||||
}
|
||||
return getMainClass(props, componentName, obj)
|
||||
})
|
||||
const getStyle = computed(() => {
|
||||
return getMainStyle(props, {
|
||||
animationDuration: props.duration ? `${props.duration}ms` : undefined,
|
||||
})
|
||||
})
|
||||
|
||||
function animate() {
|
||||
animated.value = false
|
||||
// #ifdef H5
|
||||
requestAniFrame(() => {
|
||||
requestAniFrame(() => {
|
||||
animated.value = true
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function handleClick(event: unknown) {
|
||||
if (props.action === 'click') {
|
||||
animate()
|
||||
emit(CLICK_EVENT, event as MouseEvent)
|
||||
emit('animate')
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.show,
|
||||
(val) => {
|
||||
if (val) {
|
||||
animate()
|
||||
emit('animate')
|
||||
}
|
||||
},
|
||||
)
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
const componentName = `${PREFIX}-animate`
|
||||
|
||||
export default defineComponent({
|
||||
name: componentName,
|
||||
options: {
|
||||
virtualHost: true,
|
||||
addGlobalClass: true,
|
||||
styleIsolation: 'shared',
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="nut-animate">
|
||||
<view
|
||||
:class="classes"
|
||||
:style="getStyle"
|
||||
@click="handleClick"
|
||||
>
|
||||
<slot />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import './index';
|
||||
</style>
|
||||
316
uni_modules/nutui-uni/components/animate/index.scss
Normal file
316
uni_modules/nutui-uni/components/animate/index.scss
Normal file
@@ -0,0 +1,316 @@
|
||||
.nut-animate {
|
||||
.nut-animate__container {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* Animation css */
|
||||
[class*="nut-animate-"] {
|
||||
animation-duration: 0.5s;
|
||||
animation-timing-function: ease-out;
|
||||
animation-fill-mode: both;
|
||||
}
|
||||
|
||||
// 抖动
|
||||
.nut-animate-shake {
|
||||
animation-name: shake;
|
||||
}
|
||||
|
||||
// 心跳
|
||||
.nut-animate-ripple {
|
||||
animation-name: ripple;
|
||||
}
|
||||
|
||||
// 漂浮
|
||||
.nut-animate-float {
|
||||
position: relative;
|
||||
animation-name: float-pop;
|
||||
}
|
||||
|
||||
// 呼吸灯
|
||||
.nut-animate-breath {
|
||||
animation-name: breath;
|
||||
animation-duration: 2700ms;
|
||||
animation-timing-function: ease-in-out;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
|
||||
// 右侧向左侧划入
|
||||
.nut-animate-slide-right {
|
||||
animation-name: slide-right;
|
||||
}
|
||||
|
||||
// 右侧向左侧划入
|
||||
.nut-animate-slide-left {
|
||||
animation-name: slide-left;
|
||||
}
|
||||
|
||||
// 上面向下面划入
|
||||
.nut-animate-slide-top {
|
||||
animation-name: slide-top;
|
||||
}
|
||||
|
||||
// 下面向上面划入
|
||||
.nut-animate-slide-bottom {
|
||||
animation-name: slide-bottom;
|
||||
}
|
||||
|
||||
.nut-animate-jump {
|
||||
transform-origin: center center;
|
||||
animation: jump 0.7s linear;
|
||||
}
|
||||
|
||||
// 循环
|
||||
.loop {
|
||||
animation-iteration-count: infinite;
|
||||
}
|
||||
|
||||
// 抖动动画
|
||||
@keyframes shake {
|
||||
0%,
|
||||
100% {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
10% {
|
||||
transform: translateX(-9px);
|
||||
}
|
||||
|
||||
20% {
|
||||
transform: translateX(8px);
|
||||
}
|
||||
|
||||
30% {
|
||||
transform: translateX(-7px);
|
||||
}
|
||||
|
||||
40% {
|
||||
transform: translateX(6px);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: translateX(-5px);
|
||||
}
|
||||
|
||||
60% {
|
||||
transform: translateX(4px);
|
||||
}
|
||||
|
||||
70% {
|
||||
transform: translateX(-3px);
|
||||
}
|
||||
|
||||
80% {
|
||||
transform: translateX(2px);
|
||||
}
|
||||
|
||||
90% {
|
||||
transform: translateX(-1px);
|
||||
}
|
||||
}
|
||||
|
||||
// 心跳
|
||||
@keyframes ripple {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
||||
|
||||
// 呼吸
|
||||
@keyframes breath {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
// 右侧向左侧划入
|
||||
// stylelint-disable-next-line keyframes-name-pattern
|
||||
@keyframes slide-right {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateX(100%);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
// 左侧向右侧划入
|
||||
// stylelint-disable-next-line keyframes-name-pattern
|
||||
@keyframes slide-left {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
// 上面向下面划入
|
||||
// stylelint-disable-next-line keyframes-name-pattern
|
||||
@keyframes slide-top {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(-100%);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
// 下面向上面划入
|
||||
// stylelint-disable-next-line keyframes-name-pattern
|
||||
@keyframes slide-bottom {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(100%);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
// 漂浮 float
|
||||
// stylelint-disable-next-line keyframes-name-pattern
|
||||
@keyframes float-pop {
|
||||
0% {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
25% {
|
||||
top: 1px;
|
||||
}
|
||||
|
||||
50% {
|
||||
top: 4px;
|
||||
}
|
||||
|
||||
75% {
|
||||
top: 1px;
|
||||
}
|
||||
|
||||
100% {
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 跳跃
|
||||
@keyframes jump {
|
||||
0% {
|
||||
transform: rotate(0deg) translateY(0);
|
||||
animation-timing-function: ease-in;
|
||||
}
|
||||
|
||||
25% {
|
||||
transform: rotate(10deg) translateY(20 * 1px);
|
||||
animation-timing-function: ease-out;
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: rotate(0deg) translateY(-10 * 1px);
|
||||
animation-timing-function: ease-in;
|
||||
}
|
||||
|
||||
75% {
|
||||
transform: rotate(-10deg) translateY(20 * 1px);
|
||||
animation-timing-function: ease-out;
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(0deg) translateY(0);
|
||||
animation-timing-function: ease-in;
|
||||
}
|
||||
}
|
||||
|
||||
.nut-animate-twinkle {
|
||||
position: relative;
|
||||
|
||||
&::after,
|
||||
&::before {
|
||||
position: absolute;
|
||||
right: 50%;
|
||||
z-index: 1;
|
||||
box-sizing: border-box;
|
||||
width: 60 * 1px;
|
||||
height: 60 * 1px;
|
||||
margin-top: calc(-30 / 2) * 1px;
|
||||
margin-right: calc(-60 / 2) * 1px;
|
||||
content: "";
|
||||
border: 4 * 1px solid rgb(255 255 255 / 60%);
|
||||
border-radius: calc(60 / 2) * 1px;
|
||||
transform: scale(0);
|
||||
animation: twinkle 2s ease-out infinite;
|
||||
}
|
||||
|
||||
&::after {
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
}
|
||||
|
||||
// 水波
|
||||
@keyframes twinkle {
|
||||
0% {
|
||||
transform: scale(0);
|
||||
}
|
||||
|
||||
20% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
50%,
|
||||
100% {
|
||||
opacity: 0;
|
||||
transform: scale(1.4);
|
||||
}
|
||||
}
|
||||
|
||||
.nut-animate-flicker {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
&::after {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100 * 1px;
|
||||
height: 60 * 1px;
|
||||
content: "";
|
||||
background-image: linear-gradient(106deg, rgb(232 224 255 / 0%) 24%, #e8e0ff 91%);
|
||||
filter: blur(3 * 1px);
|
||||
opacity: 0.73;
|
||||
transform: skewX(-20deg);
|
||||
animation: flicker 1.5s linear infinite;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes flicker {
|
||||
0% {
|
||||
transform: translateX(-100 * 1px) skewX(-20deg);
|
||||
}
|
||||
|
||||
40%,
|
||||
100% {
|
||||
transform: translateX(150 * 1px) skewX(-20deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
1
uni_modules/nutui-uni/components/animate/index.ts
Normal file
1
uni_modules/nutui-uni/components/animate/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './animate'
|
||||
4
uni_modules/nutui-uni/components/animate/type.ts
Normal file
4
uni_modules/nutui-uni/components/animate/type.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export const animateType = ['shake', 'ripple', 'breath', 'float', 'slide-right', 'slide-left', 'slide-top', 'slide-bottom', 'jump', 'twinkle', 'flicker'] as const
|
||||
export type AnimateType = (typeof animateType)[number]
|
||||
export const animateAction = ['initial', 'click', ''] as const
|
||||
export type AnimateAction = (typeof animateAction)[number]
|
||||
25
uni_modules/nutui-uni/components/avatar/avatar.ts
Normal file
25
uni_modules/nutui-uni/components/avatar/avatar.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import type { ExtractPropTypes } from 'vue'
|
||||
import { commonProps, makeNumericProp, makeStringProp } from '../_utils'
|
||||
import type { AvatarShape, AvatarSize } from './type'
|
||||
|
||||
export const avatarProps = {
|
||||
...commonProps,
|
||||
/**
|
||||
* @description 头像的大小,可选值为:`large`、`normal`、`small`,支持直接输入数字
|
||||
*/
|
||||
size: makeNumericProp<AvatarSize | string | number | undefined>(undefined),
|
||||
/**
|
||||
* @description 头像的形状,可选值为:`square`、`round`
|
||||
*/
|
||||
shape: makeStringProp<AvatarShape | undefined>(undefined),
|
||||
/**
|
||||
* @description 背景色
|
||||
*/
|
||||
bgColor: makeStringProp('#eee'),
|
||||
/**
|
||||
* @description 字体颜色
|
||||
*/
|
||||
customColor: makeStringProp('#666'),
|
||||
}
|
||||
|
||||
export type AvatarProps = ExtractPropTypes<typeof avatarProps>
|
||||
139
uni_modules/nutui-uni/components/avatar/avatar.vue
Normal file
139
uni_modules/nutui-uni/components/avatar/avatar.vue
Normal file
@@ -0,0 +1,139 @@
|
||||
<script setup lang="ts">
|
||||
import type { CSSProperties } from 'vue'
|
||||
import { computed, defineComponent, getCurrentInstance, ref, watch } from 'vue'
|
||||
import { PREFIX } from '../_constants'
|
||||
import { useInject } from '../_hooks'
|
||||
import { getMainClass, getMainStyle, pxCheck } from '../_utils'
|
||||
import type { AvatarGroupProps } from '../avatargroup'
|
||||
import { AVATAR_GROUP_KEY } from '../avatargroup'
|
||||
import { avatarProps } from './avatar'
|
||||
import type { AvatarFinalSize, AvatarShape, AvatarSize } from './type'
|
||||
import { avatarSize } from './type'
|
||||
|
||||
const props = defineProps(avatarProps)
|
||||
|
||||
const instance = getCurrentInstance()
|
||||
|
||||
const { parent } = useInject<{ props: Required<AvatarGroupProps> }>(AVATAR_GROUP_KEY)
|
||||
|
||||
const show = ref(true)
|
||||
|
||||
const innerZIndex = ref<number | undefined>(undefined)
|
||||
|
||||
watch(() => ({
|
||||
maxCount: parent?.props.maxCount,
|
||||
children: parent?.internalChildren,
|
||||
}), ({ maxCount, children }) => {
|
||||
if (maxCount == null || Number(maxCount) <= 0 || children == null || instance == null) {
|
||||
show.value = true
|
||||
innerZIndex.value = undefined
|
||||
return
|
||||
}
|
||||
|
||||
const index = children.findIndex((item) => {
|
||||
return item.uid === instance.uid && !(item.props.customClass as string)?.includes('avatar-fold')
|
||||
})
|
||||
|
||||
if (index < 0) {
|
||||
show.value = true
|
||||
innerZIndex.value = undefined
|
||||
return
|
||||
}
|
||||
|
||||
show.value = index < Number(maxCount)
|
||||
|
||||
if (parent?.props.zIndex === 'right')
|
||||
innerZIndex.value = children.length - index
|
||||
else
|
||||
innerZIndex.value = undefined
|
||||
}, {
|
||||
immediate: true,
|
||||
deep: true,
|
||||
})
|
||||
|
||||
function getTrulySize() {
|
||||
if (props.size != null)
|
||||
return props.size
|
||||
|
||||
if (parent != null && parent.props.size != null)
|
||||
return parent.props.size
|
||||
|
||||
return 'normal'
|
||||
}
|
||||
|
||||
const finalSize = computed<AvatarFinalSize>(() => {
|
||||
const size: string | number = getTrulySize()
|
||||
|
||||
const preset: boolean = avatarSize.includes(size as AvatarSize)
|
||||
|
||||
return {
|
||||
preset,
|
||||
value: preset ? (size as AvatarSize) : pxCheck(size),
|
||||
}
|
||||
})
|
||||
|
||||
const finalShape = computed<AvatarShape>(() => {
|
||||
if (props.shape != null)
|
||||
return props.shape
|
||||
|
||||
if (parent != null && parent.props.shape != null)
|
||||
return parent.props.shape
|
||||
|
||||
return 'round'
|
||||
})
|
||||
|
||||
const classes = computed(() => {
|
||||
const value: Record<string, boolean> = {
|
||||
[`nut-avatar-${finalShape.value}`]: true,
|
||||
'nut-hidden': !show.value,
|
||||
}
|
||||
|
||||
if (finalSize.value.preset)
|
||||
value[`nut-avatar-${finalSize.value.value}`] = true
|
||||
|
||||
return getMainClass(props, componentName, value)
|
||||
})
|
||||
|
||||
const styles = computed(() => {
|
||||
const value: CSSProperties = {
|
||||
backgroundColor: props.bgColor,
|
||||
color: props.customColor,
|
||||
}
|
||||
|
||||
if (!finalSize.value.preset) {
|
||||
value.width = finalSize.value.value
|
||||
value.height = finalSize.value.value
|
||||
}
|
||||
|
||||
if (parent?.props.span)
|
||||
value.marginLeft = pxCheck(parent?.props.span)
|
||||
|
||||
if (innerZIndex.value !== undefined)
|
||||
value.zIndex = innerZIndex.value
|
||||
|
||||
return getMainStyle(props, value)
|
||||
})
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
const componentName = `${PREFIX}-avatar`
|
||||
|
||||
export default defineComponent({
|
||||
name: componentName,
|
||||
options: {
|
||||
virtualHost: true,
|
||||
addGlobalClass: true,
|
||||
styleIsolation: 'shared',
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view :style="styles" :class="classes">
|
||||
<slot />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import './index';
|
||||
</style>
|
||||
55
uni_modules/nutui-uni/components/avatar/index.scss
Normal file
55
uni_modules/nutui-uni/components/avatar/index.scss
Normal file
@@ -0,0 +1,55 @@
|
||||
.nut-avatar {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
flex: 0 0 auto; // 防止被压缩
|
||||
text-align: center;
|
||||
vertical-align: top;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
background-size: 100% 100%;
|
||||
|
||||
image {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.nut-icon {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
background-size: 100% 100%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
}
|
||||
|
||||
.nut-avatar-large {
|
||||
width: $avatar-large-width;
|
||||
height: $avatar-large-height;
|
||||
line-height: $avatar-large-height;
|
||||
}
|
||||
|
||||
.nut-avatar-normal {
|
||||
width: $avatar-normal-width;
|
||||
height: $avatar-normal-height;
|
||||
line-height: $avatar-normal-height;
|
||||
}
|
||||
|
||||
.nut-avatar-small {
|
||||
width: $avatar-small-width;
|
||||
height: $avatar-small-height;
|
||||
line-height: $avatar-small-height;
|
||||
}
|
||||
|
||||
.nut-avatar-square {
|
||||
border-radius: $avatar-square;
|
||||
}
|
||||
|
||||
.nut-avatar-round {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.nut-avatar-square,
|
||||
.nut-avatar-round {
|
||||
overflow: hidden;
|
||||
}
|
||||
2
uni_modules/nutui-uni/components/avatar/index.ts
Normal file
2
uni_modules/nutui-uni/components/avatar/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './avatar'
|
||||
export * from './type'
|
||||
16
uni_modules/nutui-uni/components/avatar/type.ts
Normal file
16
uni_modules/nutui-uni/components/avatar/type.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
export const avatarSize = ['large', 'normal', 'small'] as const
|
||||
export type AvatarSize = (typeof avatarSize)[number]
|
||||
|
||||
export const avatarShape = ['round', 'square'] as const
|
||||
export type AvatarShape = (typeof avatarShape)[number]
|
||||
|
||||
export interface AvatarFinalSize {
|
||||
/**
|
||||
* 是否为预设尺寸
|
||||
*/
|
||||
preset: boolean
|
||||
/**
|
||||
* 尺寸值
|
||||
*/
|
||||
value: string
|
||||
}
|
||||
50
uni_modules/nutui-uni/components/avatargroup/avatargroup.ts
Normal file
50
uni_modules/nutui-uni/components/avatargroup/avatargroup.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import type { ExtractPropTypes } from 'vue'
|
||||
import { commonProps, makeNumericProp, makeStringProp } from '../_utils'
|
||||
import type { AvatarShape, AvatarSize } from '../avatar'
|
||||
|
||||
export const AVATAR_GROUP_KEY = Symbol('avatarGroup')
|
||||
|
||||
export const avatargroupProps = {
|
||||
...commonProps,
|
||||
/**
|
||||
* @description 显示的最大头像个数
|
||||
*/
|
||||
maxCount: makeNumericProp(-1),
|
||||
|
||||
/**
|
||||
* @description 头像数量超出时,会出现一个头像折叠元素,该元素内容可为`...`、`more`、`+N`
|
||||
*/
|
||||
maxContent: makeStringProp(''),
|
||||
|
||||
/**
|
||||
* @description 头像的大小,可选值为:`large`、`normal`、`small`,支持直接输入数字
|
||||
*/
|
||||
size: makeNumericProp<AvatarSize | string | number>('normal'),
|
||||
|
||||
/**
|
||||
* @description 头像的形状,可选值为:`square`、`round`
|
||||
*/
|
||||
shape: makeStringProp<AvatarShape>('round'),
|
||||
|
||||
/**
|
||||
* @description 头像折叠元素的字体颜色
|
||||
*/
|
||||
maxColor: makeStringProp('#666'),
|
||||
|
||||
/**
|
||||
* @description 头像折叠元素的背景色
|
||||
*/
|
||||
maxBgColor: makeStringProp('#eee'),
|
||||
|
||||
/**
|
||||
* @description 头像之间的间距
|
||||
*/
|
||||
span: makeNumericProp('-8'),
|
||||
|
||||
/**
|
||||
* @description 组合头像之间的层级方向,可选值为:`left`、`right`
|
||||
*/
|
||||
zIndex: makeStringProp<'left' | 'right'>('left'),
|
||||
}
|
||||
|
||||
export type AvatarGroupProps = ExtractPropTypes<typeof avatargroupProps>
|
||||
82
uni_modules/nutui-uni/components/avatargroup/avatargroup.vue
Normal file
82
uni_modules/nutui-uni/components/avatargroup/avatargroup.vue
Normal file
@@ -0,0 +1,82 @@
|
||||
<script setup lang="ts">
|
||||
import type { CSSProperties } from 'vue'
|
||||
import { computed, defineComponent, ref, watch } from 'vue'
|
||||
import { PREFIX } from '../_constants'
|
||||
import { useProvide } from '../_hooks'
|
||||
import { getMainClass, getMainStyle, pxCheck } from '../_utils'
|
||||
import NutAvatar from '../avatar/avatar.vue'
|
||||
import { AVATAR_GROUP_KEY, avatargroupProps } from './avatargroup'
|
||||
|
||||
const props = defineProps(avatargroupProps)
|
||||
|
||||
const { internalChildren } = useProvide(AVATAR_GROUP_KEY, `${PREFIX}-avatar`)({ props })
|
||||
|
||||
const innerMaxCount = computed<number>(() => {
|
||||
return Number(props.maxCount)
|
||||
})
|
||||
|
||||
const foldCount = ref(0)
|
||||
|
||||
watch(() => ({
|
||||
maxCount: props.maxCount,
|
||||
children: internalChildren,
|
||||
}), ({ children }) => {
|
||||
if (innerMaxCount.value > 0)
|
||||
foldCount.value = Math.min(99, children.length - innerMaxCount.value)
|
||||
else
|
||||
foldCount.value = 0
|
||||
}, {
|
||||
immediate: true,
|
||||
deep: true,
|
||||
})
|
||||
|
||||
const classes = computed(() => {
|
||||
return getMainClass(props, componentName)
|
||||
})
|
||||
|
||||
const styles = computed(() => {
|
||||
return getMainStyle(props, {
|
||||
marginLeft: `calc(0px - ${pxCheck(props.span)})`,
|
||||
})
|
||||
})
|
||||
|
||||
const foldStyles = computed<CSSProperties>(() => {
|
||||
return {
|
||||
marginLeft: pxCheck(props.span),
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
const componentName = `${PREFIX}-avatar-group`
|
||||
|
||||
export default defineComponent({
|
||||
name: componentName,
|
||||
options: {
|
||||
virtualHost: true,
|
||||
addGlobalClass: true,
|
||||
styleIsolation: 'shared',
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view :class="classes" :style="styles">
|
||||
<slot />
|
||||
<NutAvatar
|
||||
v-if="foldCount > 0"
|
||||
custom-class="avatar-fold"
|
||||
:custom-style="foldStyles"
|
||||
:size="props.size"
|
||||
:shape="props.shape"
|
||||
:bg-color="props.maxBgColor"
|
||||
:custom-color="props.maxColor"
|
||||
>
|
||||
{{ props.maxContent || foldCount }}
|
||||
</NutAvatar>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import './index';
|
||||
</style>
|
||||
12
uni_modules/nutui-uni/components/avatargroup/index.scss
Normal file
12
uni_modules/nutui-uni/components/avatargroup/index.scss
Normal file
@@ -0,0 +1,12 @@
|
||||
.nut-avatar-group {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex: 0 0 auto; // 防止被压缩
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
background-size: 100% 100%;
|
||||
|
||||
.nut-avatar {
|
||||
border: 1px solid #fff;
|
||||
}
|
||||
}
|
||||
1
uni_modules/nutui-uni/components/avatargroup/index.ts
Normal file
1
uni_modules/nutui-uni/components/avatargroup/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './avatargroup'
|
||||
44
uni_modules/nutui-uni/components/backtop/backtop.ts
Normal file
44
uni_modules/nutui-uni/components/backtop/backtop.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import type { ExtractPropTypes } from 'vue'
|
||||
import { CLICK_EVENT } from '../_constants'
|
||||
import { commonProps, makeNumberProp, makeStringProp } from '../_utils'
|
||||
|
||||
export const backtopProps = {
|
||||
...commonProps,
|
||||
/**
|
||||
* @description 滚动区域的高度
|
||||
*/
|
||||
height: makeStringProp('100vh'),
|
||||
|
||||
/**
|
||||
* @description 距离页面底部距离
|
||||
*/
|
||||
bottom: makeNumberProp(20),
|
||||
|
||||
/**
|
||||
* @description 距离页面右侧距离
|
||||
*/
|
||||
right: makeNumberProp(10),
|
||||
|
||||
/**
|
||||
* @description 页面垂直滚动多高后出现
|
||||
*/
|
||||
distance: makeNumberProp(200),
|
||||
|
||||
/**
|
||||
* @description 设置组件页面层级
|
||||
*/
|
||||
zIndex: makeNumberProp(10),
|
||||
|
||||
/**
|
||||
* @description 自定义图标颜色
|
||||
*/
|
||||
customColor: String,
|
||||
}
|
||||
|
||||
export type BacktopProps = ExtractPropTypes<typeof backtopProps>
|
||||
|
||||
export const backtopEmits = {
|
||||
[CLICK_EVENT]: (evt: MouseEvent) => evt instanceof Object,
|
||||
}
|
||||
|
||||
export type BacktopEmits = typeof backtopEmits
|
||||
78
uni_modules/nutui-uni/components/backtop/backtop.vue
Normal file
78
uni_modules/nutui-uni/components/backtop/backtop.vue
Normal file
@@ -0,0 +1,78 @@
|
||||
<script setup lang="ts">
|
||||
import type { ScrollViewOnScrollEvent } from '@uni-helper/uni-app-types'
|
||||
import { computed, defineComponent, ref } from 'vue'
|
||||
import { CLICK_EVENT, PREFIX } from '../_constants'
|
||||
import { getMainClass, getMainStyle } from '../_utils'
|
||||
import NutIcon from '../icon/icon.vue'
|
||||
import { backtopEmits, backtopProps } from './backtop'
|
||||
|
||||
const props = defineProps(backtopProps)
|
||||
|
||||
const emit = defineEmits(backtopEmits)
|
||||
|
||||
const backTop = ref(false)
|
||||
const scrollTop = ref(1)
|
||||
const classes = computed(() => {
|
||||
return getMainClass(props, componentName, {
|
||||
show: backTop.value,
|
||||
})
|
||||
})
|
||||
const style = computed(() => {
|
||||
return getMainStyle(props, {
|
||||
right: `${props.right}px`,
|
||||
bottom: `${props.bottom}px`,
|
||||
zIndex: props.zIndex,
|
||||
})
|
||||
})
|
||||
|
||||
function scroll(e: ScrollViewOnScrollEvent) {
|
||||
scrollTop.value = 2
|
||||
backTop.value = e.detail.scrollTop >= props.distance
|
||||
}
|
||||
|
||||
function click(e: unknown) {
|
||||
scrollTop.value = 1
|
||||
emit(CLICK_EVENT, e as MouseEvent)
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
const componentName = `${PREFIX}-backtop`
|
||||
|
||||
export default defineComponent({
|
||||
name: componentName,
|
||||
options: {
|
||||
virtualHost: true,
|
||||
addGlobalClass: true,
|
||||
styleIsolation: 'shared',
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view>
|
||||
<scroll-view
|
||||
:scroll-y="true"
|
||||
:style="{ height }"
|
||||
:scroll-top="scrollTop"
|
||||
:scroll-with-animation="true"
|
||||
@scroll="scroll"
|
||||
>
|
||||
<slot name="content" />
|
||||
</scroll-view>
|
||||
<view :class="classes" :style="style" @click.stop="click">
|
||||
<slot name="icon">
|
||||
<NutIcon
|
||||
:custom-color="customColor"
|
||||
name="top"
|
||||
:size="19"
|
||||
custom-class="nut-backtop-main"
|
||||
/>
|
||||
</slot>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import './index';
|
||||
</style>
|
||||
40
uni_modules/nutui-uni/components/backtop/index.scss
Normal file
40
uni_modules/nutui-uni/components/backtop/index.scss
Normal file
@@ -0,0 +1,40 @@
|
||||
.nut-theme-dark {
|
||||
.nut-backtop {
|
||||
&.show {
|
||||
color: $dark-color;
|
||||
background: $dark-background;
|
||||
border: 1px solid $dark-background;
|
||||
}
|
||||
|
||||
&-main {
|
||||
color:'#ffffff';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nut-backtop {
|
||||
position: fixed;
|
||||
display: none;
|
||||
|
||||
&.show {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background: $white;
|
||||
border: 1px solid $backtop-border-color;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
&.show :active {
|
||||
background: rgb(0 0 0 / 10%);
|
||||
}
|
||||
|
||||
&-main {
|
||||
color:'#000000';
|
||||
transition: all 0.2s ease-in-out
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
1
uni_modules/nutui-uni/components/backtop/index.ts
Normal file
1
uni_modules/nutui-uni/components/backtop/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export type * from './backtop'
|
||||
52
uni_modules/nutui-uni/components/badge/badge.ts
Normal file
52
uni_modules/nutui-uni/components/badge/badge.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import type { ExtractPropTypes } from 'vue'
|
||||
import { commonProps, makeNumberProp, makeStringProp } from '../_utils'
|
||||
|
||||
export const badgeProps = {
|
||||
...commonProps,
|
||||
/**
|
||||
* @description 显示的内容
|
||||
*/
|
||||
value: [String, Number],
|
||||
/**
|
||||
* @description `value` 为数值时,最大值
|
||||
*/
|
||||
max: makeNumberProp(10000),
|
||||
|
||||
/**
|
||||
* @description 是否为小点
|
||||
*/
|
||||
dot: Boolean,
|
||||
|
||||
/**
|
||||
* @description 是否为气泡形状
|
||||
* @since >v4.0.0
|
||||
*/
|
||||
bubble: Boolean,
|
||||
|
||||
/**
|
||||
* @description 是否隐藏
|
||||
*/
|
||||
hidden: Boolean,
|
||||
|
||||
/**
|
||||
* @description 上下偏移量,支持单位设置,可设置为:`5px` 等
|
||||
*/
|
||||
top: makeStringProp('0'),
|
||||
|
||||
/**
|
||||
* @description 左右偏移量,支持单位设置,可设置为:`5px` 等
|
||||
*/
|
||||
right: makeStringProp('0'),
|
||||
|
||||
/**
|
||||
* @description 徽标的 `z-index` 值
|
||||
*/
|
||||
zIndex: makeNumberProp(10),
|
||||
|
||||
/**
|
||||
* @description 徽标背景颜色
|
||||
*/
|
||||
customColor: makeStringProp(''),
|
||||
}
|
||||
|
||||
export type BadgeProps = ExtractPropTypes<typeof badgeProps>
|
||||
65
uni_modules/nutui-uni/components/badge/badge.vue
Normal file
65
uni_modules/nutui-uni/components/badge/badge.vue
Normal file
@@ -0,0 +1,65 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, defineComponent } from 'vue'
|
||||
import { PREFIX } from '../_constants'
|
||||
import { getMainClass, getMainStyle, pxCheck } from '../_utils'
|
||||
import { badgeProps } from './badge'
|
||||
|
||||
const props = defineProps(badgeProps)
|
||||
|
||||
const getStyle = computed(() => {
|
||||
return getMainStyle(props, {
|
||||
top: pxCheck(props.top),
|
||||
right: pxCheck(props.right),
|
||||
zIndex: props.zIndex,
|
||||
background: props.customColor,
|
||||
})
|
||||
})
|
||||
const classes = computed(() => {
|
||||
return getMainClass(props, componentName)
|
||||
})
|
||||
|
||||
const content = computed(() => {
|
||||
if (props.dot)
|
||||
return
|
||||
const value = props.value
|
||||
const max = props.max
|
||||
if (typeof value === 'number' && typeof max === 'number')
|
||||
return max < value ? `${max}+` : value
|
||||
|
||||
return value
|
||||
})
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
const componentName = `${PREFIX}-badge`
|
||||
|
||||
export default defineComponent({
|
||||
name: componentName,
|
||||
options: {
|
||||
virtualHost: true,
|
||||
addGlobalClass: true,
|
||||
styleIsolation: 'shared',
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view :class="classes">
|
||||
<view v-if="!props.hidden && !props.dot && $slots.icon" class="nut-badge__icon" :style="getStyle">
|
||||
<slot name="icon" />
|
||||
</view>
|
||||
<slot />
|
||||
<view
|
||||
v-if="!props.hidden && (content || props.dot)"
|
||||
class="nut-badge__content nut-badge__content--sup"
|
||||
:class="{ 'nut-badge__content--dot': props.dot, 'nut-badge__content--bubble': !props.dot && props.bubble }"
|
||||
:style="getStyle"
|
||||
>
|
||||
{{ content }}
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import './index';
|
||||
</style>
|
||||
54
uni_modules/nutui-uni/components/badge/index.scss
Normal file
54
uni_modules/nutui-uni/components/badge/index.scss
Normal file
@@ -0,0 +1,54 @@
|
||||
.nut-theme-dark {
|
||||
.nut-badge {
|
||||
&.show {
|
||||
color: $dark-color;
|
||||
background: $dark-background;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nut-badge {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
|
||||
.nut-badge__icon {
|
||||
position: absolute;
|
||||
z-index: $badge-z-index;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: $badge-icon-padding;
|
||||
line-height: normal;
|
||||
text-align: center;
|
||||
background: $badge-background-color;
|
||||
border-radius: $badge-border-radius;
|
||||
transform: $badge-content-transform;
|
||||
}
|
||||
|
||||
.nut-badge__content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
transform: $badge-content-transform;
|
||||
|
||||
&--sup {
|
||||
position: absolute;
|
||||
padding: $badge-padding;
|
||||
font-size: $badge-font-size;
|
||||
font-weight: normal;
|
||||
color: $badge-color;
|
||||
text-align: center;
|
||||
background: $badge-background-color;
|
||||
border-radius: $badge-border-radius;
|
||||
}
|
||||
|
||||
&--dot {
|
||||
width: $badge-dot-width;
|
||||
height: $badge-dot-height;
|
||||
padding: $badge-dot-padding;
|
||||
border-radius: $badge-dot-border-radius;
|
||||
}
|
||||
|
||||
&--bubble {
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
1
uni_modules/nutui-uni/components/badge/index.ts
Normal file
1
uni_modules/nutui-uni/components/badge/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './badge'
|
||||
41
uni_modules/nutui-uni/components/barrage/barrage.ts
Normal file
41
uni_modules/nutui-uni/components/barrage/barrage.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import type { ExtractPropTypes } from 'vue'
|
||||
import { commonProps, makeArrayProp, makeNumberProp, truthProp } from '../_utils'
|
||||
|
||||
export const barrageProps = {
|
||||
...commonProps,
|
||||
/**
|
||||
* @description 弹幕列表数据
|
||||
*/
|
||||
danmu: makeArrayProp<string>([]),
|
||||
|
||||
/**
|
||||
* @description 可视区域内每个弹幕出现的时间间隔
|
||||
*/
|
||||
frequency: makeNumberProp<number>(500),
|
||||
|
||||
/**
|
||||
* @description 每个弹幕的滚动时间
|
||||
*/
|
||||
speeds: makeNumberProp<number>(5000),
|
||||
|
||||
/**
|
||||
* @description 弹幕行数,分几行展示
|
||||
*/
|
||||
rows: makeNumberProp<number>(3),
|
||||
|
||||
/**
|
||||
* @description 弹幕垂直距离
|
||||
*/
|
||||
top: makeNumberProp<number>(10),
|
||||
|
||||
/**
|
||||
* @description 是否循环播放
|
||||
*/
|
||||
loop: truthProp,
|
||||
}
|
||||
|
||||
export type BarrageProps = ExtractPropTypes<typeof barrageProps>
|
||||
|
||||
export interface BarrageInst {
|
||||
add: (word: string) => void
|
||||
}
|
||||
132
uni_modules/nutui-uni/components/barrage/barrage.vue
Normal file
132
uni_modules/nutui-uni/components/barrage/barrage.vue
Normal file
@@ -0,0 +1,132 @@
|
||||
<script setup lang="ts">
|
||||
import type { ComponentInternalInstance } from 'vue'
|
||||
import { computed, defineComponent, getCurrentInstance, onMounted, reactive, ref, useSlots, watch } from 'vue'
|
||||
import { PREFIX } from '../_constants'
|
||||
import { useSelectorQuery } from '../_hooks'
|
||||
import { getMainClass } from '../_utils'
|
||||
import { barrageProps } from './barrage'
|
||||
|
||||
const props = defineProps(barrageProps)
|
||||
|
||||
defineExpose({ add })
|
||||
|
||||
const instance = getCurrentInstance() as ComponentInternalInstance
|
||||
const { getSelectorNodeInfo } = useSelectorQuery(instance)
|
||||
const classTime = new Date().getTime()
|
||||
|
||||
const slotDefault = !!useSlots().default
|
||||
|
||||
const timeId = ref(new Date().getTime())
|
||||
const danmuList = ref<any>(props.danmu)
|
||||
const rows = ref<number>(props.rows)
|
||||
const top = ref<number>(props.top)
|
||||
const speeds = props.speeds
|
||||
|
||||
const classes = computed(() => {
|
||||
return getMainClass(props, componentName, {
|
||||
[`nut-barrage--dmBody${timeId.value}`]: true,
|
||||
})
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
// #ifdef H5
|
||||
if (slotDefault) {
|
||||
const list = document
|
||||
.getElementsByClassName(`nut-barrage__slotBody${classTime}`)[0]
|
||||
.getElementsByClassName('nut-barrage__item')
|
||||
const childrens = list?.[0]?.children || []
|
||||
danmuList.value = childrens
|
||||
}
|
||||
// #endif
|
||||
runStep()
|
||||
})
|
||||
|
||||
watch(
|
||||
() => props.danmu,
|
||||
(newValue) => {
|
||||
danmuList.value = [...newValue]
|
||||
},
|
||||
)
|
||||
|
||||
function add(word: string) {
|
||||
danmuList.value = [...danmuList.value, word]
|
||||
runStep()
|
||||
}
|
||||
|
||||
function getNode(index: number) {
|
||||
setTimeout(async () => {
|
||||
let width = 100
|
||||
const dmBodyNodeInfo = await getSelectorNodeInfo(`.nut-barrage--dmBody${timeId.value}`)
|
||||
width = dmBodyNodeInfo?.width || 300
|
||||
const itemNodeInfo = await getSelectorNodeInfo(`.nut-barrage__item${index}`)
|
||||
|
||||
const height = itemNodeInfo?.height
|
||||
const nodeTop = `${(index % rows.value) * (height! + top.value) + 20}px`
|
||||
styleInfo(index, nodeTop, width)
|
||||
}, 500)
|
||||
}
|
||||
|
||||
function runStep() {
|
||||
danmuList.value.forEach((item: any, index: number) => {
|
||||
getNode(index)
|
||||
})
|
||||
}
|
||||
const styleList: any[] = reactive([])
|
||||
function styleInfo(index: number, nodeTop: string, width: number) {
|
||||
const timeIndex = index - rows.value > 0 ? index - rows.value : 0
|
||||
const list = styleList
|
||||
const time = list[timeIndex] ? Number(list[timeIndex]['--time']) : 0
|
||||
// distance.value = '-' + (speeds / 1000) * 200 + '%';
|
||||
|
||||
const obj = {
|
||||
'top': nodeTop,
|
||||
'--time': `${props.frequency * index + time}`,
|
||||
'animationDuration': `${speeds}ms`,
|
||||
'animationIterationCount': `${props.loop ? 'infinite' : 1}`,
|
||||
'animationDelay': `${props.frequency * index + time}ms`,
|
||||
'--move-distance': `-${width}px`,
|
||||
}
|
||||
if (slotDefault && danmuList.value[index]?.el) {
|
||||
const orginalSty = danmuList.value[index].el.style
|
||||
danmuList.value[index].el.style = Object.assign(orginalSty, obj)
|
||||
}
|
||||
else {
|
||||
styleList.push(obj)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
const componentName = `${PREFIX}-barrage`
|
||||
|
||||
export default defineComponent({
|
||||
name: componentName,
|
||||
options: {
|
||||
virtualHost: true,
|
||||
addGlobalClass: true,
|
||||
styleIsolation: 'shared',
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view :class="classes" :style="customStyle">
|
||||
<div>
|
||||
<div :class="[`nut-barrage__slotBody${classTime}`]">
|
||||
<view
|
||||
v-for="(item, index) of danmuList"
|
||||
:key="`danmu${index}`"
|
||||
class="nut-barrage__item move"
|
||||
:class="[`nut-barrage__item${index}`]"
|
||||
:style="styleList[index]"
|
||||
>
|
||||
{{ item.length > 8 ? `${item.substr(0, 8)}...` : item }}
|
||||
</view>
|
||||
</div>
|
||||
</div>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import './index';
|
||||
</style>
|
||||
60
uni_modules/nutui-uni/components/barrage/index.scss
Normal file
60
uni_modules/nutui-uni/components/barrage/index.scss
Normal file
@@ -0,0 +1,60 @@
|
||||
.nut-barrage {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
|
||||
--move-distance: '300%';
|
||||
|
||||
&__item {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
display: block;
|
||||
width: 100px;
|
||||
padding: 3px 25px;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
white-space: pre;
|
||||
background: linear-gradient(to right, rgb(0 0 0 / 15%), rgb(0 0 0 / 0%));
|
||||
border-radius: 50px;
|
||||
transform: translateX(100%);
|
||||
|
||||
&.move {
|
||||
animation-name: moving;
|
||||
animation-play-state: running;
|
||||
animation-timing-function: linear;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
@keyframes moving {
|
||||
from {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: translateX(var(--move-distance));
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes moving {
|
||||
from {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: translateX(var(--move-distance));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nut-theme-dark {
|
||||
.nut-barrage {
|
||||
.nut-barrage__item {
|
||||
color: $dark-color-gray;
|
||||
}
|
||||
}
|
||||
}
|
||||
1
uni_modules/nutui-uni/components/barrage/index.ts
Normal file
1
uni_modules/nutui-uni/components/barrage/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export type * from './barrage'
|
||||
139
uni_modules/nutui-uni/components/button/button.ts
Normal file
139
uni_modules/nutui-uni/components/button/button.ts
Normal file
@@ -0,0 +1,139 @@
|
||||
import type { ButtonLang, ButtonOnAddgroupappEvent, ButtonOnAgreeprivacyauthorizationEvent, ButtonOnChooseaddressEvent, ButtonOnChooseavatarEvent, ButtonOnChooseinvoicetitleEvent, ButtonOnErrorEvent, ButtonOnGetphonenumberEvent, ButtonOnLaunchappEvent, ButtonOnLoginEvent, ButtonOnOpensettingEvent, ButtonOnSubscribeEvent, ButtonOpenType } from '@uni-helper/uni-app-types'
|
||||
import type { ExtractPropTypes, PropType } from 'vue'
|
||||
import { CLICK_EVENT } from '../_constants'
|
||||
import { commonProps, makeNumberProp, makeStringProp } from '../_utils'
|
||||
import type { ButtonFormType, ButtonShape, ButtonSize, ButtonType } from './type'
|
||||
|
||||
export const buttonProps = {
|
||||
...commonProps,
|
||||
/**
|
||||
* @description 指定按钮按下去的样式类
|
||||
*/
|
||||
hoverClass: makeStringProp('button-hover'),
|
||||
/**
|
||||
* @description 按住后多久出现点击态,单位毫秒
|
||||
*/
|
||||
hoverStartTime: makeNumberProp(20),
|
||||
/**
|
||||
* @description 手指松开后点击态保留时间,单位毫秒
|
||||
*/
|
||||
hoverStayTime: makeNumberProp(70),
|
||||
/**
|
||||
* @description 按钮颜色,支持传入 `linear-gradient` 渐变色
|
||||
*/
|
||||
customColor: String,
|
||||
/**
|
||||
* @description 形状,可选值为 `square` `round`
|
||||
*/
|
||||
shape: makeStringProp<ButtonShape>('round'),
|
||||
/**
|
||||
* @description 是否为朴素按钮
|
||||
*/
|
||||
plain: Boolean,
|
||||
/**
|
||||
* @description 按钮 `loading` 状态
|
||||
*/
|
||||
loading: Boolean,
|
||||
/**
|
||||
* @description 是否禁用按钮
|
||||
*/
|
||||
disabled: Boolean,
|
||||
/**
|
||||
* @description 按钮类型,可选值为 `primary` `info` `warning` `danger` `success` `default`
|
||||
*/
|
||||
type: makeStringProp<ButtonType>('default'),
|
||||
/**
|
||||
* @description 表单类型,可选值 `button` `submit` `reset`
|
||||
*/
|
||||
formType: makeStringProp<ButtonFormType>('button'),
|
||||
/**
|
||||
* @description 尺寸,可选值为 `large` `small` `mini` `normal`
|
||||
*/
|
||||
size: makeStringProp<ButtonSize>('normal'),
|
||||
/**
|
||||
* @description 是否为块级元素
|
||||
*/
|
||||
block: Boolean,
|
||||
/**
|
||||
* @description 小程序开放能力
|
||||
*/
|
||||
openType: String as PropType<ButtonOpenType>,
|
||||
/**
|
||||
* @description 指定返回用户信息的语言,zh_CN 简体中文,zh_TW 繁体中文,en 英文
|
||||
*/
|
||||
lang: makeStringProp<ButtonLang>('en'),
|
||||
/**
|
||||
* @description 会话来源,openType="contact"时有效
|
||||
*/
|
||||
sessionFrom: String,
|
||||
/**
|
||||
* @description 会话内消息卡片标题,openType="contact"时有效
|
||||
*/
|
||||
sendMessageTitle: String,
|
||||
/**
|
||||
* @description 会话内消息卡片点击跳转小程序路径,openType="contact"时有效
|
||||
*/
|
||||
sendMessagePath: String,
|
||||
/**
|
||||
* @description 会话内消息卡片图片,openType="contact"时有效
|
||||
*/
|
||||
sendMessageImg: String,
|
||||
/**
|
||||
* @description 是否显示会话内消息卡片,设置此参数为 true,用户进入客服会话会在右下角显示"可能要发送的小程序"提示,用户点击后可以快速发送小程序消息,openType="contact"时有效
|
||||
*/
|
||||
showMessageCard: Boolean,
|
||||
/**
|
||||
* @description 打开群资料卡时,传递的群号,openType="openGroupProfile"时有效
|
||||
*/
|
||||
groupId: String,
|
||||
/**
|
||||
* @description 打开频道页面时,传递的频道号 openType="openGuildProfile"时有效
|
||||
*/
|
||||
guildId: makeStringProp(''),
|
||||
/**
|
||||
* @description 打开公众号资料卡时,传递的号码 openType="openPublicProfile"时有效
|
||||
*/
|
||||
publicId: String,
|
||||
/**
|
||||
* @description 客服的抖音号,openType="im"时有效
|
||||
*/
|
||||
dataImId: String,
|
||||
/**
|
||||
* @description IM卡片类型,openType="im"时有效
|
||||
*/
|
||||
dataImType: String,
|
||||
/**
|
||||
* @description 商品的id,仅支持泛知识课程库和生活服务商品库中的商品,openType="im"时有效
|
||||
*/
|
||||
dataGoodsId: String,
|
||||
/**
|
||||
* @description 订单的id,仅支持交易2.0订单, openType="im"时有效
|
||||
*/
|
||||
dataOrderId: String,
|
||||
/**
|
||||
* @description 商品类型,“1”代表生活服务,“2”代表泛知识。openType="im"时有效
|
||||
*/
|
||||
dataBizLine: String,
|
||||
} as const
|
||||
|
||||
export type ButtonProps = ExtractPropTypes<typeof buttonProps>
|
||||
|
||||
export const buttonEmits = {
|
||||
[CLICK_EVENT]: (evt: MouseEvent) => evt instanceof Object,
|
||||
getphonenumber: (evt: ButtonOnGetphonenumberEvent) => evt instanceof Object,
|
||||
getuserinfo: (evt: any) => evt instanceof Object,
|
||||
error: (evt: ButtonOnErrorEvent) => evt instanceof Object,
|
||||
opensetting: (evt: ButtonOnOpensettingEvent) => evt instanceof Object,
|
||||
launchapp: (evt: ButtonOnLaunchappEvent) => evt instanceof Object,
|
||||
contact: (evt: any) => evt instanceof Object,
|
||||
chooseavatar: (evt: ButtonOnChooseavatarEvent) => evt instanceof Object,
|
||||
agreeprivacyauthorization: (evt: ButtonOnAgreeprivacyauthorizationEvent) => evt instanceof Object,
|
||||
addgroupapp: (evt: ButtonOnAddgroupappEvent) => evt instanceof Object,
|
||||
chooseaddress: (evt: ButtonOnChooseaddressEvent) => evt instanceof Object,
|
||||
chooseinvoicetitle: (evt: ButtonOnChooseinvoicetitleEvent) => evt instanceof Object,
|
||||
subscribe: (evt: ButtonOnSubscribeEvent) => evt instanceof Object,
|
||||
login: (evt: ButtonOnLoginEvent) => evt instanceof Object,
|
||||
im: (evt: any) => evt instanceof Object,
|
||||
}
|
||||
|
||||
export type ButtonEmits = typeof buttonEmits
|
||||
121
uni_modules/nutui-uni/components/button/button.vue
Normal file
121
uni_modules/nutui-uni/components/button/button.vue
Normal file
@@ -0,0 +1,121 @@
|
||||
<script lang="ts" setup>
|
||||
import type { CSSProperties } from 'vue'
|
||||
import { computed, defineComponent } from 'vue'
|
||||
import { CLICK_EVENT, PREFIX } from '../_constants'
|
||||
import { getMainClass, getMainStyle } from '../_utils'
|
||||
import Icon from '../icon/icon.vue'
|
||||
import { buttonEmits, buttonProps } from './button'
|
||||
|
||||
const props = defineProps(buttonProps)
|
||||
|
||||
const emit = defineEmits(buttonEmits)
|
||||
|
||||
const classes = computed(() => {
|
||||
return getMainClass(props, componentName, {
|
||||
[`${componentName}--${props.type}`]: !!props.type,
|
||||
[`${componentName}--${props.size}`]: !!props.size,
|
||||
[`${componentName}--${props.shape}`]: !!props.shape,
|
||||
[`${componentName}--plain`]: props.plain,
|
||||
[`${componentName}--block`]: props.block,
|
||||
[`${componentName}--disabled`]: props.disabled,
|
||||
[`${componentName}--loading`]: props.loading,
|
||||
[`${componentName}--hovercls`]: props.hoverClass !== 'button-hover',
|
||||
})
|
||||
})
|
||||
|
||||
const styles = computed(() => {
|
||||
const value: CSSProperties = {}
|
||||
|
||||
if (props.customColor) {
|
||||
if (props.plain) {
|
||||
value.color = props.customColor
|
||||
value.background = '#fff'
|
||||
|
||||
if (!props.customColor.includes('gradient'))
|
||||
value.borderColor = props.customColor
|
||||
}
|
||||
else {
|
||||
value.color = '#fff'
|
||||
value.background = props.customColor
|
||||
}
|
||||
}
|
||||
|
||||
return getMainStyle(props, value)
|
||||
})
|
||||
|
||||
function handleClick(event: any) {
|
||||
if (props.disabled || props.loading)
|
||||
return
|
||||
|
||||
emit(CLICK_EVENT, event)
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
const componentName = `${PREFIX}-button`
|
||||
|
||||
export default defineComponent({
|
||||
name: componentName,
|
||||
options: {
|
||||
virtualHost: true,
|
||||
addGlobalClass: true,
|
||||
// #ifndef H5
|
||||
styleIsolation: 'shared',
|
||||
// #endif
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button
|
||||
:class="classes"
|
||||
:style="styles"
|
||||
:form-type="props.formType === 'button' ? undefined : props.formType"
|
||||
:open-type="props.disabled || props.loading ? undefined : props.openType"
|
||||
:hover-class="props.hoverClass"
|
||||
:hover-start-time="props.hoverStartTime"
|
||||
:hover-stay-time="props.hoverStayTime"
|
||||
hover-stop-propagation
|
||||
:lang="props.lang"
|
||||
:session-from="props.sessionFrom"
|
||||
:send-message-title="props.sendMessageTitle"
|
||||
:send-message-path="props.sendMessagePath"
|
||||
:send-message-img="props.sendMessageImg"
|
||||
:show-message-card="props.showMessageCard"
|
||||
:group-id="props.groupId"
|
||||
:guild-id="props.guildId"
|
||||
:public-id="props.publicId"
|
||||
:data-im-id="props.dataImId"
|
||||
:data-im-type="props.dataImType"
|
||||
:data-goods-id="props.dataGoodsId"
|
||||
:data-order-id="props.dataOrderId"
|
||||
:data-biz-line="props.dataBizLine"
|
||||
@click="handleClick"
|
||||
@getphonenumber="emit('getphonenumber', $event)"
|
||||
@getuserinfo="emit('getuserinfo', $event)"
|
||||
@error="emit('error', $event)"
|
||||
@opensetting="emit('opensetting', $event)"
|
||||
@addgroupapp="emit('addgroupapp', $event)"
|
||||
@chooseaddress="emit('chooseaddress', $event)"
|
||||
@chooseavatar="emit('chooseavatar', $event)"
|
||||
@chooseinvoicetitle="emit('chooseinvoicetitle', $event)"
|
||||
@launchapp="emit('launchapp', $event)"
|
||||
@login="emit('login', $event)"
|
||||
@subscribe="emit('subscribe', $event)"
|
||||
@contact="emit('contact', $event)"
|
||||
@agreeprivacyauthorization="emit('agreeprivacyauthorization', $event)"
|
||||
@im="emit('im', $event)"
|
||||
>
|
||||
<view class="nut-button__wrap">
|
||||
<Icon v-if="loading" name="loading" class="nut-icon-loading" />
|
||||
<slot v-if="$slots.icon && !loading" name="icon" />
|
||||
<view v-if="$slots.default" :class="{ 'nut-button__text': $slots.icon || loading }">
|
||||
<slot />
|
||||
</view>
|
||||
</view>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import './index';
|
||||
</style>
|
||||
292
uni_modules/nutui-uni/components/button/index.scss
Normal file
292
uni_modules/nutui-uni/components/button/index.scss
Normal file
@@ -0,0 +1,292 @@
|
||||
.nut-theme-dark {
|
||||
.nut-button {
|
||||
&--default {
|
||||
color: $dark-color3;
|
||||
background: $dark-background2;
|
||||
border: $button-border-width solid $dark-background2;
|
||||
}
|
||||
|
||||
&--plain {
|
||||
background: $dark-background2;
|
||||
}
|
||||
|
||||
&:not(.nut-button--hovercls) {
|
||||
.nut-button--plain:not([disabled]):active {
|
||||
background: $dark-background2;
|
||||
}
|
||||
|
||||
.nut-button--default:not([disabled]):active {
|
||||
color: $dark-color3;
|
||||
background: $dark-background2;
|
||||
border: $button-border-width solid $dark-background2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nut-button {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
display: inline-block;
|
||||
flex-shrink: 0;
|
||||
width: auto;
|
||||
height: $button-default-height;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-size: $button-default-font-size;
|
||||
line-height: $button-default-line-height;
|
||||
text-align: center;
|
||||
vertical-align: bottom;
|
||||
appearance: none;
|
||||
touch-action: manipulation;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
transition: opacity 0.2s;
|
||||
-webkit-tap-highlight-color: rgb(0 0 0 / 0%);
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
|
||||
.nut-button__text {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
content: "";
|
||||
background-color: $black;
|
||||
border: inherit;
|
||||
border-color: $black;
|
||||
border-radius: inherit;
|
||||
opacity: 0;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
&::after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:not(.nut-button--hovercls) {
|
||||
&:active::before {
|
||||
opacity: 0.1;
|
||||
}
|
||||
}
|
||||
|
||||
&__wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&--loading,
|
||||
&--disabled {
|
||||
&::before {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&--default {
|
||||
color: $button-default-color;
|
||||
background: $button-default-bg-color;
|
||||
background-origin: border-box;
|
||||
border: $button-border-width solid $button-default-border-color;
|
||||
}
|
||||
|
||||
&--primary {
|
||||
color: $button-primary-color;
|
||||
background: $button-primary-background-color;
|
||||
background-origin: border-box;
|
||||
border: $button-border-width solid transparent;
|
||||
}
|
||||
|
||||
&--info {
|
||||
color: $button-info-color;
|
||||
background: $button-info-background-color;
|
||||
background-origin: border-box;
|
||||
border: $button-border-width solid transparent;
|
||||
}
|
||||
|
||||
&--success {
|
||||
color: $button-success-color;
|
||||
background: $button-success-background-color;
|
||||
background-origin: border-box;
|
||||
border: $button-border-width solid transparent;
|
||||
}
|
||||
|
||||
&--danger {
|
||||
color: $button-danger-color;
|
||||
background: $button-danger-background-color;
|
||||
background-origin: border-box;
|
||||
border: $button-border-width solid transparent;
|
||||
}
|
||||
|
||||
&--warning {
|
||||
color: $button-warning-color;
|
||||
background: $button-warning-background-color;
|
||||
background-origin: border-box;
|
||||
border: $button-border-width solid transparent;
|
||||
}
|
||||
|
||||
&--plain {
|
||||
background: $button-plain-background-color;
|
||||
background-origin: border-box;
|
||||
|
||||
&.nut-button--primary {
|
||||
color: $button-primary-border-color;
|
||||
border-color: $button-primary-border-color;
|
||||
}
|
||||
|
||||
&.nut-button--info {
|
||||
color: $button-info-border-color;
|
||||
border-color: $button-info-border-color;
|
||||
}
|
||||
|
||||
&.nut-button--success {
|
||||
color: $button-success-border-color;
|
||||
border-color: $button-success-border-color;
|
||||
}
|
||||
|
||||
&.nut-button--danger {
|
||||
color: $button-danger-border-color;
|
||||
border-color: $button-danger-border-color;
|
||||
}
|
||||
|
||||
&.nut-button--warning {
|
||||
color: $button-warning-border-color;
|
||||
border-color: $button-warning-border-color;
|
||||
}
|
||||
|
||||
&:not(.nut-button--hovercls) {
|
||||
&.nut-button--primary:not([disabled]):active {
|
||||
color: $button-primary-border-color;
|
||||
border-color: $button-primary-border-color;
|
||||
}
|
||||
|
||||
&.nut-button--info:not([disabled]):active {
|
||||
color: $button-info-border-color;
|
||||
border-color: $button-info-border-color;
|
||||
}
|
||||
|
||||
&.nut-button--success:not([disabled]):active {
|
||||
color: $button-success-border-color;
|
||||
border-color: $button-success-border-color;
|
||||
}
|
||||
|
||||
&.nut-button--danger:not([disabled]):active {
|
||||
color: $button-danger-border-color;
|
||||
border-color: $button-danger-border-color;
|
||||
}
|
||||
|
||||
&.nut-button--warning:not([disabled]):active {
|
||||
color: $button-warning-border-color;
|
||||
border-color: $button-warning-border-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&--large {
|
||||
width: 100%;
|
||||
height: $button-large-height;
|
||||
font-size: $button-large-font-size;
|
||||
line-height: $button-large-line-height;
|
||||
}
|
||||
|
||||
&--normal {
|
||||
padding: $button-default-padding;
|
||||
font-size: $button-default-font-size;
|
||||
}
|
||||
|
||||
&--small {
|
||||
height: $button-small-height;
|
||||
padding: $button-small-padding;
|
||||
font-size: $button-small-font-size;
|
||||
line-height: $button-small-line-height;
|
||||
|
||||
&.nut-button--round {
|
||||
border-radius: $button-small-round-border-radius;
|
||||
}
|
||||
}
|
||||
|
||||
&--mini {
|
||||
height: $button-mini-height;
|
||||
padding: $button-mini-padding;
|
||||
font-size: $button-mini-font-size;
|
||||
line-height: $button-mini-line-height;
|
||||
}
|
||||
|
||||
&--block {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&--disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: $button-disabled-opacity;
|
||||
}
|
||||
|
||||
&--loading {
|
||||
cursor: default;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
&--round {
|
||||
border-radius: $button-border-radius;
|
||||
}
|
||||
|
||||
&--square {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
&:not(.nut-button--hovercls) {
|
||||
.nut-button--default:not([disabled]):active {
|
||||
color: $button-default-color;
|
||||
background: $button-default-bg-color;
|
||||
background-origin: border-box;
|
||||
border: $button-border-width solid $button-default-border-color;
|
||||
}
|
||||
|
||||
.nut-button--primary:not([disabled]):active {
|
||||
color: $button-primary-color;
|
||||
background: $button-primary-background-color;
|
||||
background-origin: border-box;
|
||||
border: $button-border-width solid transparent;
|
||||
}
|
||||
|
||||
.nut-button--info:not([disabled]):active {
|
||||
color: $button-info-color;
|
||||
background: $button-info-background-color;
|
||||
background-origin: border-box;
|
||||
border: $button-border-width solid transparent;
|
||||
}
|
||||
|
||||
.nut-button--success:not([disabled]):active {
|
||||
color: $button-success-color;
|
||||
background: $button-success-background-color;
|
||||
background-origin: border-box;
|
||||
border: $button-border-width solid transparent;
|
||||
}
|
||||
|
||||
.nut-button--danger:not([disabled]):active {
|
||||
color: $button-danger-color;
|
||||
background: $button-danger-background-color;
|
||||
background-origin: border-box;
|
||||
border: $button-border-width solid transparent;
|
||||
}
|
||||
|
||||
.nut-button--warning:not([disabled]):active {
|
||||
color: $button-warning-color;
|
||||
background: $button-warning-background-color;
|
||||
background-origin: border-box;
|
||||
border: $button-border-width solid transparent;
|
||||
}
|
||||
|
||||
.nut-button--plain:not([disabled]):active {
|
||||
background: $button-plain-background-color;
|
||||
background-origin: border-box;
|
||||
}
|
||||
}
|
||||
}
|
||||
2
uni_modules/nutui-uni/components/button/index.ts
Normal file
2
uni_modules/nutui-uni/components/button/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './button'
|
||||
export * from './type'
|
||||
8
uni_modules/nutui-uni/components/button/type.ts
Normal file
8
uni_modules/nutui-uni/components/button/type.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export const buttonType = ['default', 'primary', 'info', 'success', 'warning', 'danger'] as const
|
||||
export type ButtonType = (typeof buttonType)[number]
|
||||
export const buttonSize = ['large', 'normal', 'small', 'mini'] as const
|
||||
export type ButtonSize = (typeof buttonSize)[number]
|
||||
export const buttonShape = ['square', 'round'] as const
|
||||
export type ButtonShape = (typeof buttonShape)[number]
|
||||
export const buttonFormType = ['button', 'submit', 'reset'] as const
|
||||
export type ButtonFormType = (typeof buttonFormType)[number]
|
||||
125
uni_modules/nutui-uni/components/calendar/calendar.ts
Normal file
125
uni_modules/nutui-uni/components/calendar/calendar.ts
Normal file
@@ -0,0 +1,125 @@
|
||||
import type { ExtractPropTypes, PropType, StyleValue } from 'vue'
|
||||
import {
|
||||
CHOOSE_EVENT,
|
||||
CLOSE_EVENT,
|
||||
CLOSED_EVENT,
|
||||
OPEN_EVENT,
|
||||
OPENED_EVENT,
|
||||
SELECT_EVENT,
|
||||
UPDATE_VISIBLE_EVENT,
|
||||
} from '../_constants'
|
||||
import { commonProps, getDay, makeNumberProp, makeStringProp, truthProp } from '../_utils'
|
||||
import { popupProps } from '../popup/popup'
|
||||
|
||||
export const calendarProps = {
|
||||
...popupProps,
|
||||
...commonProps,
|
||||
/**
|
||||
* @description 是否可见
|
||||
*/
|
||||
visible: Boolean,
|
||||
/**
|
||||
* @description 类型,日期单选 `one`,区间选择 `range`,日期多选 `multiple`,周选择 `week`
|
||||
*/
|
||||
type: makeStringProp<'one' | 'range' | 'multiple' | 'week'>('one'),
|
||||
/**
|
||||
* @description 是否弹窗状态展示
|
||||
*/
|
||||
poppable: truthProp,
|
||||
/**
|
||||
* @description 自动回填
|
||||
*/
|
||||
isAutoBackFill: Boolean,
|
||||
/**
|
||||
* @description 显示标题
|
||||
*/
|
||||
title: makeStringProp('日期选择'),
|
||||
/**
|
||||
* @description 默认值,单个日期选择为 `string`,其他为 `string[]`
|
||||
*/
|
||||
defaultValue: {
|
||||
type: [String, Array] as PropType<string | string[]>,
|
||||
},
|
||||
/**
|
||||
* @description 开始日期
|
||||
*/
|
||||
startDate: makeStringProp(getDay(0)),
|
||||
/**
|
||||
* @description 结束日期
|
||||
*/
|
||||
endDate: makeStringProp(getDay(365)),
|
||||
/**
|
||||
* @description 范围选择,开始信息文案
|
||||
*/
|
||||
startText: makeStringProp('开始'),
|
||||
/**
|
||||
* @description 范围选择,结束信息文案
|
||||
*/
|
||||
endText: makeStringProp('结束'),
|
||||
/**
|
||||
* @description 底部确认按钮文案
|
||||
*/
|
||||
confirmText: makeStringProp('确认'),
|
||||
/**
|
||||
* @description 是否展示今天标记
|
||||
*/
|
||||
showToday: truthProp,
|
||||
/**
|
||||
* @description 是否在展示日历标题
|
||||
*/
|
||||
showTitle: truthProp,
|
||||
/**
|
||||
* @description 是否展示日期标题
|
||||
*/
|
||||
showSubTitle: truthProp,
|
||||
/**
|
||||
* @description 是否启动滚动动画
|
||||
*/
|
||||
toDateAnimation: truthProp,
|
||||
/**
|
||||
* @description 设置周起始日
|
||||
*/
|
||||
firstDayOfWeek: makeNumberProp(0),
|
||||
/**
|
||||
* @description 一个用来判断该日期是否被禁用的函数,接受一个 `年 - 月 - 日` 作为参数。 应该返回一个 Boolean 值。
|
||||
* @default undefined
|
||||
*/
|
||||
disabledDate: Function,
|
||||
/**
|
||||
* @description 是否使用 footer 插槽,如果使用,此值必须为 true
|
||||
*/
|
||||
footerSlot: Boolean,
|
||||
/**
|
||||
* @description 是否使用 btn 插槽,如果使用,此值必须为 true
|
||||
*/
|
||||
btnSlot: Boolean,
|
||||
/**
|
||||
* @description 自定义弹窗样式
|
||||
*/
|
||||
popStyle: {
|
||||
type: [String, Object, Array] as PropType<StyleValue>,
|
||||
default: '',
|
||||
},
|
||||
/**
|
||||
* @description 遮罩显示时的背景是否锁定
|
||||
*/
|
||||
lockScroll: truthProp,
|
||||
}
|
||||
|
||||
export type CalendarProps = ExtractPropTypes<typeof calendarProps>
|
||||
|
||||
/* eslint-disable unused-imports/no-unused-vars */
|
||||
export const calendarEmits = {
|
||||
[UPDATE_VISIBLE_EVENT]: (value: boolean) => true,
|
||||
[CHOOSE_EVENT]: (value: string | object) => true,
|
||||
[SELECT_EVENT]: (value: any) => true,
|
||||
clickCloseIcon: () => true,
|
||||
clickOverlay: () => true,
|
||||
[OPEN_EVENT]: () => true,
|
||||
[OPENED_EVENT]: () => true,
|
||||
[CLOSE_EVENT]: () => true,
|
||||
[CLOSED_EVENT]: () => true,
|
||||
}
|
||||
/* eslint-enable unused-imports/no-unused-vars */
|
||||
|
||||
export type CalendarEmits = typeof calendarEmits
|
||||
262
uni_modules/nutui-uni/components/calendar/calendar.vue
Normal file
262
uni_modules/nutui-uni/components/calendar/calendar.vue
Normal file
@@ -0,0 +1,262 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, defineComponent, ref, useSlots } from 'vue'
|
||||
import {
|
||||
CHOOSE_EVENT,
|
||||
CLOSE_EVENT,
|
||||
CLOSED_EVENT,
|
||||
OPEN_EVENT,
|
||||
OPENED_EVENT,
|
||||
PREFIX,
|
||||
SELECT_EVENT,
|
||||
UPDATE_VISIBLE_EVENT,
|
||||
} from '../_constants'
|
||||
import { getMainClass } from '../_utils'
|
||||
import NutCalendarItem from '../calendaritem/calendaritem.vue'
|
||||
import type { CalendarInst } from '../calendaritem/types'
|
||||
import NutPopup from '../popup/popup.vue'
|
||||
import { calendarEmits, calendarProps } from './calendar'
|
||||
|
||||
const props = defineProps(calendarProps)
|
||||
|
||||
const emit = defineEmits(calendarEmits)
|
||||
|
||||
const slots = useSlots()
|
||||
|
||||
const innerVisible = computed({
|
||||
get() {
|
||||
return props.visible
|
||||
},
|
||||
set(value) {
|
||||
emit('update:visible', value)
|
||||
},
|
||||
})
|
||||
|
||||
const classes = computed(() => {
|
||||
return getMainClass(props, componentName)
|
||||
})
|
||||
|
||||
const popClasses = computed(() => {
|
||||
return `${componentName}__popup ${props.popClass}`
|
||||
})
|
||||
|
||||
const popStyles = computed(() => {
|
||||
return [{
|
||||
height: '85vh',
|
||||
}, props.popStyle]
|
||||
})
|
||||
|
||||
const overlayClasses = computed(() => {
|
||||
return `${componentName}__overlay ${props.overlayClass}`
|
||||
})
|
||||
|
||||
const calendarRef = ref<CalendarInst | null>(null)
|
||||
|
||||
function scrollToDate(date: string) {
|
||||
calendarRef.value?.scrollToDate(date)
|
||||
}
|
||||
|
||||
function initPosition() {
|
||||
calendarRef.value?.initPosition()
|
||||
}
|
||||
|
||||
function close() {
|
||||
emit(UPDATE_VISIBLE_EVENT, false)
|
||||
emit(CLOSE_EVENT)
|
||||
}
|
||||
|
||||
function choose(param: string | object) {
|
||||
close()
|
||||
|
||||
emit(CHOOSE_EVENT, param)
|
||||
}
|
||||
|
||||
function select(param: string) {
|
||||
emit(SELECT_EVENT, param)
|
||||
}
|
||||
|
||||
function update() {
|
||||
emit(UPDATE_VISIBLE_EVENT, false)
|
||||
}
|
||||
|
||||
function handleCloseIconClick() {
|
||||
emit('clickCloseIcon')
|
||||
}
|
||||
|
||||
function handleOverlayClick() {
|
||||
emit('clickOverlay')
|
||||
}
|
||||
|
||||
function handleOpen() {
|
||||
emit(OPEN_EVENT)
|
||||
}
|
||||
|
||||
function handleOpened() {
|
||||
emit(OPENED_EVENT)
|
||||
|
||||
if (props.defaultValue) {
|
||||
if (Array.isArray(props.defaultValue)) {
|
||||
if (props.defaultValue.length > 0) {
|
||||
scrollToDate(props.defaultValue[0])
|
||||
}
|
||||
}
|
||||
else {
|
||||
scrollToDate(props.defaultValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleClose() {
|
||||
emit(CLOSE_EVENT)
|
||||
}
|
||||
|
||||
function handleClosed() {
|
||||
emit(CLOSED_EVENT)
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
scrollToDate,
|
||||
initPosition,
|
||||
})
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
const componentName = `${PREFIX}-calendar`
|
||||
|
||||
export default defineComponent({
|
||||
name: componentName,
|
||||
options: {
|
||||
virtualHost: true,
|
||||
addGlobalClass: true,
|
||||
styleIsolation: 'shared',
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view :class="classes" :style="props.customStyle">
|
||||
<template v-if="props.poppable">
|
||||
<NutPopup
|
||||
v-model:visible="innerVisible"
|
||||
:custom-class="popClasses"
|
||||
:custom-style="popStyles"
|
||||
:overlay-class="overlayClasses"
|
||||
:overlay-style="props.overlayStyle"
|
||||
position="bottom"
|
||||
round
|
||||
:closeable="props.closeable"
|
||||
:close-icon="props.closeIcon"
|
||||
:close-icon-position="props.closeIconPosition"
|
||||
:z-index="props.zIndex"
|
||||
:lock-scroll="props.lockScroll"
|
||||
:overlay="props.overlay"
|
||||
:close-on-click-overlay="props.closeOnClickOverlay"
|
||||
:destroy-on-close="false"
|
||||
@click-close-icon="handleCloseIconClick"
|
||||
@click-overlay="handleOverlayClick"
|
||||
@open="handleOpen"
|
||||
@opened="handleOpened"
|
||||
@close="handleClose"
|
||||
@closed="handleClosed"
|
||||
>
|
||||
<NutCalendarItem
|
||||
ref="calendarRef"
|
||||
:visible="innerVisible"
|
||||
:type="props.type"
|
||||
:poppable="props.poppable"
|
||||
:is-auto-back-fill="props.isAutoBackFill"
|
||||
:title="props.title"
|
||||
:default-value="props.defaultValue"
|
||||
:start-date="props.startDate"
|
||||
:end-date="props.endDate"
|
||||
:start-text="props.startText"
|
||||
:end-text="props.endText"
|
||||
:confirm-text="props.confirmText"
|
||||
:show-today="props.showToday"
|
||||
:show-title="props.showTitle"
|
||||
:show-sub-title="props.showSubTitle"
|
||||
:to-date-animation="props.toDateAnimation"
|
||||
:first-day-of-week="props.firstDayOfWeek"
|
||||
:disabled-date="props.disabledDate"
|
||||
:footer-slot="props.footerSlot"
|
||||
:btn-slot="props.btnSlot"
|
||||
@choose="choose"
|
||||
@select="select"
|
||||
@update="update"
|
||||
@close="close"
|
||||
>
|
||||
<template v-if="slots.btn" #btn>
|
||||
<slot name="btn" />
|
||||
</template>
|
||||
|
||||
<template v-if="slots.day" #day="{ date }">
|
||||
<slot name="day" :date="date" />
|
||||
</template>
|
||||
|
||||
<template v-if="slots.topInfo" #topInfo="{ date }">
|
||||
<slot name="topInfo" :date="date" />
|
||||
</template>
|
||||
|
||||
<template v-if="slots.bottomInfo" #bottomInfo="{ date }">
|
||||
<slot name="bottomInfo" :date="date" />
|
||||
</template>
|
||||
|
||||
<template v-if="slots.footer" #footer="{ date }">
|
||||
<slot name="footer" :date="date" />
|
||||
</template>
|
||||
</NutCalendarItem>
|
||||
</NutPopup>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<NutCalendarItem
|
||||
ref="calendarRef"
|
||||
:visible="innerVisible"
|
||||
:type="props.type"
|
||||
:poppable="props.poppable"
|
||||
:is-auto-back-fill="props.isAutoBackFill"
|
||||
:title="props.title"
|
||||
:default-value="props.defaultValue"
|
||||
:start-date="props.startDate"
|
||||
:end-date="props.endDate"
|
||||
:start-text="props.startText"
|
||||
:end-text="props.endText"
|
||||
:confirm-text="props.confirmText"
|
||||
:show-today="props.showToday"
|
||||
:show-title="props.showTitle"
|
||||
:show-sub-title="props.showSubTitle"
|
||||
:to-date-animation="props.toDateAnimation"
|
||||
:first-day-of-week="props.firstDayOfWeek"
|
||||
:disabled-date="props.disabledDate"
|
||||
:footer-slot="props.footerSlot"
|
||||
:btn-slot="props.btnSlot"
|
||||
@choose="choose"
|
||||
@select="select"
|
||||
@close="close"
|
||||
>
|
||||
<template v-if="slots.btn" #btn>
|
||||
<slot name="btn" />
|
||||
</template>
|
||||
|
||||
<template v-if="slots.day" #day="{ date }">
|
||||
<slot name="day" :date="date" />
|
||||
</template>
|
||||
|
||||
<template v-if="slots.topInfo" #topInfo="{ date }">
|
||||
<slot name="topInfo" :date="date" />
|
||||
</template>
|
||||
|
||||
<template v-if="slots.bottomInfo" #bottomInfo="{ date }">
|
||||
<slot name="bottomInfo" :date="date" />
|
||||
</template>
|
||||
|
||||
<template v-if="slots.footer" #footer="{ date }">
|
||||
<slot name="footer" :date="date" />
|
||||
</template>
|
||||
</NutCalendarItem>
|
||||
</template>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import "./index";
|
||||
</style>
|
||||
1
uni_modules/nutui-uni/components/calendar/index.scss
Normal file
1
uni_modules/nutui-uni/components/calendar/index.scss
Normal file
@@ -0,0 +1 @@
|
||||
@import "../popup/index";
|
||||
1
uni_modules/nutui-uni/components/calendar/index.ts
Normal file
1
uni_modules/nutui-uni/components/calendar/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './calendar'
|
||||
@@ -0,0 +1,99 @@
|
||||
import type { ExtractPropTypes } from 'vue'
|
||||
import { CHOOSE_EVENT, SELECT_EVENT } from '../_constants'
|
||||
import { commonProps, getDay, makeNumberProp, makeStringProp, truthProp } from '../_utils'
|
||||
|
||||
export const calendaritemProps = {
|
||||
...commonProps,
|
||||
/**
|
||||
* @description 是否可见
|
||||
*/
|
||||
visible: Boolean,
|
||||
/**
|
||||
* @description 类型,日期单选 `one`,区间选择 `range`,日期多选 `multiple`,周选择 `week`
|
||||
*/
|
||||
type: makeStringProp<'one' | 'range' | 'multiple' | 'week'>('one'),
|
||||
/**
|
||||
* @description 是否弹窗状态展示
|
||||
*/
|
||||
poppable: truthProp,
|
||||
/**
|
||||
* @description 自动回填
|
||||
*/
|
||||
isAutoBackFill: Boolean,
|
||||
/**
|
||||
* @description 显示标题
|
||||
*/
|
||||
title: makeStringProp('日期选择'),
|
||||
/**
|
||||
* @description 默认值,单个日期选择为 `string`,其他为 `string[]`
|
||||
*/
|
||||
defaultValue: {
|
||||
type: [String, Array],
|
||||
},
|
||||
/**
|
||||
* @description 开始日期
|
||||
*/
|
||||
startDate: makeStringProp(getDay(0)),
|
||||
/**
|
||||
* @description 结束日期
|
||||
*/
|
||||
endDate: makeStringProp(getDay(365)),
|
||||
/**
|
||||
* @description 范围选择,开始信息文案
|
||||
*/
|
||||
startText: makeStringProp('开始'),
|
||||
/**
|
||||
* @description 范围选择,结束信息文案
|
||||
*/
|
||||
endText: makeStringProp('结束'),
|
||||
/**
|
||||
* @description 底部确认按钮文案
|
||||
*/
|
||||
confirmText: makeStringProp('确认'),
|
||||
/**
|
||||
* @description 是否展示今天标记
|
||||
*/
|
||||
showToday: truthProp,
|
||||
/**
|
||||
* @description 是否在展示日历标题
|
||||
*/
|
||||
showTitle: truthProp,
|
||||
/**
|
||||
* @description 是否展示日期标题
|
||||
*/
|
||||
showSubTitle: truthProp,
|
||||
/**
|
||||
* @description 是否启动滚动动画
|
||||
*/
|
||||
toDateAnimation: truthProp,
|
||||
/**
|
||||
* @description 设置周起始日
|
||||
*/
|
||||
firstDayOfWeek: makeNumberProp(0),
|
||||
/**
|
||||
* @description 一个用来判断该日期是否被禁用的函数,接受一个 `年 - 月 - 日` 作为参数。 应该返回一个 Boolean 值。
|
||||
* @default undefined
|
||||
*/
|
||||
disabledDate: Function,
|
||||
/**
|
||||
* @description 是否使用 footer 插槽,如果使用,此值必须为 true
|
||||
*/
|
||||
footerSlot: Boolean,
|
||||
/**
|
||||
* @description 是否使用 btn 插槽,如果使用,此值必须为 true
|
||||
*/
|
||||
btnSlot: Boolean,
|
||||
}
|
||||
|
||||
export type CalendarItemProps = ExtractPropTypes<typeof calendaritemProps>
|
||||
|
||||
/* eslint-disable unused-imports/no-unused-vars */
|
||||
export const calendaritemEmits = {
|
||||
[CHOOSE_EVENT]: (value: string | object) => true,
|
||||
[SELECT_EVENT]: (value: any) => true,
|
||||
update: () => true,
|
||||
close: () => true,
|
||||
}
|
||||
/* eslint-enable unused-imports/no-unused-vars */
|
||||
|
||||
export type CalendarItemEmits = typeof calendaritemEmits
|
||||
860
uni_modules/nutui-uni/components/calendaritem/calendaritem.vue
Normal file
860
uni_modules/nutui-uni/components/calendaritem/calendaritem.vue
Normal file
@@ -0,0 +1,860 @@
|
||||
<script lang="ts" setup>
|
||||
import type { ScrollViewOnScrollEvent } from '@uni-helper/uni-app-types'
|
||||
import { computed, defineComponent, onMounted, reactive, ref, useSlots, watch } from 'vue'
|
||||
import { CHOOSE_EVENT, PREFIX, SELECT_EVENT } from '../_constants'
|
||||
import {
|
||||
compareDate,
|
||||
date2Str,
|
||||
formatResultDate,
|
||||
getDay,
|
||||
getMainClass,
|
||||
getMonthDays,
|
||||
getMonthPreDay,
|
||||
getMonthWeek,
|
||||
getNumTwoBit,
|
||||
getWeekDate,
|
||||
getWhatDay,
|
||||
getYearWeek,
|
||||
isEqual,
|
||||
isH5,
|
||||
} from '../_utils'
|
||||
import requestAniFrame from '../_utils/raf'
|
||||
import { useTranslate } from '../../locale'
|
||||
import { calendaritemEmits, calendaritemProps } from './calendaritem'
|
||||
import type { CalendarDateProp, CalendarTaroState, Day, MonthInfo, StringArr } from './types'
|
||||
|
||||
const props = defineProps(calendaritemProps)
|
||||
|
||||
const emit = defineEmits(calendaritemEmits)
|
||||
|
||||
const slots = useSlots()
|
||||
|
||||
const componentName = `${PREFIX}-calendar-item`
|
||||
|
||||
const { translate } = useTranslate(componentName)
|
||||
|
||||
const state: CalendarTaroState = reactive({
|
||||
yearMonthTitle: '',
|
||||
defaultRange: [],
|
||||
containerHeight: '100%',
|
||||
currDate: '',
|
||||
propStartDate: '',
|
||||
propEndDate: '',
|
||||
unLoadPrev: false,
|
||||
touchParams: {
|
||||
startY: 0,
|
||||
endY: 0,
|
||||
startTime: 0,
|
||||
endTime: 0,
|
||||
lastY: 0,
|
||||
lastTime: 0,
|
||||
},
|
||||
transformY: 0,
|
||||
translateY: 0,
|
||||
scrollDistance: 0,
|
||||
defaultData: [],
|
||||
chooseData: [],
|
||||
monthsData: [],
|
||||
dayPrefix: 'nut-calendar__day',
|
||||
startData: '',
|
||||
endData: '',
|
||||
isRange: props.type === 'range',
|
||||
timer: 0,
|
||||
currentIndex: 0,
|
||||
avgHeight: 0,
|
||||
scrollTop: 0,
|
||||
monthsNum: 0,
|
||||
})
|
||||
|
||||
const classes = computed(() => {
|
||||
return getMainClass(props, componentName, {
|
||||
'nut-calendar--nopop': !props.poppable,
|
||||
'nut-calendar--nofooter': props.isAutoBackFill,
|
||||
})
|
||||
})
|
||||
|
||||
// 新增:自定义周起始日
|
||||
const weekdays = (translate('weekdays') as any).map((day: string, index: number) => ({
|
||||
day,
|
||||
weekend: index === 0 || index === 6,
|
||||
}))
|
||||
|
||||
const weeks = ref([...weekdays.slice(props.firstDayOfWeek, 7), ...weekdays.slice(0, props.firstDayOfWeek)])
|
||||
|
||||
const months = ref<HTMLElement | null>(null)
|
||||
|
||||
const scalePx = ref(2)
|
||||
const viewHeight = ref(0)
|
||||
|
||||
const compConthsData = computed(() => {
|
||||
return state.monthsData.slice(state.defaultRange[0], state.defaultRange[1])
|
||||
})
|
||||
|
||||
const scrollWithAnimation = ref(false)
|
||||
|
||||
// 日期转化成数组
|
||||
function splitDate(date: string) {
|
||||
return date.split('-')
|
||||
}
|
||||
|
||||
// 判断是否为开始时间
|
||||
function isStart(currDate: string) {
|
||||
return isEqual(state.currDate[0], currDate)
|
||||
}
|
||||
|
||||
// 判断是否为结束时间
|
||||
function isEnd(currDate: string) {
|
||||
return isEqual(state.currDate[1], currDate)
|
||||
}
|
||||
|
||||
function isMultiple(currDate: string) {
|
||||
if (state.currDate?.length > 0) {
|
||||
return (state.currDate as StringArr)?.some((item: string) => {
|
||||
return isEqual(item, currDate)
|
||||
})
|
||||
}
|
||||
else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// 获取当前数据
|
||||
function getCurrDate(day: Day, month: MonthInfo) {
|
||||
return `${month.curData[0]}-${month.curData[1]}-${getNumTwoBit(+day.day)}`
|
||||
}
|
||||
|
||||
// 获取样式
|
||||
function getClass(day: Day, month: MonthInfo, index?: number) {
|
||||
const res = []
|
||||
if (
|
||||
typeof index === 'number'
|
||||
&& ((index + 1 + props.firstDayOfWeek) % 7 === 0 || (index + props.firstDayOfWeek) % 7 === 0)
|
||||
) {
|
||||
res.push('weekend')
|
||||
}
|
||||
|
||||
const currDate = getCurrDate(day, month)
|
||||
const { type } = props
|
||||
if (day.type === 'curr') {
|
||||
if (
|
||||
isEqual(state.currDate as string, currDate)
|
||||
|| ((type === 'range' || type === 'week') && (isStart(currDate) || isEnd(currDate)))
|
||||
|| (type === 'multiple' && isMultiple(currDate))
|
||||
) {
|
||||
res.push(`${state.dayPrefix}--active`)
|
||||
}
|
||||
else if (
|
||||
(state.propStartDate && compareDate(currDate, state.propStartDate))
|
||||
|| (state.propEndDate && compareDate(state.propEndDate, currDate))
|
||||
|| (props.disabledDate && props.disabledDate(currDate))
|
||||
) {
|
||||
res.push(`${state.dayPrefix}--disabled`)
|
||||
}
|
||||
else if (
|
||||
(type === 'range' || type === 'week')
|
||||
&& Array.isArray(state.currDate)
|
||||
&& Object.values(state.currDate).length === 2
|
||||
&& compareDate(state.currDate[0], currDate)
|
||||
&& compareDate(currDate, state.currDate[1])
|
||||
) {
|
||||
res.push(`${state.dayPrefix}--choose`)
|
||||
}
|
||||
}
|
||||
else {
|
||||
res.push(`${state.dayPrefix}--disabled`)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// 确认选择时触发
|
||||
function confirm() {
|
||||
const { type } = props
|
||||
if ((type === 'range' && state.chooseData.length === 2) || type !== 'range') {
|
||||
let selectData: any = state.chooseData.slice(0)
|
||||
if (type === 'week') {
|
||||
selectData = {
|
||||
weekDate: [handleWeekDate(state.chooseData[0] as string[]), handleWeekDate(state.chooseData[1] as string[])],
|
||||
}
|
||||
}
|
||||
emit(CHOOSE_EVENT, selectData)
|
||||
if (props.poppable)
|
||||
emit('update')
|
||||
}
|
||||
}
|
||||
|
||||
// 选中数据
|
||||
function chooseDay(day: Day, month: MonthInfo, isFirst = false) {
|
||||
if (!getClass(day, month).includes(`${state.dayPrefix}--disabled`)) {
|
||||
const { type } = props
|
||||
const [y, m] = month.curData
|
||||
const days = [...month.curData]
|
||||
days[2] = getNumTwoBit(Number(day.day))
|
||||
days[3] = `${days[0]}-${days[1]}-${days[2]}`
|
||||
days[4] = getWhatDay(+days[0], +days[1], +days[2])
|
||||
if (type === 'multiple') {
|
||||
if (state.currDate?.length > 0) {
|
||||
let hasIndex: number | undefined;
|
||||
(state.currDate as StringArr)?.forEach((item: string, index: number) => {
|
||||
if (item === days[3])
|
||||
hasIndex = index
|
||||
})
|
||||
if (isFirst) {
|
||||
state.chooseData.push([...days])
|
||||
}
|
||||
else {
|
||||
if (hasIndex !== undefined) {
|
||||
(state.currDate as StringArr).splice(hasIndex, 1)
|
||||
state.chooseData.splice(hasIndex, 1)
|
||||
}
|
||||
else {
|
||||
(state.currDate as StringArr).push(days[3])
|
||||
state.chooseData.push([...days])
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
state.currDate = [days[3]]
|
||||
state.chooseData = [[...days]]
|
||||
}
|
||||
}
|
||||
else if (type === 'range') {
|
||||
const curDataLength = Object.values(state.currDate).length
|
||||
if (curDataLength === 2 || curDataLength === 0) {
|
||||
state.currDate = [days[3]]
|
||||
}
|
||||
else {
|
||||
if (compareDate(state.currDate[0], days[3]))
|
||||
Array.isArray(state.currDate) && state.currDate.push(days[3])
|
||||
else
|
||||
Array.isArray(state.currDate) && state.currDate.unshift(days[3])
|
||||
}
|
||||
|
||||
if (state.chooseData.length === 2 || !state.chooseData.length) {
|
||||
state.chooseData = [[...days]]
|
||||
}
|
||||
else {
|
||||
if (compareDate(state.chooseData[0][3], days[3]))
|
||||
state.chooseData = [...state.chooseData, [...days]]
|
||||
else
|
||||
state.chooseData = [[...days], ...state.chooseData]
|
||||
}
|
||||
}
|
||||
else if (type === 'week') {
|
||||
const weekArr = getWeekDate(y, m, day.day, props.firstDayOfWeek)
|
||||
if (state.propStartDate && compareDate(weekArr[0], state.propStartDate))
|
||||
weekArr.splice(0, 1, state.propStartDate)
|
||||
|
||||
if (state.propEndDate && compareDate(state.propEndDate, weekArr[1]))
|
||||
weekArr.splice(1, 1, state.propEndDate)
|
||||
|
||||
state.currDate = weekArr
|
||||
state.chooseData = [formatResultDate(weekArr[0]), formatResultDate(weekArr[1])]
|
||||
}
|
||||
else {
|
||||
state.currDate = days[3]
|
||||
state.chooseData = [...days]
|
||||
}
|
||||
|
||||
if (!isFirst) {
|
||||
let selectData: any = state.chooseData
|
||||
if (type === 'week') {
|
||||
selectData = {
|
||||
weekDate: [
|
||||
handleWeekDate(state.chooseData[0] as string[]),
|
||||
handleWeekDate(state.chooseData[1] as string[]),
|
||||
],
|
||||
}
|
||||
}
|
||||
// 点击日期 触发
|
||||
emit(SELECT_EVENT, selectData)
|
||||
if (props.isAutoBackFill || !props.poppable)
|
||||
confirm()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleWeekDate(weekDate: string[]) {
|
||||
const [y, m, d] = weekDate
|
||||
|
||||
return {
|
||||
date: weekDate,
|
||||
monthWeekNum: getMonthWeek(y, m, d, props.firstDayOfWeek),
|
||||
yearWeekNum: getYearWeek(y, m, d),
|
||||
}
|
||||
}
|
||||
|
||||
// 获取当前月数据
|
||||
function getCurrData(type: string) {
|
||||
const monthData = type === 'prev' ? state.monthsData[0] : state.monthsData[state.monthsData.length - 1]
|
||||
let year = Number.parseInt(monthData.curData[0])
|
||||
let month = Number.parseInt(monthData.curData[1].toString().replace(/^0/, ''))
|
||||
switch (type) {
|
||||
case 'prev':
|
||||
month === 1 && (year -= 1)
|
||||
month = month === 1 ? 12 : --month
|
||||
break
|
||||
case 'next':
|
||||
month === 12 && (year += 1)
|
||||
month = month === 12 ? 1 : ++month
|
||||
break
|
||||
}
|
||||
return [`${year}`, getNumTwoBit(month), `${getMonthDays(String(year), String(month))}`]
|
||||
}
|
||||
|
||||
// 获取日期状态
|
||||
function getDaysStatus(days: number, type: string, dateInfo: CalendarDateProp) {
|
||||
// 修复:当某个月的1号是周日时,月份下方会空出来一行
|
||||
const { year, month } = dateInfo
|
||||
if (type === 'prev' && days >= 7)
|
||||
days -= 7
|
||||
|
||||
return Array.from(Array.from({ length: days }), (v, k) => {
|
||||
return {
|
||||
day: String(k + 1),
|
||||
type,
|
||||
year,
|
||||
month,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 获取上一个月的最后一周天数,填充当月空白
|
||||
function getPreDaysStatus(days: number, type: string, dateInfo: CalendarDateProp, preCurrMonthDays: number) {
|
||||
// 新增:自定义周起始日
|
||||
days = days - props.firstDayOfWeek
|
||||
// 修复:当某个月的1号是周日时,月份下方会空出来一行
|
||||
const { year, month } = dateInfo
|
||||
if (type === 'prev' && days >= 7)
|
||||
days -= 7
|
||||
|
||||
const months = Array.from(Array.from({ length: preCurrMonthDays }), (v, k) => {
|
||||
return {
|
||||
day: String(k + 1),
|
||||
type,
|
||||
year,
|
||||
month,
|
||||
}
|
||||
})
|
||||
return months.slice(preCurrMonthDays - days)
|
||||
}
|
||||
|
||||
// 获取月数据
|
||||
function getMonth(curData: string[], type: string) {
|
||||
// 一号为周几
|
||||
const preMonthDays = getMonthPreDay(+curData[0], +curData[1])
|
||||
|
||||
let preMonth = Number(curData[1]) - 1
|
||||
let preYear = Number(curData[0])
|
||||
if (preMonth <= 0) {
|
||||
preMonth = 12
|
||||
preYear += 1
|
||||
}
|
||||
// 当月天数与上个月天数
|
||||
const currMonthDays = getMonthDays(String(curData[0]), String(curData[1]))
|
||||
const preCurrMonthDays = getMonthDays(`${preYear}`, `${preMonth}`)
|
||||
|
||||
const title = {
|
||||
year: curData[0],
|
||||
month: curData[1],
|
||||
}
|
||||
const monthInfo: MonthInfo = {
|
||||
curData,
|
||||
title: translate('monthTitle', title.year, title.month),
|
||||
monthData: [
|
||||
...(getPreDaysStatus(
|
||||
preMonthDays,
|
||||
'prev',
|
||||
{ month: String(preMonth), year: String(preYear) },
|
||||
preCurrMonthDays,
|
||||
) as Day[]),
|
||||
...(getDaysStatus(currMonthDays, 'curr', title) as Day[]),
|
||||
],
|
||||
cssHeight: 0,
|
||||
cssScrollHeight: 0,
|
||||
}
|
||||
let titleHeight, itemHeight
|
||||
if (isH5) {
|
||||
titleHeight = 46 * scalePx.value + 16 * scalePx.value * 2
|
||||
itemHeight = 128 * scalePx.value
|
||||
}
|
||||
else {
|
||||
titleHeight = Math.floor(46 * scalePx.value) + Math.floor(16 * scalePx.value) * 2
|
||||
itemHeight = Math.floor(128 * scalePx.value)
|
||||
}
|
||||
monthInfo.cssHeight = titleHeight + (monthInfo.monthData.length > 35 ? itemHeight * 6 : itemHeight * 5)
|
||||
|
||||
let cssScrollHeight = 0
|
||||
|
||||
if (state.monthsData.length > 0) {
|
||||
cssScrollHeight
|
||||
= (state.monthsData[state.monthsData.length - 1] as MonthInfo).cssScrollHeight
|
||||
+ (state.monthsData[state.monthsData.length - 1] as MonthInfo).cssHeight
|
||||
}
|
||||
monthInfo.cssScrollHeight = cssScrollHeight
|
||||
if (type === 'next') {
|
||||
// 判断当前日期 是否大于 最后一天
|
||||
if (
|
||||
!state.endData
|
||||
|| !compareDate(
|
||||
`${state.endData[0]}-${state.endData[1]}-${getMonthDays(state.endData[0], state.endData[1])}`,
|
||||
`${curData[0]}-${curData[1]}-${curData[2]}`,
|
||||
)
|
||||
) {
|
||||
state.monthsData.push(monthInfo)
|
||||
}
|
||||
}
|
||||
else {
|
||||
// 判断当前日期 是否小于 第一天
|
||||
if (
|
||||
!state.startData
|
||||
|| !compareDate(
|
||||
`${curData[0]}-${curData[1]}-${curData[2]}`,
|
||||
`${state.startData[0]}-${state.startData[1]}-01`,
|
||||
)
|
||||
) {
|
||||
state.monthsData.unshift(monthInfo)
|
||||
}
|
||||
else {
|
||||
state.unLoadPrev = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化数据
|
||||
function initData() {
|
||||
// 初始化开始结束数据
|
||||
const propStartDate = props.startDate ? props.startDate : getDay(0)
|
||||
const propEndDate = props.endDate ? props.endDate : getDay(365)
|
||||
state.propStartDate = propStartDate
|
||||
state.propEndDate = propEndDate
|
||||
state.startData = splitDate(propStartDate)
|
||||
state.endData = splitDate(propEndDate)
|
||||
|
||||
// 根据是否存在默认时间,初始化当前日期,
|
||||
if (props.defaultValue || (Array.isArray(props.defaultValue) && (props.defaultValue as any[]).length > 0)) {
|
||||
state.currDate
|
||||
= props.type !== 'one' ? ([...props.defaultValue] as StringArr) : (props.defaultValue as string | StringArr)
|
||||
}
|
||||
|
||||
// 判断时间范围内存在多少个月
|
||||
const startDate = {
|
||||
year: Number(state.startData[0]),
|
||||
month: Number(state.startData[1]),
|
||||
}
|
||||
const endDate = {
|
||||
year: Number(state.endData[0]),
|
||||
month: Number(state.endData[1]),
|
||||
}
|
||||
let monthsNum = endDate.month - startDate.month
|
||||
if (endDate.year - startDate.year > 0)
|
||||
monthsNum = monthsNum + 12 * (endDate.year - startDate.year)
|
||||
|
||||
if (monthsNum <= 0)
|
||||
monthsNum = 1
|
||||
|
||||
// 设置月份数据
|
||||
getMonth(state.startData, 'next')
|
||||
|
||||
let i = 1
|
||||
do
|
||||
getMonth(getCurrData('next'), 'next')
|
||||
while (i++ < monthsNum)
|
||||
state.monthsNum = monthsNum
|
||||
|
||||
// 日期转化为数组,限制初始日期。判断时间范围
|
||||
if (props.type === 'range' && Array.isArray(state.currDate)) {
|
||||
if (state.currDate.length > 0) {
|
||||
if (propStartDate && compareDate(state.currDate[0], propStartDate))
|
||||
state.currDate.splice(0, 1, propStartDate)
|
||||
|
||||
if (propEndDate && compareDate(propEndDate, state.currDate[1]))
|
||||
state.currDate.splice(1, 1, propEndDate)
|
||||
|
||||
state.defaultData = [...splitDate(state.currDate[0]), ...splitDate(state.currDate[1])]
|
||||
}
|
||||
}
|
||||
else if (props.type === 'multiple' && Array.isArray(state.currDate)) {
|
||||
if (state.currDate.length > 0) {
|
||||
const defaultArr: string[] = []
|
||||
const obj: any = {}
|
||||
state.currDate.forEach((item: string) => {
|
||||
if (
|
||||
propStartDate
|
||||
&& !compareDate(item, propStartDate)
|
||||
&& propEndDate
|
||||
&& !compareDate(propEndDate, item)
|
||||
) {
|
||||
if (!Object.hasOwnProperty.call(obj, item)) {
|
||||
defaultArr.push(item)
|
||||
obj[item] = item
|
||||
}
|
||||
}
|
||||
})
|
||||
state.currDate = [...defaultArr]
|
||||
state.defaultData = [...splitDate(defaultArr[0])]
|
||||
}
|
||||
}
|
||||
else if (props.type === 'week' && Array.isArray(state.currDate)) {
|
||||
if (state.currDate.length > 0) {
|
||||
const [y, m, d] = splitDate(state.currDate[0])
|
||||
|
||||
state.currDate = getWeekDate(y, m, d, props.firstDayOfWeek)
|
||||
|
||||
if (propStartDate && compareDate(state.currDate[0], propStartDate))
|
||||
state.currDate.splice(0, 1, propStartDate)
|
||||
|
||||
if (propEndDate && compareDate(propEndDate, state.currDate[1]))
|
||||
state.currDate.splice(1, 1, propEndDate)
|
||||
|
||||
state.defaultData = [...splitDate(state.currDate[0]), ...splitDate(state.currDate[1])]
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (state.currDate) {
|
||||
if (propStartDate && compareDate(state.currDate as string, propStartDate))
|
||||
state.currDate = propStartDate
|
||||
else if (propEndDate && !compareDate(state.currDate as string, propEndDate))
|
||||
state.currDate = propEndDate
|
||||
|
||||
state.defaultData = [...splitDate(state.currDate as string)]
|
||||
}
|
||||
}
|
||||
// 设置默认可见区域
|
||||
let current = 0
|
||||
let lastCurrent = 0
|
||||
if (state.defaultData.length > 0) {
|
||||
state.monthsData.forEach((item, index) => {
|
||||
if (item.title === translate('monthTitle', state.defaultData[0], state.defaultData[1]))
|
||||
current = index
|
||||
|
||||
if (props.type === 'range' || props.type === 'week') {
|
||||
if (item.title === translate('monthTitle', state.defaultData[3], state.defaultData[4]))
|
||||
lastCurrent = index
|
||||
}
|
||||
})
|
||||
}
|
||||
setDefaultRange(monthsNum, current)
|
||||
state.currentIndex = current
|
||||
state.yearMonthTitle = state.monthsData[state.currentIndex].title
|
||||
if (state.defaultData.length > 0) {
|
||||
// 设置当前选中日期
|
||||
if (state.isRange) {
|
||||
chooseDay({ day: state.defaultData[2], type: 'curr' }, state.monthsData[state.currentIndex], true)
|
||||
chooseDay({ day: state.defaultData[5], type: 'curr' }, state.monthsData[lastCurrent], true)
|
||||
}
|
||||
else if (props.type === 'week') {
|
||||
chooseDay({ day: state.defaultData[2], type: 'curr' }, state.monthsData[state.currentIndex], true)
|
||||
}
|
||||
else if (props.type === 'multiple') {
|
||||
[...state.currDate].forEach((item: string) => {
|
||||
const dateArr = splitDate(item)
|
||||
let current = state.currentIndex
|
||||
state.monthsData.forEach((item, index) => {
|
||||
if (item.title === translate('monthTitle', dateArr[0], dateArr[1]))
|
||||
current = index
|
||||
})
|
||||
chooseDay({ day: dateArr[2], type: 'curr' }, state.monthsData[current], true)
|
||||
})
|
||||
}
|
||||
else {
|
||||
chooseDay({ day: state.defaultData[2], type: 'curr' }, state.monthsData[state.currentIndex], true)
|
||||
}
|
||||
}
|
||||
|
||||
const lastItem = state.monthsData[state.monthsData.length - 1]
|
||||
const containerHeight = lastItem.cssHeight + lastItem.cssScrollHeight
|
||||
|
||||
state.containerHeight = `${containerHeight}px`
|
||||
state.scrollTop = Math.ceil(state.monthsData[state.currentIndex].cssScrollHeight)
|
||||
state.avgHeight = Math.floor(containerHeight / (monthsNum + 1))
|
||||
|
||||
if (months?.value)
|
||||
viewHeight.value = months.value.clientHeight
|
||||
}
|
||||
|
||||
function scrollToDate(date: string) {
|
||||
if (compareDate(date, state.propStartDate))
|
||||
date = state.propStartDate
|
||||
else if (!compareDate(date, state.propEndDate))
|
||||
date = state.propEndDate
|
||||
|
||||
const dateArr = splitDate(date)
|
||||
|
||||
state.monthsData.forEach((item, index) => {
|
||||
if (item.title === translate('monthTitle', dateArr[0], dateArr[1])) {
|
||||
// scrollTop 不会实时变更。当再次赋值时,scrollTop无变化时,不会触发滚动
|
||||
state.scrollTop += 1
|
||||
scrollWithAnimation.value = props.toDateAnimation
|
||||
requestAniFrame(() => {
|
||||
setTimeout(() => {
|
||||
state.scrollTop = state.monthsData[index].cssScrollHeight
|
||||
|
||||
setTimeout(() => {
|
||||
scrollWithAnimation.value = false
|
||||
}, 200)
|
||||
}, 10)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function initPosition() {
|
||||
state.scrollTop = Math.ceil(state.monthsData[state.currentIndex].cssScrollHeight)
|
||||
}
|
||||
|
||||
// 设置当前可见月份
|
||||
function setDefaultRange(monthsNum: number, current: number) {
|
||||
if (monthsNum >= 3) {
|
||||
if (current > 0 && current < monthsNum)
|
||||
state.defaultRange = [current - 1, current + 3]
|
||||
|
||||
else if (current === 0)
|
||||
state.defaultRange = [current, current + 4]
|
||||
|
||||
else if (current === monthsNum)
|
||||
state.defaultRange = [current - 2, current + 2]
|
||||
}
|
||||
else {
|
||||
state.defaultRange = [0, monthsNum + 2]
|
||||
}
|
||||
|
||||
state.translateY = state.monthsData[state.defaultRange[0]].cssScrollHeight
|
||||
}
|
||||
|
||||
// 区间选择&&当前月&&选中态
|
||||
function isActive(day: Day, month: MonthInfo) {
|
||||
return (
|
||||
(props.type === 'range' || props.type === 'week')
|
||||
&& day.type === 'curr'
|
||||
&& getClass(day, month).includes('nut-calendar__day--active')
|
||||
)
|
||||
}
|
||||
|
||||
// 是否有开始提示
|
||||
function isStartTip(day: Day, month: MonthInfo) {
|
||||
return isActive(day, month) && isStart(getCurrDate(day, month))
|
||||
}
|
||||
|
||||
// 是否有结束提示
|
||||
function isEndTip(day: Day, month: MonthInfo) {
|
||||
if (state.currDate.length >= 2 && isEnd(getCurrDate(day, month)))
|
||||
return isActive(day, month)
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// 开始结束时间是否相等
|
||||
function rangeTip() {
|
||||
if (state.currDate.length >= 2)
|
||||
return isEqual(state.currDate[0], state.currDate[1])
|
||||
}
|
||||
|
||||
// 是否有 当前日期
|
||||
function isCurrDay(dateInfo: Day) {
|
||||
const date = `${dateInfo.year}-${dateInfo.month}-${Number(dateInfo.day) < 10 ? `0${dateInfo.day}` : dateInfo.day}`
|
||||
return isEqual(date, date2Str(new Date()))
|
||||
}
|
||||
|
||||
// 滚动处理事件
|
||||
function mothsViewScroll(e: ScrollViewOnScrollEvent) {
|
||||
if (state.monthsData.length <= 1)
|
||||
return
|
||||
|
||||
const currentScrollTop = e.detail.scrollTop
|
||||
let current = Math.floor(currentScrollTop / state.avgHeight)
|
||||
if (current === 0) {
|
||||
if (currentScrollTop >= state.monthsData[current + 1].cssScrollHeight)
|
||||
current += 1
|
||||
}
|
||||
else if (current > 0 && current < state.monthsNum - 1) {
|
||||
if (currentScrollTop >= state.monthsData[current + 1].cssScrollHeight)
|
||||
current += 1
|
||||
|
||||
if (currentScrollTop < state.monthsData[current].cssScrollHeight)
|
||||
current -= 1
|
||||
}
|
||||
if (state.currentIndex !== current) {
|
||||
state.currentIndex = current
|
||||
setDefaultRange(state.monthsNum, current)
|
||||
}
|
||||
state.yearMonthTitle = state.monthsData[current].title
|
||||
}
|
||||
|
||||
// 重新渲染
|
||||
function resetRender() {
|
||||
state.chooseData.splice(0)
|
||||
state.monthsData.splice(0)
|
||||
initData()
|
||||
}
|
||||
|
||||
// 监听 默认值更改
|
||||
watch(() => props.defaultValue, (value) => {
|
||||
if (value) {
|
||||
if (props.poppable) {
|
||||
resetRender()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
// 初始化数据
|
||||
uni.getSystemInfo({
|
||||
success(res) {
|
||||
let scale = 2
|
||||
let toFixed = 3
|
||||
if (isH5) {
|
||||
toFixed = 5
|
||||
const fontSize = document.documentElement.style.fontSize
|
||||
scale = Number((Number.parseInt(fontSize) / 40).toFixed(toFixed))
|
||||
}
|
||||
else {
|
||||
const screenWidth = res.screenWidth
|
||||
scale = Number((screenWidth / 750).toFixed(toFixed))
|
||||
}
|
||||
scalePx.value = scale
|
||||
initData()
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
scrollToDate,
|
||||
initPosition,
|
||||
})
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default defineComponent({
|
||||
name: `${PREFIX}-calendar-item`,
|
||||
options: {
|
||||
virtualHost: true,
|
||||
addGlobalClass: true,
|
||||
styleIsolation: 'shared',
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view :class="classes" :style="props.customStyle">
|
||||
<view class="nut-calendar__header">
|
||||
<view v-if="props.showTitle" class="nut-calendar__header-title">
|
||||
{{ props.title || translate('title') }}
|
||||
</view>
|
||||
|
||||
<view v-if="props.btnSlot" class="nut-calendar__header-slot">
|
||||
<slot name="btn" />
|
||||
</view>
|
||||
|
||||
<view v-if="props.showSubTitle" class="nut-calendar__header-subtitle">
|
||||
{{ state.yearMonthTitle }}
|
||||
</view>
|
||||
|
||||
<view class="nut-calendar__weekdays">
|
||||
<view
|
||||
v-for="(item, index) of weeks"
|
||||
:key="index"
|
||||
class="nut-calendar__weekday"
|
||||
:class="{ weekend: item.weekend }"
|
||||
>
|
||||
{{ item.day }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<scroll-view
|
||||
ref="months"
|
||||
class="nut-calendar__content"
|
||||
:scroll-y="true"
|
||||
:scroll-top="state.scrollTop"
|
||||
:scroll-with-animation="scrollWithAnimation"
|
||||
@scroll="mothsViewScroll"
|
||||
>
|
||||
<view class="nut-calendar__panel" :style="{ height: state.containerHeight }">
|
||||
<view class="nut-calendar__body" :style="{ transform: `translateY(${state.translateY}px)` }">
|
||||
<view v-for="(month, index) of compConthsData" :key="index" class="nut-calendar__month">
|
||||
<view class="nut-calendar__month-title">
|
||||
{{ month.title }}
|
||||
</view>
|
||||
|
||||
<view class="nut-calendar__days">
|
||||
<view
|
||||
class="nut-calendar__days-item"
|
||||
:class="{ 'nut-calendar__days-item--range': props.type === 'range' }"
|
||||
>
|
||||
<template v-for="(day, i) of month.monthData" :key="i">
|
||||
<view
|
||||
class="nut-calendar__day"
|
||||
:class="getClass(day, month, i)"
|
||||
@click="chooseDay(day, month)"
|
||||
>
|
||||
<!-- 日期显示slot -->
|
||||
<view class="nut-calendar__day-value">
|
||||
<!-- #ifdef MP -->
|
||||
{{ day.type === 'curr' ? day.day : '' }}
|
||||
<!-- #endif -->
|
||||
|
||||
<!-- #ifndef MP -->
|
||||
<slot name="day" :date="day.type === 'curr' ? day : ''">
|
||||
{{ day.type === 'curr' ? day.day : '' }}
|
||||
</slot>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
|
||||
<!-- #ifdef H5 -->
|
||||
<view v-if="slots.topInfo" class="nut-calendar__day-tips nut-calendar__day-tips--top">
|
||||
<slot name="topInfo" :date="day.type === 'curr' ? day : ''" />
|
||||
</view>
|
||||
|
||||
<view v-if="slots.bottomInfo" class="nut-calendar__day-tips nut-calendar__day-tips--bottom">
|
||||
<slot name="bottomInfo" :date="day.type === 'curr' ? day : ''" />
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
|
||||
<!-- #ifndef MP -->
|
||||
<view
|
||||
v-if="!slots.bottomInfo && props.showToday && isCurrDay(day)"
|
||||
class="nut-calendar__day-tips--curr"
|
||||
>
|
||||
{{ translate('today') }}
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
|
||||
<!-- #ifdef MP -->
|
||||
<view v-if="props.showToday && isCurrDay(day)" class="nut-calendar__day-tips--curr">
|
||||
{{ translate('today') }}
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
|
||||
<view
|
||||
v-if="isStartTip(day, month)"
|
||||
class="nut-calendar__day-tip"
|
||||
:class="{ 'nut-calendar__day-tips--top': rangeTip() }"
|
||||
>
|
||||
{{ props.startText || translate('start') }}
|
||||
</view>
|
||||
|
||||
<view v-if="isEndTip(day, month)" class="nut-calendar__day-tip">
|
||||
{{ props.endText || translate('end') }}
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<view v-if="props.poppable && !props.isAutoBackFill" class="nut-calendar__footer">
|
||||
<slot v-if="props.footerSlot" name="footer" :date="state.chooseData" />
|
||||
|
||||
<view v-else class="nut-calendar__confirm" @click="confirm">
|
||||
{{ props.confirmText || translate('confirm') }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import "./index";
|
||||
</style>
|
||||
270
uni_modules/nutui-uni/components/calendaritem/index.scss
Normal file
270
uni_modules/nutui-uni/components/calendaritem/index.scss
Normal file
@@ -0,0 +1,270 @@
|
||||
.nut-theme-dark {
|
||||
.nut-calendar {
|
||||
$block: &;
|
||||
|
||||
&-item {
|
||||
color: $dark-color;
|
||||
background: $dark-background;
|
||||
}
|
||||
|
||||
&__header {
|
||||
color: $dark-color;
|
||||
background: $dark-background;
|
||||
}
|
||||
|
||||
&__content {
|
||||
#{$block}__panel {
|
||||
#{$block}__days {
|
||||
#{$block}__day {
|
||||
&--disabled {
|
||||
color: $dark-calendar-disabled !important;
|
||||
}
|
||||
}
|
||||
|
||||
.calendar-month-day {
|
||||
&-choose {
|
||||
color: $calendar-choose-font-color;
|
||||
background-color: $dark-calendar-choose-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__footer {
|
||||
color: $dark-color;
|
||||
background: $dark-background2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nut-calendar {
|
||||
$block: &;
|
||||
|
||||
&-item {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
background-color: $white;
|
||||
}
|
||||
|
||||
&#{$block}--nopop {
|
||||
#{$block}__header {
|
||||
#{$block}__header-title {
|
||||
font-size: $calendar-base-font;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.popup-box {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-top: 1px;
|
||||
text-align: center;
|
||||
background-color: $white;
|
||||
|
||||
&-title {
|
||||
font-size: $calendar-title-font;
|
||||
font-weight: $calendar-title-font-weight;
|
||||
line-height: 44px;
|
||||
}
|
||||
|
||||
&-slot {
|
||||
min-height: 24px;
|
||||
}
|
||||
|
||||
&-subtitle {
|
||||
padding: 7px 0;
|
||||
font-size: $calendar-sub-title-font;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
#{$block}__weekdays {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
height: 36px;
|
||||
border-radius: 0 0 12px 12px;
|
||||
box-shadow: 0 4px 10px 0 rgba($color: #000, $alpha: 6%);
|
||||
|
||||
#{$block}__weekday {
|
||||
&.weekend {
|
||||
color: $calendar-day67-font-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__content {
|
||||
display: block;
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
overflow-y: auto;
|
||||
|
||||
#{$block}__panel {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
|
||||
#{$block}__body {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#{$block}__month {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
view:nth-of-type(2) {
|
||||
#{$block}__month-title {
|
||||
padding-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.calendar-loading-tip {
|
||||
position: absolute;
|
||||
top: -50px;
|
||||
right: 0;
|
||||
left: 0;
|
||||
height: 50px;
|
||||
font-size: $calendar-text-font;
|
||||
line-height: 50px;
|
||||
color: $text-color;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#{$block}__month-title {
|
||||
height: 23px;
|
||||
margin: 8px 0;
|
||||
font-size: $calendar-month-title-font-size;
|
||||
line-height: 23px;
|
||||
}
|
||||
|
||||
#{$block}__days {
|
||||
overflow: hidden;
|
||||
|
||||
#{$block}__day {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
float: left;
|
||||
width: 14.28%;
|
||||
height: 64px;
|
||||
font-weight: $calendar-day-font-weight;
|
||||
|
||||
&.weekend {
|
||||
color: $calendar-day67-font-color;
|
||||
}
|
||||
|
||||
#{$block}__day-tips {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#{$block}__day-tips--curr {
|
||||
position: absolute;
|
||||
bottom: 6px;
|
||||
width: 100%;
|
||||
font-size: 12px;
|
||||
line-height: 14px;
|
||||
}
|
||||
|
||||
#{$block}__day-tip {
|
||||
position: absolute;
|
||||
bottom: 6px;
|
||||
width: 100%;
|
||||
font-size: 12px;
|
||||
line-height: 14px;
|
||||
color: $calendar-primary-color;
|
||||
}
|
||||
|
||||
#{$block}__day-tips--top {
|
||||
top: 6px;
|
||||
}
|
||||
|
||||
#{$block}__day-tips--bottom {
|
||||
bottom: 6px;
|
||||
}
|
||||
|
||||
&--active {
|
||||
color: $white !important;
|
||||
background-color: $calendar-primary-color;
|
||||
border-radius: $calendar-day-active-border-radius;
|
||||
|
||||
#{$block}__day-tips {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
#{$block}__day-tips--curr {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
#{$block}__day-tip {
|
||||
color: $white;
|
||||
}
|
||||
}
|
||||
|
||||
&--disabled {
|
||||
color: $calendar-disable-color !important;
|
||||
}
|
||||
|
||||
&--choose {
|
||||
color: $calendar-choose-font-color;
|
||||
|
||||
&::after {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
content: "";
|
||||
background-color: $calendar-choose-color;
|
||||
opacity: 0.09;
|
||||
}
|
||||
}
|
||||
|
||||
#{$block}__day-value {
|
||||
padding: 2px 0;
|
||||
font-size: $calendar-day-font;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__footer {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 62px;
|
||||
background-color: $white;
|
||||
|
||||
#{$block}__confirm {
|
||||
width: 100%;
|
||||
height: 44px;
|
||||
margin: 10px 18px;
|
||||
line-height: 44px;
|
||||
color: $white;
|
||||
text-align: center;
|
||||
background: $button-primary-background-color;
|
||||
border-radius: 22px;
|
||||
}
|
||||
}
|
||||
}
|
||||
1
uni_modules/nutui-uni/components/calendaritem/index.ts
Normal file
1
uni_modules/nutui-uni/components/calendaritem/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './calendaritem'
|
||||
65
uni_modules/nutui-uni/components/calendaritem/types.ts
Normal file
65
uni_modules/nutui-uni/components/calendaritem/types.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
export interface TouchParam {
|
||||
startY: number
|
||||
endY: number
|
||||
startTime: number
|
||||
endTime: number
|
||||
lastY: number
|
||||
lastTime: number
|
||||
}
|
||||
|
||||
export type InputDate = string | string[]
|
||||
export type StringArr = string[]
|
||||
|
||||
export interface CalendarState {
|
||||
yearMonthTitle: string
|
||||
currDate: string | string[]
|
||||
propStartDate: string
|
||||
propEndDate: string
|
||||
currentIndex: number
|
||||
unLoadPrev: boolean
|
||||
touchParams: TouchParam
|
||||
transformY: number
|
||||
translateY: number
|
||||
scrollDistance: number
|
||||
defaultData: InputDate
|
||||
chooseData: (string | string[])[]
|
||||
monthsData: MonthInfo[]
|
||||
dayPrefix: string
|
||||
startData: InputDate
|
||||
endData: InputDate
|
||||
isRange: boolean
|
||||
timer: number
|
||||
avgHeight: number
|
||||
monthsNum: number
|
||||
defaultRange: number[]
|
||||
}
|
||||
|
||||
export interface CalendarTaroState extends CalendarState {
|
||||
scrollTop: number
|
||||
containerHeight: string
|
||||
}
|
||||
|
||||
export interface CalendarDateProp {
|
||||
year: string
|
||||
month: string
|
||||
}
|
||||
|
||||
export interface Day {
|
||||
day: string
|
||||
type: string
|
||||
year?: string
|
||||
month?: string
|
||||
}
|
||||
|
||||
export interface MonthInfo {
|
||||
curData: string[] | string
|
||||
title: string
|
||||
monthData: Day[]
|
||||
cssHeight: number
|
||||
cssScrollHeight: number
|
||||
}
|
||||
|
||||
export interface CalendarInst extends HTMLElement {
|
||||
scrollToDate: (date: string) => void
|
||||
initPosition: () => void
|
||||
}
|
||||
47
uni_modules/nutui-uni/components/card/card.ts
Normal file
47
uni_modules/nutui-uni/components/card/card.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import type { ExtractPropTypes } from 'vue'
|
||||
import { commonProps, makeStringProp, truthProp } from '../_utils'
|
||||
|
||||
export const cardProps = {
|
||||
...commonProps,
|
||||
/**
|
||||
* @description 左侧图片 `Url`
|
||||
*/
|
||||
imgUrl: makeStringProp(''),
|
||||
|
||||
/**
|
||||
* @description 标题
|
||||
*/
|
||||
title: makeStringProp(''),
|
||||
|
||||
/**
|
||||
* @description 商品价格
|
||||
*/
|
||||
price: makeStringProp(''),
|
||||
|
||||
/**
|
||||
* @description 会员价格
|
||||
*/
|
||||
vipPrice: makeStringProp(''),
|
||||
|
||||
/**
|
||||
* @description 店铺介绍
|
||||
*/
|
||||
shopDesc: makeStringProp(''),
|
||||
|
||||
/**
|
||||
* @description 配送方式
|
||||
*/
|
||||
delivery: makeStringProp(''),
|
||||
|
||||
/**
|
||||
* @description 店铺名称
|
||||
*/
|
||||
shopName: makeStringProp(''),
|
||||
|
||||
/**
|
||||
* @description 是否需要价格展示
|
||||
*/
|
||||
isNeedPrice: truthProp,
|
||||
}
|
||||
|
||||
export type CardProps = ExtractPropTypes<typeof cardProps>
|
||||
72
uni_modules/nutui-uni/components/card/card.vue
Normal file
72
uni_modules/nutui-uni/components/card/card.vue
Normal file
@@ -0,0 +1,72 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, defineComponent } from 'vue'
|
||||
import { PREFIX } from '../_constants'
|
||||
import { getMainClass } from '../_utils'
|
||||
import NutPrice from '../price/price.vue'
|
||||
import NutTag from '../tag/tag.vue'
|
||||
import { cardProps } from './card'
|
||||
|
||||
const props = defineProps(cardProps)
|
||||
const classes = computed(() => {
|
||||
return getMainClass(props, componentName)
|
||||
})
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
const componentName = `${PREFIX}-card`
|
||||
|
||||
export default defineComponent({
|
||||
name: componentName,
|
||||
options: {
|
||||
virtualHost: true,
|
||||
addGlobalClass: true,
|
||||
styleIsolation: 'shared',
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="classes" :style="customStyle">
|
||||
<div class="nut-card__left">
|
||||
<image :src="imgUrl" alt="" />
|
||||
</div>
|
||||
<div class="nut-card__right">
|
||||
<div class="nut-card__right__title">
|
||||
{{ title }}
|
||||
</div>
|
||||
<slot name="prolist" />
|
||||
<div v-if="isNeedPrice" class="nut-card__right__price">
|
||||
<slot name="price">
|
||||
<NutPrice v-if="price" :price="price" />
|
||||
</slot>
|
||||
<slot name="origin">
|
||||
<view class="nut-card__right__price__origin">
|
||||
<NutPrice v-if="vipPrice" :price="vipPrice" />
|
||||
</view>
|
||||
</slot>
|
||||
</div>
|
||||
<div class="nut-card__right__other">
|
||||
<slot name="shopTag">
|
||||
<NutTag type="danger">
|
||||
{{ shopDesc }}
|
||||
</NutTag>
|
||||
<NutTag plain>
|
||||
{{ delivery }}
|
||||
</NutTag>
|
||||
</slot>
|
||||
</div>
|
||||
<div class="nut-card__right__shop">
|
||||
<slot name="shopName">
|
||||
<div class="nut-card__right__shop__name">
|
||||
{{ shopName }}
|
||||
</div>
|
||||
</slot>
|
||||
<slot name="footer" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import './index';
|
||||
</style>
|
||||
112
uni_modules/nutui-uni/components/card/index.scss
Normal file
112
uni_modules/nutui-uni/components/card/index.scss
Normal file
@@ -0,0 +1,112 @@
|
||||
@import '../price/index';
|
||||
@import '../tag/index';
|
||||
|
||||
.nut-theme-dark {
|
||||
.nut-card {
|
||||
.nut-card__right {
|
||||
color: $dark-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nut-card {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
|
||||
.nut-card__left {
|
||||
flex-shrink: 0;
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
background-color: $card-left-background-color;
|
||||
border-radius: $card-left-border-radius;
|
||||
|
||||
> image {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.nut-card__right {
|
||||
flex: 1;
|
||||
padding: 0 10px 8px;
|
||||
|
||||
.nut-card__right__title {
|
||||
display: -webkit-box;
|
||||
overflow: hidden;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
word-break: break-all;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
}
|
||||
|
||||
.nut-card__right__price {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 18px;
|
||||
margin-top: 9px;
|
||||
line-height: 18px;
|
||||
|
||||
.nut-price {
|
||||
.nut-price--symbol-large {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.nut-price--large {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.nut-price--decimal-large {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.nut-card__right__price__origin {
|
||||
:deep(.nut-price) {
|
||||
margin-left: 2px;
|
||||
color: #d2a448;
|
||||
|
||||
.nut-price--symbol-large {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.nut-price--large {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.nut-price--decimal-large {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.nut-card__right__other {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 5px 0 2px;
|
||||
|
||||
.nut-tag {
|
||||
padding: 0 2px;
|
||||
margin-right: 5px;
|
||||
font-size: $card-font-size-0;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
.nut-card__right__shop {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-top: 4px;
|
||||
|
||||
.nut-card__right__shop__name {
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1
uni_modules/nutui-uni/components/card/index.ts
Normal file
1
uni_modules/nutui-uni/components/card/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './card'
|
||||
106
uni_modules/nutui-uni/components/cascader/cascader.ts
Normal file
106
uni_modules/nutui-uni/components/cascader/cascader.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import type { ExtractPropTypes, PropType, StyleValue } from 'vue'
|
||||
import {
|
||||
CHANGE_EVENT,
|
||||
CLOSE_EVENT,
|
||||
CLOSED_EVENT,
|
||||
OPEN_EVENT,
|
||||
OPENED_EVENT,
|
||||
UPDATE_MODEL_EVENT,
|
||||
UPDATE_VISIBLE_EVENT,
|
||||
} from '../_constants'
|
||||
import { commonProps, makeArrayProp, makeNumericProp, makeStringProp, truthProp } from '../_utils'
|
||||
import { popupProps } from '../popup/popup'
|
||||
import type { CascaderOption, CascaderValue } from './types'
|
||||
|
||||
export const cascaderProps = {
|
||||
...popupProps,
|
||||
...commonProps,
|
||||
/**
|
||||
* @description 选中值,双向绑定
|
||||
*/
|
||||
modelValue: Array,
|
||||
/**
|
||||
* @description 显示选择层
|
||||
*/
|
||||
visible: Boolean,
|
||||
/**
|
||||
* @description 级联数据
|
||||
*/
|
||||
options: makeArrayProp<any>([]),
|
||||
/**
|
||||
* @description 是否开启动态加载
|
||||
*/
|
||||
lazy: Boolean,
|
||||
/**
|
||||
* @description 动态加载回调,开启动态加载时生效
|
||||
*/
|
||||
lazyLoad: Function,
|
||||
/**
|
||||
* @description 自定义 `options` 结构中 `value` 的字段
|
||||
*/
|
||||
valueKey: makeStringProp('value'),
|
||||
/**
|
||||
* @description 自定义 `options` 结构中 `text` 的字段
|
||||
*/
|
||||
textKey: makeStringProp('text'),
|
||||
/**
|
||||
* @description 自定义 `options` 结构中 `children` 的字段
|
||||
*/
|
||||
childrenKey: makeStringProp('children'),
|
||||
/**
|
||||
* @description 当 `options` 为可转换为树形结构的扁平结构时,配置转换规则
|
||||
*/
|
||||
convertConfig: Object,
|
||||
/**
|
||||
* @description 是否需要弹层展示(设置为 `false` 后,`title` 失效)
|
||||
*/
|
||||
poppable: truthProp,
|
||||
/**
|
||||
* @description 标题
|
||||
*/
|
||||
title: String,
|
||||
/**
|
||||
* @description 选中底部展示样式 可选值: 'line', 'smile'
|
||||
*/
|
||||
titleType: makeStringProp<'line' | 'card' | 'smile'>('line'),
|
||||
/**
|
||||
* @description 标签栏字体尺寸大小 可选值: 'large', 'normal', 'small'
|
||||
*/
|
||||
titleSize: makeStringProp<'large' | 'normal' | 'small'>('normal'),
|
||||
/**
|
||||
* @description 标签间隙
|
||||
*/
|
||||
titleGutter: makeNumericProp(0),
|
||||
/**
|
||||
* @description 是否省略过长的标题文字
|
||||
*/
|
||||
titleEllipsis: truthProp,
|
||||
/**
|
||||
* @description 自定义弹窗样式
|
||||
*/
|
||||
popStyle: {
|
||||
type: [String, Object, Array] as PropType<StyleValue>,
|
||||
default: '',
|
||||
},
|
||||
/**
|
||||
* @description 遮罩显示时的背景是否锁定
|
||||
*/
|
||||
lockScroll: truthProp,
|
||||
}
|
||||
|
||||
export type CascaderProps = ExtractPropTypes<typeof cascaderProps>
|
||||
|
||||
/* eslint-disable unused-imports/no-unused-vars */
|
||||
export const cascaderEmits = {
|
||||
[UPDATE_MODEL_EVENT]: (value: CascaderValue) => true,
|
||||
[UPDATE_VISIBLE_EVENT]: (value: boolean) => true,
|
||||
[CHANGE_EVENT]: (value: CascaderValue, nodes: CascaderOption[]) => true,
|
||||
pathChange: (nodes: CascaderOption[]) => true,
|
||||
[OPEN_EVENT]: () => true,
|
||||
[OPENED_EVENT]: () => true,
|
||||
[CLOSE_EVENT]: () => true,
|
||||
[CLOSED_EVENT]: () => true,
|
||||
}
|
||||
/* eslint-enable unused-imports/no-unused-vars */
|
||||
|
||||
export type CascaderEmits = typeof cascaderEmits
|
||||
171
uni_modules/nutui-uni/components/cascader/cascader.vue
Normal file
171
uni_modules/nutui-uni/components/cascader/cascader.vue
Normal file
@@ -0,0 +1,171 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, defineComponent, ref, useSlots, watch } from 'vue'
|
||||
import {
|
||||
CHANGE_EVENT,
|
||||
CLOSE_EVENT,
|
||||
CLOSED_EVENT,
|
||||
OPEN_EVENT,
|
||||
OPENED_EVENT,
|
||||
PREFIX,
|
||||
UPDATE_MODEL_EVENT,
|
||||
UPDATE_VISIBLE_EVENT,
|
||||
} from '../_constants'
|
||||
import { getMainClass } from '../_utils'
|
||||
import NutCascaderItem from '../cascaderitem/cascaderitem.vue'
|
||||
import NutPopup from '../popup/popup.vue'
|
||||
import { cascaderEmits, cascaderProps } from './cascader'
|
||||
import type { CascaderOption, CascaderValue } from './types'
|
||||
|
||||
const props = defineProps(cascaderProps)
|
||||
|
||||
const emit = defineEmits(cascaderEmits)
|
||||
|
||||
const slots = useSlots()
|
||||
|
||||
const innerValue = ref(props.modelValue as CascaderValue)
|
||||
|
||||
const innerVisible = computed({
|
||||
get() {
|
||||
return props.visible
|
||||
},
|
||||
set(value) {
|
||||
emit(UPDATE_VISIBLE_EVENT, value)
|
||||
},
|
||||
})
|
||||
|
||||
const classes = computed(() => {
|
||||
return getMainClass(props, componentName)
|
||||
})
|
||||
|
||||
const popClasses = computed(() => {
|
||||
return `${componentName}__popup ${props.popClass}`
|
||||
})
|
||||
|
||||
const overlayClasses = computed(() => {
|
||||
return `${componentName}__overlay ${props.overlayClass}`
|
||||
})
|
||||
|
||||
function handleChange(value: CascaderValue, pathNodes: CascaderOption[]) {
|
||||
innerValue.value = value
|
||||
innerVisible.value = false
|
||||
|
||||
emit(UPDATE_MODEL_EVENT, value)
|
||||
emit(CHANGE_EVENT, value, pathNodes)
|
||||
}
|
||||
|
||||
function handlePathChange(pathNodes: CascaderOption[]) {
|
||||
emit('pathChange', pathNodes)
|
||||
}
|
||||
|
||||
function handleOpen() {
|
||||
emit(OPEN_EVENT)
|
||||
}
|
||||
|
||||
function handleOpened() {
|
||||
emit(OPENED_EVENT)
|
||||
}
|
||||
|
||||
function handleClose() {
|
||||
emit(CLOSE_EVENT)
|
||||
}
|
||||
|
||||
function handleClosed() {
|
||||
emit(CLOSED_EVENT)
|
||||
}
|
||||
|
||||
watch(() => props.modelValue, (value) => {
|
||||
if (value !== innerValue.value) {
|
||||
innerValue.value = value as CascaderValue
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
const componentName = `${PREFIX}-cascader`
|
||||
|
||||
export default defineComponent({
|
||||
name: componentName,
|
||||
options: {
|
||||
virtualHost: true,
|
||||
addGlobalClass: true,
|
||||
styleIsolation: 'shared',
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view :class="classes" :style="props.customStyle">
|
||||
<template v-if="props.poppable">
|
||||
<NutPopup
|
||||
v-model:visible="innerVisible"
|
||||
:custom-class="popClasses"
|
||||
:custom-style="props.popStyle"
|
||||
:overlay-class="overlayClasses"
|
||||
:overlay-style="props.overlayStyle"
|
||||
position="bottom"
|
||||
round
|
||||
:closeable="props.closeable"
|
||||
:close-icon="props.closeIcon"
|
||||
:close-icon-position="props.closeIconPosition"
|
||||
:z-index="props.zIndex"
|
||||
:lock-scroll="props.lockScroll"
|
||||
:overlay="props.overlay"
|
||||
:close-on-click-overlay="props.closeOnClickOverlay"
|
||||
:destroy-on-close="false"
|
||||
@open="handleOpen"
|
||||
@opened="handleOpened"
|
||||
@close="handleClose"
|
||||
@closed="handleClosed"
|
||||
>
|
||||
<slot v-if="slots.title" name="title" />
|
||||
|
||||
<template v-else>
|
||||
<rich-text v-if="props.title" class="nut-cascader__bar" :nodes="props.title" />
|
||||
</template>
|
||||
|
||||
<NutCascaderItem
|
||||
:model-value="innerValue"
|
||||
:visible="innerVisible"
|
||||
:options="props.options"
|
||||
:lazy="props.lazy"
|
||||
:lazy-load="props.lazyLoad"
|
||||
:value-key="props.valueKey"
|
||||
:text-key="props.textKey"
|
||||
:children-key="props.childrenKey"
|
||||
:convert-config="props.convertConfig"
|
||||
:title-type="props.titleType"
|
||||
:title-size="props.titleSize"
|
||||
:title-gutter="props.titleGutter"
|
||||
:title-ellipsis="props.titleEllipsis"
|
||||
@change="handleChange"
|
||||
@path-change="handlePathChange"
|
||||
/>
|
||||
</NutPopup>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<NutCascaderItem
|
||||
:model-value="innerValue"
|
||||
:visible="innerVisible"
|
||||
:options="props.options"
|
||||
:lazy="props.lazy"
|
||||
:lazy-load="props.lazyLoad"
|
||||
:value-key="props.valueKey"
|
||||
:text-key="props.textKey"
|
||||
:children-key="props.childrenKey"
|
||||
:convert-config="props.convertConfig"
|
||||
:title-type="props.titleType"
|
||||
:title-size="props.titleSize"
|
||||
:title-gutter="props.titleGutter"
|
||||
:title-ellipsis="props.titleEllipsis"
|
||||
@change="handleChange"
|
||||
@path-change="handlePathChange"
|
||||
/>
|
||||
</template>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import "./index";
|
||||
@import "../popup/index";
|
||||
</style>
|
||||
80
uni_modules/nutui-uni/components/cascader/helper.ts
Normal file
80
uni_modules/nutui-uni/components/cascader/helper.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import type { CascaderConfig, CascaderOption, ConvertConfig } from './types'
|
||||
|
||||
export function formatTree(tree: CascaderOption[], parent: CascaderOption | null, config: CascaderConfig): CascaderOption[] {
|
||||
return tree.map((node: CascaderOption) => {
|
||||
const { value: valueKey = 'value', text: textKey = 'text', children: childrenKey = 'children' } = config
|
||||
|
||||
const { [valueKey]: value, [textKey]: text, [childrenKey]: children, ...others } = node
|
||||
|
||||
const newNode: CascaderOption = {
|
||||
loading: false,
|
||||
...others,
|
||||
level: parent ? ((parent && parent.level) || 0) + 1 : 0,
|
||||
value,
|
||||
text,
|
||||
children,
|
||||
_parent: parent,
|
||||
}
|
||||
|
||||
if (newNode.children && newNode.children.length)
|
||||
newNode.children = formatTree(newNode.children, newNode, config)
|
||||
|
||||
return newNode
|
||||
})
|
||||
}
|
||||
|
||||
export function eachTree(tree: CascaderOption[], cb: (node: CascaderOption) => any): void {
|
||||
let i = 0
|
||||
let node: CascaderOption
|
||||
/* eslint-disable no-cond-assign */
|
||||
while ((node = tree[i++])) {
|
||||
if (cb(node) === true)
|
||||
break
|
||||
|
||||
if (node.children && node.children.length)
|
||||
eachTree(node.children, cb)
|
||||
}
|
||||
}
|
||||
|
||||
const defaultConvertConfig = {
|
||||
topId: null,
|
||||
idKey: 'id',
|
||||
pidKey: 'pid',
|
||||
sortKey: '',
|
||||
}
|
||||
|
||||
export function convertListToOptions(list: CascaderOption[], options: ConvertConfig): CascaderOption[] {
|
||||
const mergedOptions = {
|
||||
...defaultConvertConfig,
|
||||
...(options || {}),
|
||||
}
|
||||
|
||||
const { topId, idKey, pidKey, sortKey } = mergedOptions
|
||||
|
||||
let result: CascaderOption[] = []
|
||||
let map: any = {}
|
||||
|
||||
list.forEach((node: CascaderOption) => {
|
||||
node = { ...node }
|
||||
const { [idKey]: id, [pidKey]: pid } = node
|
||||
const children = (map[pid] = map[pid] || [])
|
||||
|
||||
if (!result.length && pid === topId)
|
||||
result = children
|
||||
|
||||
children.push(node)
|
||||
|
||||
node.children = map[id] || (map[id] = [])
|
||||
})
|
||||
|
||||
if (sortKey) {
|
||||
Object.keys(map).forEach((i) => {
|
||||
if (map[i].length > 1)
|
||||
map[i].sort((a: CascaderOption, b: CascaderOption) => a[sortKey] - b[sortKey])
|
||||
})
|
||||
}
|
||||
|
||||
map = null
|
||||
|
||||
return result
|
||||
}
|
||||
110
uni_modules/nutui-uni/components/cascader/index.scss
Normal file
110
uni_modules/nutui-uni/components/cascader/index.scss
Normal file
@@ -0,0 +1,110 @@
|
||||
.nut-theme-dark {
|
||||
.nut-cascader {
|
||||
.nut-tabs__titles {
|
||||
background: $dark-background3 !important;
|
||||
}
|
||||
|
||||
&__bar {
|
||||
color: $dark-color;
|
||||
background: $dark-background2;
|
||||
}
|
||||
|
||||
&-item {
|
||||
&__inner {
|
||||
color: $dark-color-gray;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nut-cascader {
|
||||
.nut-tab-pane {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.nut-tabs__titles {
|
||||
padding: $cascader-tabs-item-padding;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
&-item {
|
||||
width: 100%;
|
||||
font-size: $cascader-font-size;
|
||||
line-height: $cascader-line-height;
|
||||
|
||||
$block: &;
|
||||
|
||||
&.nut-tabs {
|
||||
&.horizontal {
|
||||
.nut-tabs__titles {
|
||||
.nut-tabs__titles-item {
|
||||
flex: initial;
|
||||
padding: $cascader-tabs-item-padding;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: $cascader-item-padding;
|
||||
margin: 0;
|
||||
font-size: $cascader-item-font-size;
|
||||
color: $cascader-item-color;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&__title {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&__icon-check {
|
||||
margin-left: 10px;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
&__icon-loading {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
&.active {
|
||||
&:not(.disabled) {
|
||||
color: $cascader-item-active-color;
|
||||
}
|
||||
|
||||
#{$block}__icon-check {
|
||||
color: $cascader-item-active-color;
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
&__bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: $cascader-bar-padding;
|
||||
font-size: $cascader-bar-font-size;
|
||||
font-weight: bold;
|
||||
line-height: $cascader-bar-line-height;
|
||||
color: $cascader-bar-color;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&-pane {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 342px;
|
||||
padding: 10px 0 0;
|
||||
margin: 0;
|
||||
overflow-y: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
}
|
||||
1
uni_modules/nutui-uni/components/cascader/index.ts
Normal file
1
uni_modules/nutui-uni/components/cascader/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './cascader'
|
||||
73
uni_modules/nutui-uni/components/cascader/tree.ts
Normal file
73
uni_modules/nutui-uni/components/cascader/tree.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import { eachTree, formatTree } from './helper'
|
||||
import type { CascaderConfig, CascaderOption, CascaderValue } from './types'
|
||||
|
||||
class Tree {
|
||||
nodes: CascaderOption[]
|
||||
readonly config: CascaderConfig
|
||||
|
||||
constructor(nodes: CascaderOption[], config?: CascaderConfig) {
|
||||
this.config = {
|
||||
value: 'value',
|
||||
text: 'text',
|
||||
children: 'children',
|
||||
...(config || {}),
|
||||
}
|
||||
this.nodes = formatTree(nodes, null, this.config)
|
||||
}
|
||||
|
||||
updateChildren(nodes: CascaderOption[], parent: CascaderOption | null): void {
|
||||
if (!parent)
|
||||
this.nodes = formatTree(nodes, null, this.config)
|
||||
|
||||
else
|
||||
parent.children = formatTree(nodes, parent, this.config)
|
||||
}
|
||||
|
||||
// for test
|
||||
getNodeByValue(value: CascaderOption['value']): CascaderOption | void {
|
||||
let foundNode
|
||||
eachTree(this.nodes, (node: CascaderOption) => {
|
||||
if (node.value === value) {
|
||||
foundNode = node
|
||||
return true
|
||||
}
|
||||
})
|
||||
|
||||
return foundNode
|
||||
}
|
||||
|
||||
getPathNodesByValue(value: CascaderValue): CascaderOption[] {
|
||||
if (!value.length)
|
||||
return []
|
||||
|
||||
const pathNodes = []
|
||||
let currentNodes: CascaderOption[] | void = this.nodes
|
||||
|
||||
while (currentNodes && currentNodes.length) {
|
||||
const foundNode: CascaderOption | void = currentNodes.find(node => node.value === value[node.level as number])
|
||||
|
||||
if (!foundNode)
|
||||
break
|
||||
|
||||
pathNodes.push(foundNode)
|
||||
currentNodes = foundNode.children
|
||||
}
|
||||
|
||||
return pathNodes
|
||||
}
|
||||
|
||||
isLeaf(node: CascaderOption, lazy: boolean): boolean {
|
||||
const { leaf, children } = node
|
||||
const hasChildren = Array.isArray(children) && Boolean(children.length)
|
||||
|
||||
return leaf == null ? !hasChildren && !lazy : leaf
|
||||
}
|
||||
|
||||
hasChildren(node: CascaderOption, lazy: boolean): boolean {
|
||||
if (lazy)
|
||||
return Array.isArray(node.children) && Boolean(node.children.length)
|
||||
return !this.isLeaf(node, lazy)
|
||||
}
|
||||
}
|
||||
|
||||
export default Tree
|
||||
37
uni_modules/nutui-uni/components/cascader/types.ts
Normal file
37
uni_modules/nutui-uni/components/cascader/types.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
export interface CascaderPane {
|
||||
nodes: CascaderOption[]
|
||||
selectedNode: CascaderOption | null
|
||||
}
|
||||
|
||||
export interface CascaderConfig {
|
||||
value?: string
|
||||
text?: string
|
||||
children?: string
|
||||
}
|
||||
|
||||
export interface CascaderTabs {
|
||||
title: string
|
||||
paneKey: string
|
||||
disabled: boolean
|
||||
}
|
||||
|
||||
export interface CascaderOption {
|
||||
text?: string
|
||||
value?: number | string
|
||||
disabled?: boolean
|
||||
children?: CascaderOption[]
|
||||
leaf?: boolean
|
||||
level?: number
|
||||
loading?: boolean
|
||||
|
||||
[key: PropertyKey]: any
|
||||
}
|
||||
|
||||
export type CascaderValue = CascaderOption['value'][]
|
||||
|
||||
export interface ConvertConfig {
|
||||
topId?: string | number | null
|
||||
idKey?: string
|
||||
pidKey?: string
|
||||
sortKey?: string
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import type { ExtractPropTypes } from 'vue'
|
||||
import { CHANGE_EVENT, UPDATE_MODEL_EVENT } from '../_constants'
|
||||
import { commonProps, makeArrayProp, makeNumericProp, makeStringProp, truthProp } from '../_utils'
|
||||
import type { CascaderOption, CascaderValue } from '../cascader/types'
|
||||
|
||||
export const cascaderitemProps = {
|
||||
...commonProps,
|
||||
/**
|
||||
* @description 选中值,双向绑定
|
||||
*/
|
||||
modelValue: Array,
|
||||
/**
|
||||
* @description 显示选择层
|
||||
*/
|
||||
visible: Boolean,
|
||||
/**
|
||||
* @description 级联数据
|
||||
*/
|
||||
options: makeArrayProp<any>([]),
|
||||
/**
|
||||
* @description 是否开启动态加载
|
||||
*/
|
||||
lazy: Boolean,
|
||||
/**
|
||||
* @description 动态加载回调,开启动态加载时生效
|
||||
*/
|
||||
lazyLoad: Function,
|
||||
/**
|
||||
* @description 自定义 `options` 结构中 `value` 的字段
|
||||
*/
|
||||
valueKey: makeStringProp('value'),
|
||||
/**
|
||||
* @description 自定义 `options` 结构中 `text` 的字段
|
||||
*/
|
||||
textKey: makeStringProp('text'),
|
||||
/**
|
||||
* @description 自定义 `options` 结构中 `children` 的字段
|
||||
*/
|
||||
childrenKey: makeStringProp('children'),
|
||||
/**
|
||||
* @description 当 `options` 为可转换为树形结构的扁平结构时,配置转换规则
|
||||
*/
|
||||
convertConfig: Object,
|
||||
/**
|
||||
* @description 选中底部展示样式 可选值: 'line', 'smile'
|
||||
*/
|
||||
titleType: makeStringProp<'line' | 'card' | 'smile'>('line'),
|
||||
/**
|
||||
* @description 标签栏字体尺寸大小 可选值: 'large', 'normal', 'small'
|
||||
*/
|
||||
titleSize: makeStringProp<'large' | 'normal' | 'small'>('normal'),
|
||||
/**
|
||||
* @description 标签间隙
|
||||
*/
|
||||
titleGutter: makeNumericProp(0),
|
||||
/**
|
||||
* @description 是否省略过长的标题文字
|
||||
*/
|
||||
titleEllipsis: truthProp,
|
||||
}
|
||||
|
||||
export type CascaderItemProps = ExtractPropTypes<typeof cascaderitemProps>
|
||||
|
||||
/* eslint-disable unused-imports/no-unused-vars */
|
||||
export const cascaderitemEmits = {
|
||||
[UPDATE_MODEL_EVENT]: (value: CascaderValue) => true,
|
||||
[CHANGE_EVENT]: (value: CascaderValue, nodes: CascaderOption[]) => true,
|
||||
pathChange: (value: CascaderOption[]) => true,
|
||||
}
|
||||
/* eslint-enable unused-imports/no-unused-vars */
|
||||
|
||||
export type CascaderItemEmits = typeof cascaderitemEmits
|
||||
339
uni_modules/nutui-uni/components/cascaderitem/cascaderitem.vue
Normal file
339
uni_modules/nutui-uni/components/cascaderitem/cascaderitem.vue
Normal file
@@ -0,0 +1,339 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, defineComponent, ref, watch } from 'vue'
|
||||
import { CHANGE_EVENT, PREFIX, UPDATE_MODEL_EVENT } from '../_constants'
|
||||
import { getMainClass } from '../_utils'
|
||||
import { useTranslate } from '../../locale'
|
||||
import { convertListToOptions } from '../cascader/helper'
|
||||
import Tree from '../cascader/tree'
|
||||
import type { CascaderOption, CascaderPane, CascaderTabs, CascaderValue, ConvertConfig } from '../cascader/types'
|
||||
import NutIcon from '../icon/icon.vue'
|
||||
import NutTabPane from '../tabpane/tabpane.vue'
|
||||
import NutTabs from '../tabs/tabs.vue'
|
||||
import { cascaderitemEmits, cascaderitemProps } from './cascaderitem'
|
||||
|
||||
const props = defineProps(cascaderitemProps)
|
||||
|
||||
const emit = defineEmits(cascaderitemEmits)
|
||||
|
||||
const classes = computed(() => {
|
||||
return getMainClass(props, componentName)
|
||||
})
|
||||
|
||||
const configs = computed(() => ({
|
||||
lazy: props.lazy,
|
||||
lazyLoad: props.lazyLoad,
|
||||
valueKey: props.valueKey,
|
||||
textKey: props.textKey,
|
||||
childrenKey: props.childrenKey,
|
||||
convertConfig: props.convertConfig,
|
||||
}))
|
||||
|
||||
const tabsCursor = ref(0)
|
||||
const initLoading = ref(false)
|
||||
|
||||
const innerValue = ref(props.modelValue as CascaderValue)
|
||||
|
||||
const tree = ref(new Tree([], {}))
|
||||
|
||||
const panes = ref<CascaderPane[]>([])
|
||||
|
||||
const isLazy = computed(() => configs.value.lazy && Boolean(configs.value.lazyLoad))
|
||||
const lazyLoadMap = new Map()
|
||||
|
||||
let currentProcessNode: CascaderOption | null
|
||||
|
||||
async function init() {
|
||||
lazyLoadMap.clear()
|
||||
|
||||
panes.value = []
|
||||
tabsCursor.value = 0
|
||||
initLoading.value = false
|
||||
currentProcessNode = null
|
||||
|
||||
let { options } = props
|
||||
|
||||
if (configs.value.convertConfig) {
|
||||
options = convertListToOptions(options as CascaderOption[], configs.value.convertConfig as ConvertConfig)
|
||||
}
|
||||
|
||||
tree.value = new Tree(options as CascaderOption[], {
|
||||
value: configs.value.valueKey,
|
||||
text: configs.value.textKey,
|
||||
children: configs.value.childrenKey,
|
||||
})
|
||||
|
||||
if (isLazy.value && !tree.value.nodes.length) {
|
||||
await invokeLazyLoad({
|
||||
root: true,
|
||||
loading: true,
|
||||
text: '',
|
||||
value: '',
|
||||
})
|
||||
}
|
||||
|
||||
panes.value = [{ nodes: tree.value.nodes, selectedNode: null }]
|
||||
|
||||
syncValue()
|
||||
}
|
||||
|
||||
const methods = {
|
||||
// 选中一个节点,静默模式不触发事件
|
||||
async handleNode(node: CascaderOption, silent?: boolean) {
|
||||
const { disabled, loading } = node
|
||||
|
||||
if ((!silent && disabled) || !panes.value[tabsCursor.value])
|
||||
return
|
||||
|
||||
if (tree.value.isLeaf(node, isLazy.value)) {
|
||||
node.leaf = true
|
||||
panes.value[tabsCursor.value].selectedNode = node
|
||||
panes.value = panes.value.slice(0, (node.level as number) + 1)
|
||||
|
||||
if (!silent) {
|
||||
const pathNodes = panes.value.map(pane => pane.selectedNode)
|
||||
|
||||
emitChange(pathNodes as CascaderOption[])
|
||||
emit('pathChange', pathNodes as CascaderOption[])
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (tree.value.hasChildren(node, isLazy.value)) {
|
||||
const level = (node.level as number) + 1
|
||||
|
||||
panes.value[tabsCursor.value].selectedNode = node
|
||||
panes.value = panes.value.slice(0, level)
|
||||
panes.value.push({
|
||||
nodes: node.children || [],
|
||||
selectedNode: null,
|
||||
})
|
||||
|
||||
tabsCursor.value = level
|
||||
|
||||
if (!silent) {
|
||||
const pathNodes = panes.value.map(pane => pane.selectedNode)
|
||||
emit('pathChange', pathNodes as CascaderOption[])
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
currentProcessNode = node
|
||||
|
||||
if (loading)
|
||||
return
|
||||
|
||||
await invokeLazyLoad(node)
|
||||
|
||||
if (currentProcessNode === node) {
|
||||
panes.value[tabsCursor.value].selectedNode = node
|
||||
methods.handleNode(node, silent)
|
||||
}
|
||||
},
|
||||
handleTabClick(tab: CascaderTabs) {
|
||||
currentProcessNode = null
|
||||
tabsCursor.value = Number(tab.paneKey)
|
||||
},
|
||||
|
||||
isSelected(pane: CascaderPane, node: CascaderOption) {
|
||||
return pane?.selectedNode?.value === node.value
|
||||
},
|
||||
}
|
||||
|
||||
async function syncValue() {
|
||||
const currentValue = innerValue.value
|
||||
|
||||
if (currentValue === undefined || !tree.value.nodes.length) {
|
||||
return
|
||||
}
|
||||
|
||||
if (currentValue.length === 0) {
|
||||
tabsCursor.value = 0
|
||||
panes.value = [{ nodes: tree.value.nodes, selectedNode: null }]
|
||||
return
|
||||
}
|
||||
|
||||
let needToSync = currentValue
|
||||
|
||||
if (isLazy.value && Array.isArray(currentValue) && currentValue.length) {
|
||||
needToSync = []
|
||||
const parent = tree.value.nodes.find(node => node.value === currentValue[0])
|
||||
|
||||
if (parent) {
|
||||
needToSync = [parent.value]
|
||||
initLoading.value = true
|
||||
|
||||
const last = await currentValue.slice(1).reduce(async (p: Promise<CascaderOption | void>, value) => {
|
||||
const parent = await p
|
||||
|
||||
await invokeLazyLoad(parent)
|
||||
const node = parent?.children?.find(item => item.value === value)
|
||||
|
||||
if (node)
|
||||
needToSync.push(value)
|
||||
|
||||
return Promise.resolve(node)
|
||||
}, Promise.resolve(parent))
|
||||
|
||||
await invokeLazyLoad(last)
|
||||
|
||||
initLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
if (needToSync.length && currentValue === props.modelValue) {
|
||||
const pathNodes = tree.value.getPathNodesByValue(needToSync)
|
||||
pathNodes.forEach((node, index) => {
|
||||
tabsCursor.value = index
|
||||
methods.handleNode(node, true)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async function invokeLazyLoad(node?: CascaderOption | void) {
|
||||
if (!node)
|
||||
return
|
||||
|
||||
if (!configs.value.lazyLoad) {
|
||||
node.leaf = true
|
||||
return
|
||||
}
|
||||
|
||||
if (tree.value.isLeaf(node, isLazy.value) || tree.value.hasChildren(node, isLazy.value))
|
||||
return
|
||||
|
||||
node.loading = true
|
||||
|
||||
const parent = node.root ? null : node
|
||||
let lazyLoadPromise = lazyLoadMap.get(node)
|
||||
|
||||
if (!lazyLoadPromise) {
|
||||
lazyLoadPromise = new Promise((resolve) => {
|
||||
// 外部必须resolve
|
||||
configs.value.lazyLoad?.(node, resolve)
|
||||
})
|
||||
lazyLoadMap.set(node, lazyLoadPromise)
|
||||
}
|
||||
|
||||
const nodes: CascaderOption[] | void = await lazyLoadPromise
|
||||
|
||||
if (Array.isArray(nodes) && nodes.length > 0) {
|
||||
tree.value.updateChildren(nodes, parent)
|
||||
}
|
||||
else {
|
||||
// 如果加载完成后没有提供子节点,作为叶子节点处理
|
||||
node.leaf = true
|
||||
}
|
||||
|
||||
node.loading = false
|
||||
lazyLoadMap.delete(node)
|
||||
}
|
||||
|
||||
function emitChange(pathNodes: CascaderOption[]) {
|
||||
const emitValue = pathNodes.map(node => node.value)
|
||||
|
||||
innerValue.value = emitValue
|
||||
|
||||
emit(UPDATE_MODEL_EVENT, emitValue)
|
||||
emit(CHANGE_EVENT, emitValue, pathNodes)
|
||||
}
|
||||
|
||||
function formatTabTitle(pane: CascaderPane) {
|
||||
return pane.selectedNode ? pane.selectedNode.text : translate('select')
|
||||
}
|
||||
|
||||
watch(() => [configs.value, props.options], () => {
|
||||
init()
|
||||
}, {
|
||||
deep: true,
|
||||
immediate: true,
|
||||
})
|
||||
|
||||
watch(() => props.modelValue, (value) => {
|
||||
if (value !== innerValue.value) {
|
||||
innerValue.value = value as CascaderValue
|
||||
|
||||
syncValue()
|
||||
}
|
||||
})
|
||||
|
||||
watch(() => props.visible, (value) => {
|
||||
// TODO: value为空时,保留上次选择记录,修复单元测试问题
|
||||
if (value && Array.isArray(innerValue.value) && innerValue.value.length > 0) {
|
||||
syncValue()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
const componentName = `${PREFIX}-cascader-item`
|
||||
|
||||
const { translate } = useTranslate(componentName)
|
||||
|
||||
export default defineComponent({
|
||||
name: componentName,
|
||||
options: {
|
||||
virtualHost: true,
|
||||
addGlobalClass: true,
|
||||
styleIsolation: 'shared',
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NutTabs
|
||||
v-model="tabsCursor"
|
||||
:custom-class="classes"
|
||||
:custom-style="props.customStyle"
|
||||
:type="props.titleType"
|
||||
:size="props.titleSize"
|
||||
:title-gutter="props.titleGutter"
|
||||
:ellipsis="props.titleEllipsis"
|
||||
title-scroll
|
||||
@click="methods.handleTabClick"
|
||||
>
|
||||
<template v-if="!initLoading && panes.length">
|
||||
<NutTabPane v-for="(pane, index) in panes" :key="index" :title="formatTabTitle(pane)">
|
||||
<view class="nut-cascader-pane" role="menu">
|
||||
<scroll-view style="height: 100%" :scroll-y="true">
|
||||
<template v-for="node in pane.nodes" :key="node.value">
|
||||
<view
|
||||
class="nut-cascader-item__inner"
|
||||
:class="{ active: methods.isSelected(pane, node), disabled: node.disabled }"
|
||||
role="menuitemradio"
|
||||
:aria-checked="methods.isSelected(pane, node)"
|
||||
:aria-disabled="node.disabled || undefined"
|
||||
@click="methods.handleNode(node, false)"
|
||||
>
|
||||
<view class="nut-cascader-item__title">
|
||||
{{ node.text }}
|
||||
</view>
|
||||
|
||||
<NutIcon
|
||||
v-if="node.loading"
|
||||
custom-class="nut-cascader-item__icon-loading"
|
||||
loading
|
||||
name="loading"
|
||||
/>
|
||||
<NutIcon
|
||||
v-else
|
||||
custom-class="nut-cascader-item__icon-check"
|
||||
name="checklist"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</NutTabPane>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<NutTabPane title="Loading...">
|
||||
<view class="nut-cascader-pane" />
|
||||
</NutTabPane>
|
||||
</template>
|
||||
</NutTabs>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import "./index";
|
||||
</style>
|
||||
3
uni_modules/nutui-uni/components/cascaderitem/index.scss
Normal file
3
uni_modules/nutui-uni/components/cascaderitem/index.scss
Normal file
@@ -0,0 +1,3 @@
|
||||
@import "../cascader/index";
|
||||
@import "../tabs/index";
|
||||
@import "../tabpane/index";
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user