这一版本优化了很多
This commit is contained in:
57
main.js
57
main.js
@@ -1,5 +1,62 @@
|
|||||||
import App from './App'
|
import App from './App'
|
||||||
|
|
||||||
|
// 全局拦截选择图片,在文件进入上传列表前进行预过滤,从根本上解决“不合规图片在列表里转圈卡死”的问题
|
||||||
|
uni.addInterceptor('chooseImage', {
|
||||||
|
success(res) {
|
||||||
|
const allowedExtensions = ['bmp', 'gif', 'jpg', 'jpeg', 'png'];
|
||||||
|
const validTempFilePaths = [];
|
||||||
|
const validTempFiles = [];
|
||||||
|
let hasInvalid = false;
|
||||||
|
let invalidExt = '';
|
||||||
|
|
||||||
|
res.tempFiles.forEach((file, index) => {
|
||||||
|
const path = file.path || res.tempFilePaths[index];
|
||||||
|
const cleanPath = path.split('?')[0];
|
||||||
|
const ext = cleanPath.split('.').pop().toLowerCase();
|
||||||
|
|
||||||
|
if (allowedExtensions.includes(ext)) {
|
||||||
|
validTempFiles.push(file);
|
||||||
|
validTempFilePaths.push(res.tempFilePaths[index]);
|
||||||
|
} else {
|
||||||
|
hasInvalid = true;
|
||||||
|
invalidExt = ext;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (hasInvalid) {
|
||||||
|
uni.showToast({
|
||||||
|
title: `已过滤不支持的 .${invalidExt} 格式图片,请上传 png/jpg/jpeg/gif/bmp`,
|
||||||
|
icon: 'none',
|
||||||
|
duration: 3500
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
res.tempFilePaths = validTempFilePaths;
|
||||||
|
res.tempFiles = validTempFiles;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 全局拦截文件上传,校验文件后缀是否在后端白名单中,预防非法格式报错
|
||||||
|
uni.addInterceptor('uploadFile', {
|
||||||
|
invoke(args) {
|
||||||
|
const filePath = args.filePath;
|
||||||
|
if (filePath) {
|
||||||
|
const cleanPath = filePath.split('?')[0];
|
||||||
|
const ext = cleanPath.split('.').pop().toLowerCase();
|
||||||
|
const allowedExtensions = ['bmp', 'gif', 'jpg', 'jpeg', 'png', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'html', 'htm', 'txt', 'rar', 'zip', 'gz', 'bz2', 'mp4', 'avi', 'rmvb', 'pdf'];
|
||||||
|
if (!allowedExtensions.includes(ext)) {
|
||||||
|
uni.showToast({
|
||||||
|
title: `不支持 .${ext} 格式,请上传合规的文件或图片`,
|
||||||
|
icon: 'none',
|
||||||
|
duration: 3000
|
||||||
|
});
|
||||||
|
return false; // 拦截请求
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// #ifndef VUE3
|
// #ifndef VUE3
|
||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
import './uni.promisify.adaptor'
|
import './uni.promisify.adaptor'
|
||||||
|
|||||||
16
node_modules/.package-lock.json
generated
vendored
16
node_modules/.package-lock.json
generated
vendored
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "sanchayibaoguang",
|
"name": "threeonecheck_小程序",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"node_modules/@babel/helper-string-parser": {
|
"node_modules/@babel/helper-string-parser": {
|
||||||
@@ -660,6 +660,18 @@
|
|||||||
"resolved": "https://registry.npmmirror.com/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz",
|
"resolved": "https://registry.npmmirror.com/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz",
|
||||||
"integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==",
|
"integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/wot-design-uni": {
|
||||||
|
"version": "1.14.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/wot-design-uni/-/wot-design-uni-1.14.0.tgz",
|
||||||
|
"integrity": "sha512-FaBXtmxxAkZNZUxR2xeKBg/Agck+SPaFFFeCvbBzAFeqM/5m2Y/v/7te7rhMJpZFkMoAgtJR1yaWR0DnjOmTnA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"HBuilderX": "^3.8.7"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"vue": ">=3.2.47"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
21
node_modules/wot-design-uni/LICENSE
generated
vendored
Normal file
21
node_modules/wot-design-uni/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2023 weisheng
|
||||||
|
|
||||||
|
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.
|
||||||
148
node_modules/wot-design-uni/README.md
generated
vendored
Normal file
148
node_modules/wot-design-uni/README.md
generated
vendored
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
<p align="center">
|
||||||
|
<img alt="logo" src="https://wot-ui.cn/logo.png" width="200">
|
||||||
|
</p>
|
||||||
|
<h1 align="center">Wot UI</h1>
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
<p>简体中文 | <a href="./README_en.md">English</a></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p align="center">📱 一个基于vue3+Typescript构建,参照<a href="https://github.com/jd-ftf/wot-design-mini?tab=readme-ov-file">wot-design</a>打造的uni-app组件库</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
|
||||||
|
<a href="https://github.com/Moonofweisheng/wot-design-uni">
|
||||||
|
<img alt="GitHub Repo stars" src="https://img.shields.io/github/stars/Moonofweisheng/wot-design-uni?logo=github&color=%234d80f0&link=https%3A%2F%2Fgithub.com%2FMoonofweisheng%2Fwot-design-uni&style=flat-square">
|
||||||
|
</a>
|
||||||
|
|
||||||
|
|
||||||
|
<a href="https://github.com/Moonofweisheng/wot-design-uni">
|
||||||
|
<img alt="GitHub" src="https://img.shields.io/codecov/c/github/Moonofweisheng/wot-design-uni?style=flat-square">
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href="https://www.npmjs.com/package/wot-design-uni">
|
||||||
|
<img alt="npm" src="https://img.shields.io/npm/dm/wot-design-uni?logo=npm&link=https%3A%2F%2Fwww.npmjs.com%2Fpackage%2Fwot-design-uni&style=flat-square">
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href="https://www.npmjs.com/package/wot-design-uni">
|
||||||
|
<img alt="npm" src="https://img.shields.io/npm/v/wot-design-uni?logo=npm&color=%234d80f0&link=https%3A%2F%2Fwww.npmjs.com%2Fpackage%2Fwot-design-uni&style=flat-square">
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href="https://github.com/actions-cool/" target="_blank" referrerpolicy="no-referrer">
|
||||||
|
<img src="https://img.shields.io/badge/using-actions--cool-red?style=flat-square" alt="actions-cool" />
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href="https://app.netlify.com/sites/wot-design-uni/deploys" target="_blank" referrerpolicy="no-referrer">
|
||||||
|
<img src="https://api.netlify.com/api/v1/badges/0991d8a9-0fb0-483b-8961-5bde066bbd50/deploy-status" alt="deploy-status" />
|
||||||
|
</a>
|
||||||
|
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
🚀 <a href="https://wot-ui.cn">文档网站 (推荐)</a>
|
||||||
|
✈️ <a href="https://wot-design-uni.pages.dev/">文档网站(cloudflare)</a>
|
||||||
|
🔥 <a href="https://wot-design-uni.netlify.app/">文档网站 (Netlify)</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
## ✨ 特性
|
||||||
|
|
||||||
|
- 🎯 多平台覆盖,支持 微信小程序、支付宝小程序、钉钉小程序、H5、APP 等.
|
||||||
|
- 🚀 70+ 个高质量组件,覆盖移动端主流场景.
|
||||||
|
- 💪 使用 Typescript 构建,提供良好的组件类型系统.
|
||||||
|
- 🌍 支持国际化,内置 15 种语言包.
|
||||||
|
- 📖 提供丰富的文档和组件示例.
|
||||||
|
- 🎨 支持修改 CSS 变量实现主题定制.
|
||||||
|
- 🍭 支持暗黑模式.
|
||||||
|
|
||||||
|
## 📱 预览
|
||||||
|
|
||||||
|
扫描二维码访问演示,注意:因微信审核机制限制,当前的微信小程序示例可能不是最新版本,可以 clone 代码到本地预览。
|
||||||
|
|
||||||
|
<p style="display:flex;gap:24px">
|
||||||
|
<img src="https://wot-ui.cn/wx.jpg" width="200" height="200"/>
|
||||||
|
<img src="https://wot-ui.cn/alipay.png" width="200" height="200" />
|
||||||
|
<img src="https://wot-ui.cn/h5.png" width="200" height="200" />
|
||||||
|
<img src="https://wot-ui.cn/android.png" width="200" height="200" />
|
||||||
|
|
||||||
|
</p>
|
||||||
|
|
||||||
|
## 快速上手
|
||||||
|
|
||||||
|
详细说明见 [快速上手](https://wot-ui.cn/guide/quick-use.html)。
|
||||||
|
|
||||||
|
## 链接
|
||||||
|
|
||||||
|
- [常见问题](https://wot-ui.cn/guide/common-problems.html)
|
||||||
|
- [更新日志](https://wot-ui.cn/guide/changelog.html)
|
||||||
|
- [Discussions 讨论区](https://github.com/Moonofweisheng/wot-design-uni/discussions)
|
||||||
|
- [QQ 群](https://wot-ui.cn/guide/join-group.html)
|
||||||
|
|
||||||
|
## 优秀案例
|
||||||
|
|
||||||
|
[这里](https://wot-ui.cn/guide/cases.html)我们收集了一些优秀的案例,欢迎大家体验!
|
||||||
|
|
||||||
|
我们也非常欢迎大家一起贡献优秀的 Demo 与案例,欢迎在此 [issue](https://github.com/Moonofweisheng/wot-design-uni/issues/16) 提交案例。
|
||||||
|
|
||||||
|
## 周边生态
|
||||||
|
|
||||||
|
| 项目 | 描述 |
|
||||||
|
| ----------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- |
|
||||||
|
| [wot-starter](https://github.com/wot-ui/wot-starter) | 基于 [vitesse-uni-app](https://github.com/uni-helper/vitesse-uni-app) 的 wot-ui 快速起手项目 |
|
||||||
|
| [wot-ui-intellisense](https://github.com/wot-ui/wot-ui-intellisense) | wot-ui vscode 代码提示插件 |
|
||||||
|
| [awesome-uni-app](https://github.com/uni-helper/awesome-uni-app) | 多端统一开发框架 uni-app 优秀开发资源汇总 |
|
||||||
|
| [create-uni](https://github.com/uni-helper/create-uni) | 快速创建 uni-app 项目 |
|
||||||
|
| [wot-starter-retail](https://github.com/Moonofweisheng/wot-starter-retail) | 基于 wot-ui 的 uni-app 零售行业模板 |
|
||||||
|
| [Wot UI Snippets](https://marketplace.visualstudio.com/items?itemName=kiko.wot-design-uni-snippets) | wot-ui 代码块提示 |
|
||||||
|
| [uni-mini-ci](https://github.com/Moonofweisheng/uni-mini-ci) | 一个 uni-app 小程序端构建后支持 CI(持续集成)的插件 |
|
||||||
|
| [uni-mini-router](https://github.com/Moonofweisheng/uni-mini-router) | 一个基于 vue3 和 Typescript 的轻量级 uni-app 路由库 |
|
||||||
|
| [unibest](https://github.com/unibest-tech/unibest) | 基于 wot-ui 的 uni-app 模板 |
|
||||||
|
| [wot-design-uni AI 助手](https://www.coze.cn/store/bot/7347916532258701363) | 一个能回答你关于 wot-ui 组件库问题的智能助手 |
|
||||||
|
| [uni-ku-root](https://github.com/uni-ku/root) | 一个模拟 App.vue 原有能力的根组件插件 |
|
||||||
|
|
||||||
|
## 贡献指南
|
||||||
|
|
||||||
|
修改代码请阅读我们的 [贡献指南](./.github/CONTRIBUTING.md)。
|
||||||
|
|
||||||
|
使用过程中发现任何问题都可以提 [Issue](https://github.com/Moonofweisheng/wot-design-uni/issues) 给我们,当然,我们也非常欢迎你给我们发 [PR](https://github.com/Moonofweisheng/wot-design-uni/pulls)。
|
||||||
|
|
||||||
|
## 贡献者们
|
||||||
|
|
||||||
|
感谢以下所有给 Wot UI 贡献过代码的 [开发者](https://github.com/Moonofweisheng/wot-design-uni/graphs/contributors)。
|
||||||
|
|
||||||
|
<a href="https://github.com/Moonofweisheng/wot-design-uni/graphs/contributors">
|
||||||
|
<img src="https://contrib.rocks/image?repo=Moonofweisheng/wot-design-uni" />
|
||||||
|
</a>
|
||||||
|
|
||||||
|
## 捐赠本项目
|
||||||
|
|
||||||
|
开发一个 UI 组件库是一项耗时的工作,尤其是要多端适配。为此 Wot UI 经常肝到深夜 ……
|
||||||
|
|
||||||
|
如果您认为 Wot UI 帮助到了您的开发工作,您可以捐赠 Wot UI 的研发工作,捐赠无门槛,哪怕是一杯可乐也好。
|
||||||
|
|
||||||
|
捐赠后您的昵称、留言等将会展示在[捐赠榜单](https://wot-ui.cn/reward/donor.html)中。
|
||||||
|
|
||||||
|
|
||||||
|
### 爱发电捐赠
|
||||||
|
|
||||||
|
<a href="https://afdian.com/a/weisheng233">https://afdian.com/a/weisheng233</a>
|
||||||
|
|
||||||
|
### 扫码捐赠
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<img src="https://wot-ui.cn/weixinQrcode.jpg" width="200" height="200" style="margin-right:30px"/>
|
||||||
|
<img src="https://wot-ui.cn/alipayQrcode.jpg" width="200" height="200" />
|
||||||
|
</p>
|
||||||
|
|
||||||
|
## 鸣谢
|
||||||
|
|
||||||
|
- [wot-design](https://github.com/jd-ftf/wot-design-mini) - 感谢 wot-design 团队多年来的不断维护,让 wot-design-uni 能够站在巨人的肩膀上。
|
||||||
|
- [uni-helper](https://github.com/uni-helper) - 感谢 uni-helper 团队提供的 uni-app 工具库,让 wot-design-uni 能够更方便地使用。
|
||||||
|
- [捐赠者](https://wot-ui.cn/reward/donor.html) - 感谢所有捐赠者,是你们的捐赠让 wot-design-uni 能够更好地发展。
|
||||||
|
|
||||||
|
|
||||||
|
## 开源协议
|
||||||
|
|
||||||
|
本项目基于 [MIT](https://zh.wikipedia.org/wiki/MIT%E8%A8%B1%E5%8F%AF%E8%AD%89) 协议,请自由地享受和参与开源。
|
||||||
|
|
||||||
|
|
||||||
|
[](https://star-history.com/#Moonofweisheng/wot-design-uni&Date)
|
||||||
1
node_modules/wot-design-uni/attributes.json
generated
vendored
Normal file
1
node_modules/wot-design-uni/attributes.json
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
2273
node_modules/wot-design-uni/changelog.md
generated
vendored
Normal file
2273
node_modules/wot-design-uni/changelog.md
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
28
node_modules/wot-design-uni/components/common/AbortablePromise.ts
generated
vendored
Normal file
28
node_modules/wot-design-uni/components/common/AbortablePromise.ts
generated
vendored
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
export class AbortablePromise<T> {
|
||||||
|
promise: Promise<T>
|
||||||
|
private _reject: ((res?: any) => void) | null = null
|
||||||
|
|
||||||
|
constructor(executor: (resolve: (value: T | PromiseLike<T>) => void, reject: (reason?: any) => void) => void) {
|
||||||
|
this.promise = new Promise<T>((resolve, reject) => {
|
||||||
|
executor(resolve, reject)
|
||||||
|
this._reject = reject // 保存reject方法的引用,以便在abort时调用
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// 提供abort方法来中止Promise
|
||||||
|
abort(error?: any) {
|
||||||
|
if (this._reject) {
|
||||||
|
this._reject(error) // 调用reject方法来中止Promise
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
then<TResult1 = T, TResult2 = never>(
|
||||||
|
onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null,
|
||||||
|
onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null
|
||||||
|
): Promise<TResult1 | TResult2> {
|
||||||
|
return this.promise.then(onfulfilled, onrejected)
|
||||||
|
}
|
||||||
|
|
||||||
|
catch<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null): Promise<T | TResult> {
|
||||||
|
return this.promise.catch(onrejected)
|
||||||
|
}
|
||||||
|
}
|
||||||
7
node_modules/wot-design-uni/components/common/abstracts/_config.scss
generated
vendored
Normal file
7
node_modules/wot-design-uni/components/common/abstracts/_config.scss
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* SCSS 配置项:命名空间以及BEM
|
||||||
|
*/
|
||||||
|
$namespace: 'wd';
|
||||||
|
$elementSeparator: '__';
|
||||||
|
$modifierSeparator: '--';
|
||||||
|
$state-prefix: 'is-';
|
||||||
89
node_modules/wot-design-uni/components/common/abstracts/_function.scss
generated
vendored
Normal file
89
node_modules/wot-design-uni/components/common/abstracts/_function.scss
generated
vendored
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
/**
|
||||||
|
* 辅助函数
|
||||||
|
*/
|
||||||
|
@import 'config';
|
||||||
|
$default-theme: #4d80f0 !default; // 正常色
|
||||||
|
|
||||||
|
/* 转换成字符串 */
|
||||||
|
@function selectorToString($selector) {
|
||||||
|
$selector: inspect($selector);
|
||||||
|
$selector: str-slice($selector, 2, -2);
|
||||||
|
|
||||||
|
@return $selector;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 判断是否存在 Modifier */
|
||||||
|
@function containsModifier($selector) {
|
||||||
|
$selector: selectorToString($selector);
|
||||||
|
|
||||||
|
@if str-index($selector, $modifierSeparator) {
|
||||||
|
@return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@else {
|
||||||
|
@return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 判断是否存在伪类 */
|
||||||
|
@function containsPseudo($selector) {
|
||||||
|
$selector: selectorToString($selector);
|
||||||
|
|
||||||
|
@if str-index($selector, ':') {
|
||||||
|
@return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@else {
|
||||||
|
@return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主题色切换
|
||||||
|
* @params $theme-color 主题色
|
||||||
|
* @params $type 变暗’dark‘ 变亮 'light'
|
||||||
|
* @params $mix-color 自己设置的混色
|
||||||
|
*/
|
||||||
|
@function themeColor($theme-color, $type: "", $mix-color: "") {
|
||||||
|
@if $default-theme !=#4d80f0 {
|
||||||
|
@if $type=="dark" {
|
||||||
|
@return darken($theme-color, 10%);
|
||||||
|
}
|
||||||
|
|
||||||
|
@else if $type=="light" {
|
||||||
|
@return lighten($theme-color, 10%);
|
||||||
|
}
|
||||||
|
|
||||||
|
@else {
|
||||||
|
@return $theme-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@else {
|
||||||
|
@return $mix-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 颜色结果切换, 如果开启线性渐变色 使用渐变色,如果没有开启,那么使用主题色
|
||||||
|
* @params $open-linear 是否开启线性渐变色
|
||||||
|
* @params $deg 渐变色角度
|
||||||
|
* @params $theme-color 当前配色
|
||||||
|
* @params [Array] $set 主题色明暗设置,与 $color-list 数量对应
|
||||||
|
* @params [Array] $color-list 渐变色顺序, $color-list 和 $per-list 数量相同
|
||||||
|
* @params [Array] $per-list 渐变色比例
|
||||||
|
*/
|
||||||
|
@function resultColor($deg, $theme-color, $set, $color-list, $per-list) {
|
||||||
|
// 开启渐变
|
||||||
|
|
||||||
|
$len: length($color-list);
|
||||||
|
$arg: $deg;
|
||||||
|
|
||||||
|
@for $i from 1 through $len {
|
||||||
|
$arg: $arg + ","+ themeColor($theme-color, nth($set, $i), nth($color-list, $i)) + " "+ nth($per-list, $i);
|
||||||
|
}
|
||||||
|
|
||||||
|
@return linear-gradient(unquote($arg));
|
||||||
|
|
||||||
|
}
|
||||||
385
node_modules/wot-design-uni/components/common/abstracts/_mixin.scss
generated
vendored
Normal file
385
node_modules/wot-design-uni/components/common/abstracts/_mixin.scss
generated
vendored
Normal file
@@ -0,0 +1,385 @@
|
|||||||
|
/**
|
||||||
|
* 混合宏
|
||||||
|
*/
|
||||||
|
@import "config";
|
||||||
|
@import "function";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BEM,定义块(b)
|
||||||
|
*/
|
||||||
|
@mixin b($block) {
|
||||||
|
$B: $namespace + "-"+ $block !global;
|
||||||
|
|
||||||
|
.#{$B} {
|
||||||
|
@content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 定义元素(e),对于伪类,会自动将 e 嵌套在 伪类 底下 */
|
||||||
|
@mixin e($element...) {
|
||||||
|
$selector: &;
|
||||||
|
$selectors: "";
|
||||||
|
|
||||||
|
@if containsPseudo($selector) {
|
||||||
|
@each $item in $element {
|
||||||
|
$selectors: #{$selectors + "." + $B + $elementSeparator + $item + ","};
|
||||||
|
}
|
||||||
|
|
||||||
|
@at-root {
|
||||||
|
#{$selector} {
|
||||||
|
#{$selectors} {
|
||||||
|
@content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@else {
|
||||||
|
@each $item in $element {
|
||||||
|
$selectors: #{$selectors + $selector + $elementSeparator + $item + ","};
|
||||||
|
}
|
||||||
|
|
||||||
|
@at-root {
|
||||||
|
#{$selectors} {
|
||||||
|
@content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* 此方法用于生成穿透样式 */
|
||||||
|
|
||||||
|
/* 定义元素(e),对于伪类,会自动将 e 嵌套在 伪类 底下 */
|
||||||
|
@mixin edeep($element...) {
|
||||||
|
$selector: &;
|
||||||
|
$selectors: "";
|
||||||
|
|
||||||
|
@if containsPseudo($selector) {
|
||||||
|
@each $item in $element {
|
||||||
|
$selectors: #{$selectors + "." + $B + $elementSeparator + $item + ","};
|
||||||
|
}
|
||||||
|
|
||||||
|
@at-root {
|
||||||
|
#{$selector} {
|
||||||
|
:deep() {
|
||||||
|
#{$selectors} {
|
||||||
|
@content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@else {
|
||||||
|
@each $item in $element {
|
||||||
|
$selectors: #{$selectors + $selector + $elementSeparator + $item + ","};
|
||||||
|
}
|
||||||
|
|
||||||
|
@at-root {
|
||||||
|
:deep() {
|
||||||
|
#{$selectors} {
|
||||||
|
@content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* 定义状态(m) */
|
||||||
|
@mixin m($modifier...) {
|
||||||
|
$selectors: "";
|
||||||
|
|
||||||
|
@each $item in $modifier {
|
||||||
|
$selectors: #{$selectors + & + $modifierSeparator + $item + ","};
|
||||||
|
}
|
||||||
|
|
||||||
|
@at-root {
|
||||||
|
#{$selectors} {
|
||||||
|
@content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 定义状态(m) */
|
||||||
|
@mixin mdeep($modifier...) {
|
||||||
|
$selectors: "";
|
||||||
|
|
||||||
|
@each $item in $modifier {
|
||||||
|
$selectors: #{$selectors + & + $modifierSeparator + $item + ","};
|
||||||
|
}
|
||||||
|
|
||||||
|
@at-root {
|
||||||
|
:deep() {
|
||||||
|
#{$selectors} {
|
||||||
|
@content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 对于需要需要嵌套在 m 底下的 e,调用这个混合宏,一般在切换整个组件的状态,如切换颜色的时候 */
|
||||||
|
@mixin me($element...) {
|
||||||
|
$selector: &;
|
||||||
|
$selectors: "";
|
||||||
|
|
||||||
|
@if containsModifier($selector) {
|
||||||
|
@each $item in $element {
|
||||||
|
$selectors: #{$selectors + "." + $B + $elementSeparator + $item + ","};
|
||||||
|
}
|
||||||
|
|
||||||
|
@at-root {
|
||||||
|
#{$selector} {
|
||||||
|
#{$selectors} {
|
||||||
|
@content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@else {
|
||||||
|
@each $item in $element {
|
||||||
|
$selectors: #{$selectors + $selector + $elementSeparator + $item + ","};
|
||||||
|
}
|
||||||
|
|
||||||
|
@at-root {
|
||||||
|
#{$selectors} {
|
||||||
|
@content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 状态,生成 is-$state 类名 */
|
||||||
|
@mixin when($states...) {
|
||||||
|
@at-root {
|
||||||
|
@each $state in $states {
|
||||||
|
&.#{$state-prefix + $state} {
|
||||||
|
@content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 常用混合宏
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* 单行超出隐藏 */
|
||||||
|
@mixin lineEllipsis {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 多行超出隐藏 */
|
||||||
|
@mixin multiEllipsis($lineNumber: 3) {
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-line-clamp: $lineNumber;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 清除浮动 */
|
||||||
|
@mixin clearFloat {
|
||||||
|
&::after {
|
||||||
|
display: block;
|
||||||
|
content: "";
|
||||||
|
height: 0;
|
||||||
|
clear: both;
|
||||||
|
overflow: hidden;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 0.5px 边框 指定方向*/
|
||||||
|
@mixin halfPixelBorder($direction: "bottom", $left: 0, $color: $-color-border-light) {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
position: absolute;
|
||||||
|
display: block;
|
||||||
|
content: "";
|
||||||
|
|
||||||
|
@if ($left==0) {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@else {
|
||||||
|
width: calc(100% - #{$left});
|
||||||
|
}
|
||||||
|
|
||||||
|
height: 1px;
|
||||||
|
left: $left;
|
||||||
|
|
||||||
|
@if ($direction=="bottom") {
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@else {
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
transform: scaleY(0.5);
|
||||||
|
background: $color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* 0.5px 边框 环绕 */
|
||||||
|
@mixin halfPixelBorderSurround($color: $-color-border-light) {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
position: absolute;
|
||||||
|
display: block;
|
||||||
|
content: ' ';
|
||||||
|
pointer-events: none;
|
||||||
|
width: 200%;
|
||||||
|
height: 200%;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
border: 1px solid $color;
|
||||||
|
transform: scale(0.5);
|
||||||
|
box-sizing: border-box;
|
||||||
|
transform-origin: left top;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin buttonClear {
|
||||||
|
outline: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 三角形实现尖角样式,适用于背景透明情况
|
||||||
|
* @param $size 三角形高,底边为 $size * 2
|
||||||
|
* @param $bg 三角形背景颜色
|
||||||
|
*/
|
||||||
|
@mixin triangleArrow($size, $bg) {
|
||||||
|
@include e(arrow) {
|
||||||
|
position: absolute;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(arrow-down) {
|
||||||
|
border-left: $size solid transparent;
|
||||||
|
border-right: $size solid transparent;
|
||||||
|
border-top: $size solid $bg;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
bottom: calc(-1 * $size)
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(arrow-up) {
|
||||||
|
border-left: $size solid transparent;
|
||||||
|
border-right: $size solid transparent;
|
||||||
|
border-bottom: $size solid $bg;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
top: calc(-1 * $size)
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(arrow-left) {
|
||||||
|
border-top: $size solid transparent;
|
||||||
|
border-bottom: $size solid transparent;
|
||||||
|
border-right: $size solid $bg;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
left: calc(-1 * $size)
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(arrow-right) {
|
||||||
|
border-top: $size solid transparent;
|
||||||
|
border-bottom: $size solid transparent;
|
||||||
|
border-left: $size solid $bg;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
right: calc(-1 * $size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 正方形实现尖角样式,适用于背景不透明情况
|
||||||
|
* @param $size 正方形边长
|
||||||
|
* @param $bg 正方形背景颜色
|
||||||
|
* @param $z-index z-index属性值,不得大于外部包裹器
|
||||||
|
* @param $box-shadow 阴影
|
||||||
|
*/
|
||||||
|
@mixin squareArrow($size, $bg, $z-index, $box-shadow) {
|
||||||
|
@include e(arrow) {
|
||||||
|
position: absolute;
|
||||||
|
width: $size;
|
||||||
|
height: $size;
|
||||||
|
z-index: $z-index;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(arrow-down) {
|
||||||
|
transform: translateX(-50%);
|
||||||
|
bottom: 0;
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
content: "";
|
||||||
|
width: $size;
|
||||||
|
height: $size;
|
||||||
|
background-color: $bg;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
bottom: calc(-1 * $size / 2);
|
||||||
|
transform: rotateZ(45deg);
|
||||||
|
box-shadow: $box-shadow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(arrow-up) {
|
||||||
|
transform: translateX(-50%);
|
||||||
|
top: 0;
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
content: "";
|
||||||
|
width: $size;
|
||||||
|
height: $size;
|
||||||
|
background-color: $bg;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: calc(-1 * $size / 2);
|
||||||
|
transform: rotateZ(45deg);
|
||||||
|
box-shadow: $box-shadow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(arrow-left) {
|
||||||
|
transform: translateY(-50%);
|
||||||
|
left: 0;
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
content: "";
|
||||||
|
width: $size;
|
||||||
|
height: $size;
|
||||||
|
background-color: $bg;
|
||||||
|
position: absolute;
|
||||||
|
left: calc(-1 * $size / 2);
|
||||||
|
top: 0;
|
||||||
|
transform: rotateZ(45deg);
|
||||||
|
box-shadow: $box-shadow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(arrow-right) {
|
||||||
|
transform: translateY(-50%);
|
||||||
|
right: 0;
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
content: "";
|
||||||
|
width: $size;
|
||||||
|
height: $size;
|
||||||
|
background-color: $bg;
|
||||||
|
position: absolute;
|
||||||
|
right: calc(-1 * $size / 2);
|
||||||
|
top: 0;
|
||||||
|
transform: rotateZ(45deg);
|
||||||
|
box-shadow: $box-shadow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1030
node_modules/wot-design-uni/components/common/abstracts/variable.scss
generated
vendored
Normal file
1030
node_modules/wot-design-uni/components/common/abstracts/variable.scss
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
29
node_modules/wot-design-uni/components/common/base64.ts
generated
vendored
Normal file
29
node_modules/wot-design-uni/components/common/base64.ts
generated
vendored
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
const _b64chars: string[] = [...'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/']
|
||||||
|
const _mkUriSafe = (src: string): string => src.replace(/[+/]/g, (m0: string) => (m0 === '+' ? '-' : '_')).replace(/=+\$/m, '')
|
||||||
|
const fromUint8Array = (src: Uint8Array, rfc4648 = false): string => {
|
||||||
|
let b64 = ''
|
||||||
|
for (let i = 0, l = src.length; i < l; i += 3) {
|
||||||
|
const [a0, a1, a2] = [src[i], src[i + 1], src[i + 2]]
|
||||||
|
const ord = (a0 << 16) | (a1 << 8) | a2
|
||||||
|
b64 += _b64chars[ord >>> 18]
|
||||||
|
b64 += _b64chars[(ord >>> 12) & 63]
|
||||||
|
b64 += typeof a1 !== 'undefined' ? _b64chars[(ord >>> 6) & 63] : '='
|
||||||
|
b64 += typeof a2 !== 'undefined' ? _b64chars[ord & 63] : '='
|
||||||
|
}
|
||||||
|
return rfc4648 ? _mkUriSafe(b64) : b64
|
||||||
|
}
|
||||||
|
const _btoa: (s: string) => string =
|
||||||
|
typeof btoa === 'function'
|
||||||
|
? (s: string) => btoa(s)
|
||||||
|
: (s: string) => {
|
||||||
|
if (s.charCodeAt(0) > 255) {
|
||||||
|
throw new RangeError('The string contains invalid characters.')
|
||||||
|
}
|
||||||
|
return fromUint8Array(Uint8Array.from(s, (c: string) => c.charCodeAt(0)))
|
||||||
|
}
|
||||||
|
const utob = (src: string): string => unescape(encodeURIComponent(src))
|
||||||
|
|
||||||
|
export default function encode(src: string, rfc4648 = false): string {
|
||||||
|
const b64 = _btoa(utob(src))
|
||||||
|
return rfc4648 ? _mkUriSafe(b64) : b64
|
||||||
|
}
|
||||||
49
node_modules/wot-design-uni/components/common/canvasHelper.ts
generated
vendored
Normal file
49
node_modules/wot-design-uni/components/common/canvasHelper.ts
generated
vendored
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
/**
|
||||||
|
* 适配 canvas 2d 上下文
|
||||||
|
* @param ctx canvas 2d 上下文
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function canvas2dAdapter(ctx: CanvasRenderingContext2D): UniApp.CanvasContext {
|
||||||
|
return Object.assign(ctx, {
|
||||||
|
setFillStyle(color: string | CanvasGradient) {
|
||||||
|
ctx.fillStyle = color
|
||||||
|
},
|
||||||
|
setStrokeStyle(color: string | CanvasGradient | CanvasPattern) {
|
||||||
|
ctx.strokeStyle = color
|
||||||
|
},
|
||||||
|
setLineWidth(lineWidth: number) {
|
||||||
|
ctx.lineWidth = lineWidth
|
||||||
|
},
|
||||||
|
setLineCap(lineCap: 'butt' | 'round' | 'square') {
|
||||||
|
ctx.lineCap = lineCap
|
||||||
|
},
|
||||||
|
|
||||||
|
setFontSize(font: string) {
|
||||||
|
ctx.font = font
|
||||||
|
},
|
||||||
|
setGlobalAlpha(alpha: number) {
|
||||||
|
ctx.globalAlpha = alpha
|
||||||
|
},
|
||||||
|
setLineJoin(lineJoin: 'bevel' | 'round' | 'miter') {
|
||||||
|
ctx.lineJoin = lineJoin
|
||||||
|
},
|
||||||
|
setTextAlign(align: 'left' | 'center' | 'right') {
|
||||||
|
ctx.textAlign = align
|
||||||
|
},
|
||||||
|
setMiterLimit(miterLimit: number) {
|
||||||
|
ctx.miterLimit = miterLimit
|
||||||
|
},
|
||||||
|
setShadow(offsetX: number, offsetY: number, blur: number, color: string) {
|
||||||
|
ctx.shadowOffsetX = offsetX
|
||||||
|
ctx.shadowOffsetY = offsetY
|
||||||
|
ctx.shadowBlur = blur
|
||||||
|
ctx.shadowColor = color
|
||||||
|
},
|
||||||
|
setTextBaseline(textBaseline: 'top' | 'bottom' | 'middle') {
|
||||||
|
ctx.textBaseline = textBaseline
|
||||||
|
},
|
||||||
|
createCircularGradient() {},
|
||||||
|
draw() {},
|
||||||
|
addColorStop() {}
|
||||||
|
}) as unknown as UniApp.CanvasContext
|
||||||
|
}
|
||||||
34
node_modules/wot-design-uni/components/common/clickoutside.ts
generated
vendored
Normal file
34
node_modules/wot-design-uni/components/common/clickoutside.ts
generated
vendored
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* @Author: weisheng
|
||||||
|
* @Date: 2023-07-02 22:51:06
|
||||||
|
* @LastEditTime: 2024-03-16 19:59:07
|
||||||
|
* @LastEditors: weisheng
|
||||||
|
* @Description:
|
||||||
|
* @FilePath: /wot-design-uni/src/uni_modules/wot-design-uni/components/common/clickoutside.ts
|
||||||
|
* 记得注释
|
||||||
|
*/
|
||||||
|
let queue: any[] = []
|
||||||
|
|
||||||
|
export function pushToQueue(comp: any) {
|
||||||
|
queue.push(comp)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeFromQueue(comp: any) {
|
||||||
|
queue = queue.filter((item) => {
|
||||||
|
return item.$.uid !== comp.$.uid
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function closeOther(comp: any) {
|
||||||
|
queue.forEach((item) => {
|
||||||
|
if (item.$.uid !== comp.$.uid) {
|
||||||
|
item.$.exposed.close()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function closeOutside() {
|
||||||
|
queue.forEach((item) => {
|
||||||
|
item.$.exposed.close()
|
||||||
|
})
|
||||||
|
}
|
||||||
8
node_modules/wot-design-uni/components/common/event.ts
generated
vendored
Normal file
8
node_modules/wot-design-uni/components/common/event.ts
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export const UPDATE_MODEL_EVENT = 'update:modelValue'
|
||||||
|
export const CHANGE_EVENT = 'change'
|
||||||
|
export const INPUT_EVENT = 'input'
|
||||||
|
export const CLICK_EVENT = 'click'
|
||||||
|
export const CLOSE_EVENT = 'close'
|
||||||
|
export const OPEN_EVENT = 'open'
|
||||||
|
export const CONFIRM_EVENT = 'confirm'
|
||||||
|
export const CANCEL_EVENT = 'cancel'
|
||||||
43
node_modules/wot-design-uni/components/common/interceptor.ts
generated
vendored
Normal file
43
node_modules/wot-design-uni/components/common/interceptor.ts
generated
vendored
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import { isPromise } from './util'
|
||||||
|
|
||||||
|
function noop() {}
|
||||||
|
|
||||||
|
export type Interceptor = (...args: any[]) => Promise<boolean> | boolean | undefined | void
|
||||||
|
|
||||||
|
export function callInterceptor(
|
||||||
|
interceptor: Interceptor | undefined,
|
||||||
|
{
|
||||||
|
args = [],
|
||||||
|
done,
|
||||||
|
canceled,
|
||||||
|
error
|
||||||
|
}: {
|
||||||
|
args?: unknown[]
|
||||||
|
done: () => void
|
||||||
|
canceled?: () => void
|
||||||
|
error?: () => void
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
if (interceptor) {
|
||||||
|
// eslint-disable-next-line prefer-spread
|
||||||
|
const returnVal = interceptor.apply(null, args)
|
||||||
|
|
||||||
|
if (isPromise(returnVal)) {
|
||||||
|
returnVal
|
||||||
|
.then((value) => {
|
||||||
|
if (value) {
|
||||||
|
done()
|
||||||
|
} else if (canceled) {
|
||||||
|
canceled()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error || noop)
|
||||||
|
} else if (returnVal) {
|
||||||
|
done()
|
||||||
|
} else if (canceled) {
|
||||||
|
canceled()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
done()
|
||||||
|
}
|
||||||
|
}
|
||||||
51
node_modules/wot-design-uni/components/common/props.ts
generated
vendored
Normal file
51
node_modules/wot-design-uni/components/common/props.ts
generated
vendored
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import type { PropType } 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 makeRequiredProp = <T>(type: T) => ({
|
||||||
|
type,
|
||||||
|
required: true as const
|
||||||
|
})
|
||||||
|
|
||||||
|
export const makeArrayProp = <T>() => ({
|
||||||
|
type: Array as PropType<T[]>,
|
||||||
|
default: () => []
|
||||||
|
})
|
||||||
|
|
||||||
|
export const makeBooleanProp = <T>(defaultVal: T) => ({
|
||||||
|
type: Boolean,
|
||||||
|
default: defaultVal
|
||||||
|
})
|
||||||
|
|
||||||
|
export const makeNumberProp = <T>(defaultVal: T) => ({
|
||||||
|
type: Number,
|
||||||
|
default: defaultVal
|
||||||
|
})
|
||||||
|
|
||||||
|
export const makeNumericProp = <T>(defaultVal: T) => ({
|
||||||
|
type: numericProp,
|
||||||
|
default: defaultVal
|
||||||
|
})
|
||||||
|
|
||||||
|
export const makeStringProp = <T>(defaultVal: T) => ({
|
||||||
|
type: String as unknown as PropType<T>,
|
||||||
|
default: defaultVal
|
||||||
|
})
|
||||||
|
|
||||||
|
export const baseProps = {
|
||||||
|
/**
|
||||||
|
* 自定义根节点样式
|
||||||
|
*/
|
||||||
|
customStyle: makeStringProp(''),
|
||||||
|
/**
|
||||||
|
* 自定义根节点样式类
|
||||||
|
*/
|
||||||
|
customClass: makeStringProp('')
|
||||||
|
}
|
||||||
836
node_modules/wot-design-uni/components/common/util.ts
generated
vendored
Normal file
836
node_modules/wot-design-uni/components/common/util.ts
generated
vendored
Normal file
@@ -0,0 +1,836 @@
|
|||||||
|
import { AbortablePromise } from './AbortablePromise'
|
||||||
|
|
||||||
|
type NotUndefined<T> = T extends undefined ? never : T
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成uuid
|
||||||
|
* @returns string
|
||||||
|
*/
|
||||||
|
export function uuid() {
|
||||||
|
return s4() + s4() + s4() + s4() + s4() + s4() + s4() + s4()
|
||||||
|
}
|
||||||
|
|
||||||
|
function s4() {
|
||||||
|
return Math.floor((1 + Math.random()) * 0x10000)
|
||||||
|
.toString(16)
|
||||||
|
.substring(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 对num自动填充px
|
||||||
|
* @param {Number} num
|
||||||
|
* @return {string} num+px
|
||||||
|
*/
|
||||||
|
export function addUnit(num: number | string) {
|
||||||
|
return Number.isNaN(Number(num)) ? `${num}` : `${num}px`
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 判断target是否对象
|
||||||
|
* @param value
|
||||||
|
* @return {boolean}
|
||||||
|
*/
|
||||||
|
export function isObj(value: any): value is object {
|
||||||
|
return Object.prototype.toString.call(value) === '[object Object]' || typeof value === 'object'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取目标原始类型
|
||||||
|
* @param target 任意类型
|
||||||
|
* @returns {string} type 数据类型
|
||||||
|
*/
|
||||||
|
export function getType(target: unknown): string {
|
||||||
|
// 得到原生类型
|
||||||
|
const typeStr = Object.prototype.toString.call(target)
|
||||||
|
// 拿到类型值
|
||||||
|
const match = typeStr.match(/\[object (\w+)\]/)
|
||||||
|
const type = match && match.length ? match[1].toLowerCase() : ''
|
||||||
|
// 类型值转小写并返回
|
||||||
|
return type
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 默认的外部格式化函数 - picker 组件
|
||||||
|
* @param items - 要格式化的数据项数组或单个数据项
|
||||||
|
* @param kv - 配置对象,包含 labelKey 作为键值
|
||||||
|
* @returns 格式化后的字符串
|
||||||
|
*/
|
||||||
|
export const defaultDisplayFormat = function (items: any[] | Record<string, any>, kv?: { labelKey?: string }): string {
|
||||||
|
const labelKey: string = kv?.labelKey || 'value'
|
||||||
|
|
||||||
|
if (Array.isArray(items)) {
|
||||||
|
return items.map((item) => item[labelKey]).join(', ')
|
||||||
|
} else {
|
||||||
|
return items[labelKey]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 默认函数占位符 - pickerView组件
|
||||||
|
* @param value 值
|
||||||
|
* @return value
|
||||||
|
*/
|
||||||
|
export const defaultFunction = <T>(value: T): T => value
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 检查值是否不为空
|
||||||
|
* @param value 值
|
||||||
|
* @return {Boolean} 是否不为空
|
||||||
|
*/
|
||||||
|
export const isDef = <T>(value: T): value is NonNullable<T> => value !== undefined && value !== null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 防止数字小于零
|
||||||
|
* @param {number} num
|
||||||
|
* @param {string} label 标签
|
||||||
|
*/
|
||||||
|
export const checkNumRange = (num: number, label: string = 'value'): void => {
|
||||||
|
if (num < 0) {
|
||||||
|
throw new Error(`${label} shouldn't be less than zero`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 防止 pixel 无意义
|
||||||
|
* @param {number} num
|
||||||
|
* @param {string} label 标签
|
||||||
|
*/
|
||||||
|
export const checkPixelRange = (num: number, label: string = 'value'): void => {
|
||||||
|
if (num <= 0) {
|
||||||
|
throw new Error(`${label} should be greater than zero`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将 RGB 值转换为十六进制颜色代码。
|
||||||
|
* @param {number} r - 红色分量 (0-255)。
|
||||||
|
* @param {number} g - 绿色分量 (0-255)。
|
||||||
|
* @param {number} b - 蓝色分量 (0-255)。
|
||||||
|
* @returns {string} 十六进制颜色代码 (#RRGGBB)。
|
||||||
|
*/
|
||||||
|
export function rgbToHex(r: number, g: number, b: number): string {
|
||||||
|
// 将 RGB 分量组合成一个十六进制数。
|
||||||
|
const hex = ((r << 16) | (g << 8) | b).toString(16)
|
||||||
|
|
||||||
|
// 使用零填充十六进制数,确保它有 6 位数字(RGB 范围)。
|
||||||
|
const paddedHex = '#' + '0'.repeat(Math.max(0, 6 - hex.length)) + hex
|
||||||
|
|
||||||
|
return paddedHex
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将十六进制颜色代码转换为 RGB 颜色数组。
|
||||||
|
* @param hex 十六进制颜色代码(例如:'#RRGGBB')
|
||||||
|
* @returns 包含红、绿、蓝三个颜色分量的数组
|
||||||
|
*/
|
||||||
|
export function hexToRgb(hex: string): number[] {
|
||||||
|
const rgb: number[] = []
|
||||||
|
|
||||||
|
// 从第一个字符开始,每两个字符代表一个颜色分量
|
||||||
|
for (let i = 1; i < 7; i += 2) {
|
||||||
|
// 将两个字符的十六进制转换为十进制,并添加到 rgb 数组中
|
||||||
|
rgb.push(parseInt('0x' + hex.slice(i, i + 2), 16))
|
||||||
|
}
|
||||||
|
|
||||||
|
return rgb
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算渐变色的中间变量数组。
|
||||||
|
* @param {string} startColor 开始颜色
|
||||||
|
* @param {string} endColor 结束颜色
|
||||||
|
* @param {number} step 获取渲染位置,默认为中间位置
|
||||||
|
* @returns {string[]} 渐变色中间颜色变量数组
|
||||||
|
*/
|
||||||
|
export const gradient = (startColor: string, endColor: string, step: number = 2): string[] => {
|
||||||
|
// 将hex转换为rgb
|
||||||
|
const sColor: number[] = hexToRgb(startColor)
|
||||||
|
const eColor: number[] = hexToRgb(endColor)
|
||||||
|
|
||||||
|
// 计算R\G\B每一步的差值
|
||||||
|
const rStep: number = (eColor[0] - sColor[0]) / step
|
||||||
|
const gStep: number = (eColor[1] - sColor[1]) / step
|
||||||
|
const bStep: number = (eColor[2] - sColor[2]) / step
|
||||||
|
|
||||||
|
const gradientColorArr: string[] = []
|
||||||
|
for (let i = 0; i < step; i++) {
|
||||||
|
// 计算每一步的hex值
|
||||||
|
gradientColorArr.push(
|
||||||
|
rgbToHex(parseInt(String(rStep * i + sColor[0])), parseInt(String(gStep * i + sColor[1])), parseInt(String(bStep * i + sColor[2])))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return gradientColorArr
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 确保数值不超出指定范围。
|
||||||
|
* @param {number} num 要限制范围的数值
|
||||||
|
* @param {number} min 最小范围
|
||||||
|
* @param {number} max 最大范围
|
||||||
|
* @returns {number} 在指定范围内的数值
|
||||||
|
*/
|
||||||
|
export const range = (num: number, min: number, max: number): number => {
|
||||||
|
// 使用 Math.min 和 Math.max 保证 num 不会超出指定范围
|
||||||
|
return Math.min(Math.max(num, min), max)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 比较两个值是否相等。
|
||||||
|
* @param {any} value1 第一个值
|
||||||
|
* @param {any} value2 第二个值
|
||||||
|
* @returns {boolean} 如果值相等则为 true,否则为 false
|
||||||
|
*/
|
||||||
|
export const isEqual = (value1: any, value2: any): boolean => {
|
||||||
|
// 使用严格相等运算符比较值是否相等
|
||||||
|
if (value1 === value2) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果其中一个值不是数组,则认为值不相等
|
||||||
|
if (!Array.isArray(value1) || !Array.isArray(value2)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果数组长度不相等,则认为值不相等
|
||||||
|
if (value1.length !== value2.length) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 逐个比较数组元素是否相等
|
||||||
|
for (let i = 0; i < value1.length; ++i) {
|
||||||
|
if (value1[i] !== value2[i]) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 所有比较均通过,则认为值相等
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在数字前补零,使其达到指定长度。
|
||||||
|
* @param {number | string} number 要补零的数字
|
||||||
|
* @param {number} length 目标长度,默认为 2
|
||||||
|
* @returns {string} 补零后的结果
|
||||||
|
*/
|
||||||
|
export const padZero = (number: number | string, length: number = 2): string => {
|
||||||
|
// 将输入转换为字符串
|
||||||
|
let numStr: string = number.toString()
|
||||||
|
|
||||||
|
// 在数字前补零,直到达到指定长度
|
||||||
|
while (numStr.length < length) {
|
||||||
|
numStr = '0' + numStr
|
||||||
|
}
|
||||||
|
|
||||||
|
return numStr
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @description 全局变量id */
|
||||||
|
export const context = {
|
||||||
|
id: 1000
|
||||||
|
}
|
||||||
|
|
||||||
|
export type RectResultType<T extends boolean> = T extends true ? UniApp.NodeInfo[] : UniApp.NodeInfo
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取节点信息
|
||||||
|
* @param selector 节点选择器 #id,.class
|
||||||
|
* @param all 是否返回所有 selector 对应的节点
|
||||||
|
* @param scope 作用域(支付宝小程序无效)
|
||||||
|
* @param useFields 是否使用 fields 方法获取节点信息
|
||||||
|
* @returns 节点信息或节点信息数组
|
||||||
|
*/
|
||||||
|
export function getRect<T extends boolean>(selector: string, all: T, scope?: any, useFields?: boolean): Promise<RectResultType<T>> {
|
||||||
|
return new Promise<RectResultType<T>>((resolve, reject) => {
|
||||||
|
let query: UniNamespace.SelectorQuery | null = null
|
||||||
|
if (scope) {
|
||||||
|
query = uni.createSelectorQuery().in(scope)
|
||||||
|
} else {
|
||||||
|
query = uni.createSelectorQuery()
|
||||||
|
}
|
||||||
|
|
||||||
|
const method = all ? 'selectAll' : 'select'
|
||||||
|
|
||||||
|
const callback = (rect: UniApp.NodeInfo | UniApp.NodeInfo[]) => {
|
||||||
|
if (all && isArray(rect) && rect.length > 0) {
|
||||||
|
resolve(rect as RectResultType<T>)
|
||||||
|
} else if (!all && rect) {
|
||||||
|
resolve(rect as RectResultType<T>)
|
||||||
|
} else {
|
||||||
|
reject(new Error('No nodes found'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (useFields) {
|
||||||
|
query[method](selector).fields({ size: true, node: true }, callback).exec()
|
||||||
|
} else {
|
||||||
|
query[method](selector).boundingClientRect(callback).exec()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将驼峰命名转换为短横线命名。
|
||||||
|
* @param {string} word 待转换的词条
|
||||||
|
* @returns {string} 转换后的结果
|
||||||
|
*/
|
||||||
|
export function kebabCase(word: string): string {
|
||||||
|
// 使用正则表达式匹配所有大写字母,并在前面加上短横线,然后转换为小写
|
||||||
|
const newWord: string = word
|
||||||
|
.replace(/[A-Z]/g, function (match) {
|
||||||
|
return '-' + match
|
||||||
|
})
|
||||||
|
.toLowerCase()
|
||||||
|
|
||||||
|
return newWord
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将短横线链接转换为驼峰命名
|
||||||
|
* @param word 需要转换的短横线链接
|
||||||
|
* @returns 转换后的驼峰命名字符串
|
||||||
|
*/
|
||||||
|
export function camelCase(word: string): string {
|
||||||
|
return word.replace(/-(\w)/g, (_, c) => c.toUpperCase())
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查给定值是否为数组。
|
||||||
|
* @param {any} value 要检查的值
|
||||||
|
* @returns {boolean} 如果是数组则返回 true,否则返回 false
|
||||||
|
*/
|
||||||
|
export function isArray(value: any): value is Array<any> {
|
||||||
|
// 如果 Array.isArray 函数可用,直接使用该函数检查
|
||||||
|
if (typeof Array.isArray === 'function') {
|
||||||
|
return Array.isArray(value)
|
||||||
|
}
|
||||||
|
// 否则,使用对象原型的 toString 方法进行检查
|
||||||
|
return Object.prototype.toString.call(value) === '[object Array]'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查给定值是否为函数。
|
||||||
|
* @param {any} value 要检查的值
|
||||||
|
* @returns {boolean} 如果是函数则返回 true,否则返回 false
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
|
export function isFunction<T extends Function>(value: any): value is T {
|
||||||
|
return getType(value) === 'function' || getType(value) === 'asyncfunction'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查给定值是否为字符串。
|
||||||
|
* @param {unknown} value 要检查的值
|
||||||
|
* @returns {value is string} 如果是字符串则返回 true,否则返回 false
|
||||||
|
*/
|
||||||
|
export function isString(value: unknown): value is string {
|
||||||
|
return getType(value) === 'string'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 否是数值
|
||||||
|
* @param {*} value
|
||||||
|
*/
|
||||||
|
export function isNumber(value: any): value is number {
|
||||||
|
return getType(value) === 'number'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查给定值是否为 Promise 对象。
|
||||||
|
* @param {unknown} value 要检查的值
|
||||||
|
* @returns {value is Promise<any>} 如果是 Promise 对象则返回 true,否则返回 false
|
||||||
|
*/
|
||||||
|
export function isPromise(value: unknown): value is Promise<any> {
|
||||||
|
// 先将 value 断言为 object 类型
|
||||||
|
if (isObj(value) && isDef(value)) {
|
||||||
|
// 然后进一步检查 value 是否具有 then 和 catch 方法,并且它们是函数类型
|
||||||
|
return isFunction((value as Promise<any>).then) && isFunction((value as Promise<any>).catch)
|
||||||
|
}
|
||||||
|
return false // 如果 value 不是对象类型,则肯定不是 Promise
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查给定的值是否为布尔类型
|
||||||
|
* @param value 要检查的值
|
||||||
|
* @returns 如果值为布尔类型,则返回true,否则返回false
|
||||||
|
*/
|
||||||
|
export function isBoolean(value: any): value is boolean {
|
||||||
|
return typeof value === 'boolean'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isUndefined(value: any): value is undefined {
|
||||||
|
return typeof value === 'undefined'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isNotUndefined<T>(value: T): value is NotUndefined<T> {
|
||||||
|
return !isUndefined(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查给定的值是否为奇数
|
||||||
|
* @param value 要检查的值
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function isOdd(value: number): boolean {
|
||||||
|
if (typeof value !== 'number') {
|
||||||
|
throw new Error('输入必须为数字')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用取模运算符来判断是否为奇数
|
||||||
|
// 如果 number 除以 2 的余数为 1,就是奇数
|
||||||
|
// 否则是偶数
|
||||||
|
return value % 2 === 1
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否为base64图片
|
||||||
|
* @param {string} url
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
export function isBase64Image(url: string) {
|
||||||
|
// 使用正则表达式检查URL是否以"data:image"开头,这是Base64图片的常见前缀
|
||||||
|
return /^data:image\/(png|jpg|jpeg|gif|bmp);base64,/.test(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将外部传入的样式格式化为可读的 CSS 样式。
|
||||||
|
* @param {object | object[]} styles 外部传入的样式对象或数组
|
||||||
|
* @returns {string} 格式化后的 CSS 样式字符串
|
||||||
|
*/
|
||||||
|
export function objToStyle(styles: Record<string, any> | Record<string, any>[]): string {
|
||||||
|
// 如果 styles 是数组类型
|
||||||
|
if (isArray(styles)) {
|
||||||
|
// 使用过滤函数去除空值和 null 值的元素
|
||||||
|
// 对每个非空元素递归调用 objToStyle,然后通过分号连接
|
||||||
|
const result = styles
|
||||||
|
.filter(function (item) {
|
||||||
|
return item != null && item !== ''
|
||||||
|
})
|
||||||
|
.map(function (item) {
|
||||||
|
return objToStyle(item)
|
||||||
|
})
|
||||||
|
.join(';')
|
||||||
|
|
||||||
|
// 如果结果不为空,确保末尾有分号
|
||||||
|
return result ? (result.endsWith(';') ? result : result + ';') : ''
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isString(styles)) {
|
||||||
|
// 如果是字符串且不为空,确保末尾有分号
|
||||||
|
return styles ? (styles.endsWith(';') ? styles : styles + ';') : ''
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果 styles 是对象类型
|
||||||
|
if (isObj(styles)) {
|
||||||
|
// 使用 Object.keys 获取所有属性名
|
||||||
|
// 使用过滤函数去除值为 null 或空字符串的属性
|
||||||
|
// 对每个属性名和属性值进行格式化,通过分号连接
|
||||||
|
const result = Object.keys(styles)
|
||||||
|
.filter(function (key) {
|
||||||
|
return styles[key] != null && styles[key] !== ''
|
||||||
|
})
|
||||||
|
.map(function (key) {
|
||||||
|
// 使用 kebabCase 函数将属性名转换为 kebab-case 格式
|
||||||
|
// 将属性名和属性值格式化为 CSS 样式的键值对
|
||||||
|
return [kebabCase(key), styles[key]].join(':')
|
||||||
|
})
|
||||||
|
.join(';')
|
||||||
|
|
||||||
|
// 如果结果不为空,确保末尾有分号
|
||||||
|
return result ? (result.endsWith(';') ? result : result + ';') : ''
|
||||||
|
}
|
||||||
|
// 如果 styles 不是对象也不是数组,则直接返回
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断一个对象是否包含任何字段
|
||||||
|
* @param obj 要检查的对象
|
||||||
|
* @returns {boolean} 如果对象为空(不包含任何字段)则返回 true,否则返回 false
|
||||||
|
*/
|
||||||
|
export function hasFields(obj: unknown): boolean {
|
||||||
|
// 如果不是对象类型或为 null,则认为没有字段
|
||||||
|
if (!isObj(obj) || obj === null) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用 Object.keys 检查对象是否有属性
|
||||||
|
return Object.keys(obj).length > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断一个对象是否为空对象(不包含任何字段)
|
||||||
|
* @param obj 要检查的对象
|
||||||
|
* @returns {boolean} 如果对象为空(不包含任何字段)则返回 true,否则返回 false
|
||||||
|
*/
|
||||||
|
export function isEmptyObj(obj: unknown): boolean {
|
||||||
|
return !hasFields(obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const requestAnimationFrame = (cb = () => {}) => {
|
||||||
|
return new AbortablePromise((resolve) => {
|
||||||
|
const timer = setInterval(() => {
|
||||||
|
clearInterval(timer)
|
||||||
|
resolve(true)
|
||||||
|
cb()
|
||||||
|
}, 1000 / 30)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 暂停指定时间函数
|
||||||
|
* @param ms 延迟时间
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const pause = (ms: number = 1000 / 30) => {
|
||||||
|
return new AbortablePromise((resolve) => {
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
clearTimeout(timer)
|
||||||
|
resolve(true)
|
||||||
|
}, ms)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 深拷贝函数,用于将对象进行完整复制。
|
||||||
|
* @param obj 要深拷贝的对象
|
||||||
|
* @param cache 用于缓存已复制的对象,防止循环引用
|
||||||
|
* @returns 深拷贝后的对象副本
|
||||||
|
*/
|
||||||
|
export function deepClone<T>(obj: T, cache: Map<any, any> = new Map()): T {
|
||||||
|
// 如果对象为 null 或或者不是对象类型,则直接返回该对象
|
||||||
|
if (obj === null || typeof obj !== 'object') {
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理特殊对象类型:日期、正则表达式、错误对象
|
||||||
|
if (isDate(obj)) {
|
||||||
|
return new Date(obj.getTime()) as any
|
||||||
|
}
|
||||||
|
if (obj instanceof RegExp) {
|
||||||
|
return new RegExp(obj.source, obj.flags) as any
|
||||||
|
}
|
||||||
|
if (obj instanceof Error) {
|
||||||
|
const errorCopy = new Error(obj.message) as any
|
||||||
|
errorCopy.stack = obj.stack
|
||||||
|
return errorCopy
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查缓存中是否已存在该对象的复制
|
||||||
|
if (cache.has(obj)) {
|
||||||
|
return cache.get(obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据原始对象的类型创建对应的空对象或数组
|
||||||
|
const copy: any = Array.isArray(obj) ? [] : {}
|
||||||
|
|
||||||
|
// 将当前对象添加到缓存中
|
||||||
|
cache.set(obj, copy)
|
||||||
|
|
||||||
|
// 递归地深拷贝对象的每个属性
|
||||||
|
for (const key in obj) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
||||||
|
copy[key] = deepClone(obj[key], cache)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return copy as T
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 深度合并两个对象。
|
||||||
|
* @param target 目标对象,将合并的结果存放在此对象中
|
||||||
|
* @param source 源对象,要合并到目标对象的对象
|
||||||
|
* @returns 合并后的目标对象
|
||||||
|
*/
|
||||||
|
export function deepMerge<T extends Record<string, any>>(target: T, source: Record<string, any>): T {
|
||||||
|
// 深拷贝目标对象,避免修改原始对象
|
||||||
|
target = deepClone(target)
|
||||||
|
|
||||||
|
// 检查目标和源是否都是对象类型
|
||||||
|
if (typeof target !== 'object' || typeof source !== 'object') {
|
||||||
|
throw new Error('Both target and source must be objects.')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 遍历源对象的属性
|
||||||
|
for (const prop in source) {
|
||||||
|
// eslint-disable-next-line no-prototype-builtins
|
||||||
|
if (!source.hasOwnProperty(prop))
|
||||||
|
continue
|
||||||
|
// 使用类型断言,告诉 TypeScript 这是有效的属性
|
||||||
|
;(target as Record<string, any>)[prop] = source[prop]
|
||||||
|
}
|
||||||
|
|
||||||
|
return target
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 深度合并两个对象。
|
||||||
|
* @param target
|
||||||
|
* @param source
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function deepAssign(target: Record<string, any>, source: Record<string, any>): Record<string, any> {
|
||||||
|
Object.keys(source).forEach((key) => {
|
||||||
|
const targetValue = target[key]
|
||||||
|
const newObjValue = source[key]
|
||||||
|
if (isObj(targetValue) && isObj(newObjValue)) {
|
||||||
|
deepAssign(targetValue, newObjValue)
|
||||||
|
} else {
|
||||||
|
target[key] = newObjValue
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return target
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建带参数的URL
|
||||||
|
* @param baseUrl 基础URL
|
||||||
|
* @param params 参数对象,键值对表示要添加到URL的参数
|
||||||
|
* @returns 返回带有参数的URL
|
||||||
|
*/
|
||||||
|
export function buildUrlWithParams(baseUrl: string, params: Record<string, string>) {
|
||||||
|
// 将参数对象转换为查询字符串
|
||||||
|
const queryString = Object.entries(params)
|
||||||
|
.map(([key, value]) => `${key}=${encodeURIComponent(value)}`)
|
||||||
|
.join('&')
|
||||||
|
|
||||||
|
// 检查基础URL是否已包含查询字符串,并选择适当的分隔符
|
||||||
|
const separator = baseUrl.includes('?') ? '&' : '?'
|
||||||
|
|
||||||
|
// 返回带有参数的URL
|
||||||
|
return `${baseUrl}${separator}${queryString}`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DebounceOptions = {
|
||||||
|
leading?: boolean // 是否在延迟时间开始时调用函数
|
||||||
|
trailing?: boolean // 是否在延迟时间结束时调用函数
|
||||||
|
}
|
||||||
|
|
||||||
|
export function debounce<T extends (...args: any[]) => any>(func: T, wait: number, options: DebounceOptions = {}): T {
|
||||||
|
let timeoutId: ReturnType<typeof setTimeout> | null = null
|
||||||
|
let lastArgs: any[] | undefined
|
||||||
|
let lastThis: any
|
||||||
|
let result: ReturnType<T> | undefined
|
||||||
|
const leading = isDef(options.leading) ? options.leading : false
|
||||||
|
const trailing = isDef(options.trailing) ? options.trailing : true
|
||||||
|
|
||||||
|
function invokeFunc() {
|
||||||
|
if (lastArgs !== undefined) {
|
||||||
|
result = func.apply(lastThis, lastArgs)
|
||||||
|
lastArgs = undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function startTimer() {
|
||||||
|
timeoutId = setTimeout(() => {
|
||||||
|
timeoutId = null
|
||||||
|
if (trailing) {
|
||||||
|
invokeFunc()
|
||||||
|
}
|
||||||
|
}, wait)
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancelTimer() {
|
||||||
|
if (timeoutId !== null) {
|
||||||
|
clearTimeout(timeoutId)
|
||||||
|
timeoutId = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function debounced(this: any, ...args: Parameters<T>): ReturnType<T> | undefined {
|
||||||
|
lastArgs = args
|
||||||
|
lastThis = this
|
||||||
|
|
||||||
|
if (timeoutId === null) {
|
||||||
|
if (leading) {
|
||||||
|
invokeFunc()
|
||||||
|
}
|
||||||
|
startTimer()
|
||||||
|
} else if (trailing) {
|
||||||
|
cancelTimer()
|
||||||
|
startTimer()
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
return debounced as T
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
|
export function throttle(func: Function, wait: number): Function {
|
||||||
|
let timeout: ReturnType<typeof setTimeout> | null = null
|
||||||
|
let previous: number = 0
|
||||||
|
|
||||||
|
const throttled = function (this: any, ...args: any[]) {
|
||||||
|
const now = Date.now()
|
||||||
|
const remaining = wait - (now - previous)
|
||||||
|
|
||||||
|
if (remaining <= 0) {
|
||||||
|
if (timeout) {
|
||||||
|
clearTimeout(timeout)
|
||||||
|
timeout = null
|
||||||
|
}
|
||||||
|
previous = now
|
||||||
|
func.apply(this, args)
|
||||||
|
} else if (!timeout) {
|
||||||
|
timeout = setTimeout(() => {
|
||||||
|
previous = Date.now()
|
||||||
|
timeout = null
|
||||||
|
func.apply(this, args)
|
||||||
|
}, remaining)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return throttled
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据属性路径获取对象中的属性值
|
||||||
|
* @param obj 目标对象
|
||||||
|
* @param path 属性路径,可以是字符串或字符串数组
|
||||||
|
* @returns 属性值,如果属性不存在或中间的属性为 null 或 undefined,则返回 undefined
|
||||||
|
*/
|
||||||
|
export const getPropByPath = (obj: any, path: string): any => {
|
||||||
|
const keys: string[] = path.split('.')
|
||||||
|
|
||||||
|
try {
|
||||||
|
return keys.reduce((acc: any, key: string) => (acc !== undefined && acc !== null ? acc[key] : undefined), obj)
|
||||||
|
} catch (error) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查一个值是否为Date类型
|
||||||
|
* @param val 要检查的值
|
||||||
|
* @returns 如果值是Date类型,则返回true,否则返回false
|
||||||
|
*/
|
||||||
|
export const isDate = (val: unknown): val is Date => Object.prototype.toString.call(val) === '[object Date]' && !Number.isNaN((val as Date).getTime())
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查提供的URL是否为视频链接。
|
||||||
|
* @param url 需要检查的URL字符串。
|
||||||
|
* @returns 返回一个布尔值,如果URL是视频链接则为true,否则为false。
|
||||||
|
*/
|
||||||
|
export function isVideoUrl(url: string): boolean {
|
||||||
|
// 使用正则表达式匹配视频文件类型的URL
|
||||||
|
const videoRegex = /\.(ogm|webm|ogv|asx|m4v|mp4|mpg|mpeg|dat|asf|avi|rm|rmvb|mov|wmv|flv|mkv|video)(?=$|[?#])/i
|
||||||
|
return videoRegex.test(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查提供的URL是否为图片URL。
|
||||||
|
* @param url 待检查的URL字符串。
|
||||||
|
* @returns 返回一个布尔值,如果URL是图片格式,则为true;否则为false。
|
||||||
|
*/
|
||||||
|
export function isImageUrl(url: string): boolean {
|
||||||
|
// 使用正则表达式匹配图片URL
|
||||||
|
const imageRegex = /\.(xbm|tif|pjp|apng|svgz|jpeg|jpg|heif|ico|tiff|heic|pjpeg|avif|gif|png|svg|webp|jfif|bmp|dpg|image)(?=$|[?#])/i
|
||||||
|
return imageRegex.test(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断环境是否是H5
|
||||||
|
*/
|
||||||
|
export const isH5 = (() => {
|
||||||
|
let isH5 = false
|
||||||
|
// #ifdef H5
|
||||||
|
isH5 = true
|
||||||
|
// #endif
|
||||||
|
return isH5
|
||||||
|
})()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 剔除对象中的某些属性
|
||||||
|
* @param obj
|
||||||
|
* @param predicate
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function omitBy<O extends Record<string, any>>(obj: O, predicate: (value: any, key: keyof O) => boolean): Partial<O> {
|
||||||
|
const newObj = deepClone(obj)
|
||||||
|
Object.keys(newObj).forEach((key) => predicate(newObj[key], key) && delete newObj[key]) // 遍历对象的键,删除值为不满足predicate的字段
|
||||||
|
return newObj
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 缓动函数,用于在动画或过渡效果中根据时间参数计算当前值
|
||||||
|
* @param t 当前时间,通常是从动画开始经过的时间
|
||||||
|
* @param b 初始值,动画属性的初始值
|
||||||
|
* @param c 变化量,动画属性的目标值与初始值的差值
|
||||||
|
* @param d 持续时间,动画持续的总时间长度
|
||||||
|
* @returns 计算出的当前值
|
||||||
|
*/
|
||||||
|
export function easingFn(t: number = 0, b: number = 0, c: number = 0, d: number = 0): number {
|
||||||
|
return (c * (-Math.pow(2, (-10 * t) / d) + 1) * 1024) / 1023 + b
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从数组中寻找最接近目标值的元素
|
||||||
|
*
|
||||||
|
* @param arr 数组
|
||||||
|
* @param target 目标值
|
||||||
|
* @returns 最接近目标值的元素
|
||||||
|
*/
|
||||||
|
export function closest(arr: number[], target: number) {
|
||||||
|
return arr.reduce((prev, curr) => (Math.abs(curr - target) < Math.abs(prev - target) ? curr : prev))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统信息接口,包含项目中实际使用的字段
|
||||||
|
*/
|
||||||
|
export interface SystemInfo {
|
||||||
|
/** 窗口宽度 */
|
||||||
|
windowWidth: number
|
||||||
|
/** 窗口高度 */
|
||||||
|
windowHeight: number
|
||||||
|
/** 窗口顶部位置 */
|
||||||
|
windowTop: number
|
||||||
|
/** 设备像素比 */
|
||||||
|
pixelRatio: number
|
||||||
|
/** 平台信息 */
|
||||||
|
platform: string
|
||||||
|
/** 主题模式 */
|
||||||
|
theme?: string
|
||||||
|
/** 状态栏高度 */
|
||||||
|
statusBarHeight?: number
|
||||||
|
/** 安全区域信息 */
|
||||||
|
safeArea?: UniApp.SafeArea
|
||||||
|
/** 屏幕高度 */
|
||||||
|
screenHeight: number
|
||||||
|
/** 安全区域插入信息 */
|
||||||
|
safeAreaInsets?: UniApp.SafeAreaInsets
|
||||||
|
// 未尽字段
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 兼容微信小程序端获取系统信息的方法
|
||||||
|
* 在微信小程序端使用新的API替代getSystemInfoSync,在其他端仍然使用getSystemInfoSync
|
||||||
|
* @returns 系统信息对象
|
||||||
|
*/
|
||||||
|
export function getSystemInfo(): SystemInfo {
|
||||||
|
let systemInfo: SystemInfo
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
try {
|
||||||
|
// const systemSetting = uni.getSystemSetting() // 暂时不需要
|
||||||
|
const deviceInfo = uni.getDeviceInfo()
|
||||||
|
const windowInfo = uni.getWindowInfo()
|
||||||
|
const appBaseInfo = uni.getAppBaseInfo()
|
||||||
|
systemInfo = {
|
||||||
|
...deviceInfo,
|
||||||
|
...windowInfo,
|
||||||
|
...appBaseInfo
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('获取系统信息失败,降级使用uni.getSystemInfoSync:', error)
|
||||||
|
// 降级处理,使用原来的方法
|
||||||
|
systemInfo = uni.getSystemInfoSync()
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
|
// #ifndef MP-WEIXIN
|
||||||
|
systemInfo = uni.getSystemInfoSync()
|
||||||
|
// #endif
|
||||||
|
return systemInfo
|
||||||
|
}
|
||||||
12
node_modules/wot-design-uni/components/composables/index.ts
generated
vendored
Normal file
12
node_modules/wot-design-uni/components/composables/index.ts
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
export { useCell } from './useCell'
|
||||||
|
export { useChildren, flattenVNodes, sortChildren } from './useChildren'
|
||||||
|
export { useCountDown } from './useCountDown'
|
||||||
|
export { useLockScroll } from './useLockScroll'
|
||||||
|
export { useParent } from './useParent'
|
||||||
|
export { usePopover } from './usePopover'
|
||||||
|
export { useQueue } from './useQueue'
|
||||||
|
export { useRaf } from './useRaf'
|
||||||
|
export { useTouch } from './useTouch'
|
||||||
|
export { useTranslate } from './useTranslate'
|
||||||
|
export { useUpload } from './useUpload'
|
||||||
|
export { useConfigProvider } from './useConfigProvider'
|
||||||
13
node_modules/wot-design-uni/components/composables/useCell.ts
generated
vendored
Normal file
13
node_modules/wot-design-uni/components/composables/useCell.ts
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { computed } from 'vue'
|
||||||
|
import { useParent } from './useParent'
|
||||||
|
import { CELL_GROUP_KEY } from '../wd-cell-group/types'
|
||||||
|
|
||||||
|
export function useCell() {
|
||||||
|
const { parent: cellGroup, index } = useParent(CELL_GROUP_KEY)
|
||||||
|
|
||||||
|
const border = computed(() => {
|
||||||
|
return cellGroup && cellGroup.props.border && index.value
|
||||||
|
})
|
||||||
|
|
||||||
|
return { border }
|
||||||
|
}
|
||||||
114
node_modules/wot-design-uni/components/composables/useChildren.ts
generated
vendored
Normal file
114
node_modules/wot-design-uni/components/composables/useChildren.ts
generated
vendored
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
import {
|
||||||
|
provide,
|
||||||
|
reactive,
|
||||||
|
getCurrentInstance,
|
||||||
|
type VNode,
|
||||||
|
type InjectionKey,
|
||||||
|
type VNodeNormalizedChildren,
|
||||||
|
type ComponentPublicInstance,
|
||||||
|
type ComponentInternalInstance
|
||||||
|
} from 'vue'
|
||||||
|
|
||||||
|
// 小程序端不支持从vue导出的isVNode方法,参考uni-mp-vue的实现
|
||||||
|
function isVNode(value: any): value is VNode {
|
||||||
|
return value ? value.__v_isVNode === true : false
|
||||||
|
}
|
||||||
|
|
||||||
|
export function flattenVNodes(children: VNode) {
|
||||||
|
const result: VNode[] = []
|
||||||
|
|
||||||
|
const traverse = (children: VNode | VNodeNormalizedChildren) => {
|
||||||
|
const vNode = Array.isArray(children) ? children : [children]
|
||||||
|
vNode.forEach((child) => {
|
||||||
|
if (Array.isArray(child)) {
|
||||||
|
traverse(child)
|
||||||
|
} else if (isVNode(child) && child.component?.subTree) {
|
||||||
|
result.push(child)
|
||||||
|
traverse(child.component.subTree)
|
||||||
|
} else if (isVNode(child) && Array.isArray(child.children)) {
|
||||||
|
traverse(child.children)
|
||||||
|
} else if (isVNode(child)) {
|
||||||
|
result.push(child)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
traverse(children)
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
const findVNodeIndex = (vnodes: VNode[], vnode: VNode) => {
|
||||||
|
const index = vnodes.indexOf(vnode)
|
||||||
|
if (index === -1) {
|
||||||
|
return vnodes.findIndex((item) => vnode.key !== undefined && vnode.key !== null && item.type === vnode.type && item.key === vnode.key)
|
||||||
|
}
|
||||||
|
return index
|
||||||
|
}
|
||||||
|
|
||||||
|
// sort children instances by vnodes order
|
||||||
|
export function sortChildren(
|
||||||
|
parent: ComponentInternalInstance,
|
||||||
|
publicChildren: ComponentPublicInstance[],
|
||||||
|
internalChildren: ComponentInternalInstance[]
|
||||||
|
) {
|
||||||
|
const vnodes = parent && parent.subTree && parent.subTree.children ? flattenVNodes(parent.subTree) : []
|
||||||
|
internalChildren.sort((a, b) => findVNodeIndex(vnodes, a.vnode) - findVNodeIndex(vnodes, b.vnode))
|
||||||
|
|
||||||
|
const orderedPublicChildren = internalChildren.map((item) => item.proxy!)
|
||||||
|
|
||||||
|
publicChildren.sort((a, b) => {
|
||||||
|
const getIndex = (comp: ComponentPublicInstance) => {
|
||||||
|
const uid = comp.$.uid
|
||||||
|
return orderedPublicChildren.findIndex((i) => i.$.uid === uid)
|
||||||
|
}
|
||||||
|
|
||||||
|
const indexA = getIndex(a)
|
||||||
|
const indexB = getIndex(b)
|
||||||
|
return indexA - indexB
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useChildren<
|
||||||
|
// eslint-disable-next-line
|
||||||
|
Child extends ComponentPublicInstance = ComponentPublicInstance<{}, any>,
|
||||||
|
ProvideValue = never
|
||||||
|
>(key: InjectionKey<ProvideValue>) {
|
||||||
|
const publicChildren: Child[] = reactive([])
|
||||||
|
const internalChildren: ComponentInternalInstance[] = reactive([])
|
||||||
|
const parent = getCurrentInstance()!
|
||||||
|
|
||||||
|
const linkChildren = (value?: ProvideValue) => {
|
||||||
|
const link = (child: ComponentInternalInstance) => {
|
||||||
|
if (child.proxy) {
|
||||||
|
internalChildren.push(child)
|
||||||
|
publicChildren.push(child.proxy as Child)
|
||||||
|
sortChildren(parent, publicChildren, internalChildren)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const unlink = (child: ComponentInternalInstance) => {
|
||||||
|
const index = internalChildren.indexOf(child)
|
||||||
|
publicChildren.splice(index, 1)
|
||||||
|
internalChildren.splice(index, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
provide(
|
||||||
|
key,
|
||||||
|
Object.assign(
|
||||||
|
{
|
||||||
|
link,
|
||||||
|
unlink,
|
||||||
|
children: publicChildren,
|
||||||
|
internalChildren
|
||||||
|
},
|
||||||
|
value
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
children: publicChildren,
|
||||||
|
linkChildren
|
||||||
|
}
|
||||||
|
}
|
||||||
31
node_modules/wot-design-uni/components/composables/useConfigProvider.ts
generated
vendored
Normal file
31
node_modules/wot-design-uni/components/composables/useConfigProvider.ts
generated
vendored
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import { computed, provide, unref, type Ref } from 'vue'
|
||||||
|
import { type ConfigProviderThemeVars } from '../wd-config-provider/types'
|
||||||
|
import { objToStyle } from '../common/util'
|
||||||
|
|
||||||
|
export const USE_CONFIG_PROVIDER_KEY = '__CONFIG_PROVIDER__'
|
||||||
|
|
||||||
|
export const kebabCase = (str: string): string => {
|
||||||
|
str = str.replace(str.charAt(0), str.charAt(0).toLocaleLowerCase())
|
||||||
|
return str.replace(/([a-z])([A-Z])/g, (_, p1, p2) => p1 + '-' + p2.toLowerCase())
|
||||||
|
}
|
||||||
|
|
||||||
|
export const mapThemeVarsToCSSVars = (themeVars: Record<string, string>) => {
|
||||||
|
if (!themeVars) return
|
||||||
|
const cssVars: Record<string, string> = {}
|
||||||
|
Object.keys(themeVars).forEach((key) => {
|
||||||
|
cssVars[`--wot-${kebabCase(key)}`] = themeVars[key]
|
||||||
|
})
|
||||||
|
|
||||||
|
return cssVars
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useConfigProvider({ themeVars }: { themeVars: ConfigProviderThemeVars | Ref<ConfigProviderThemeVars> }) {
|
||||||
|
const themeStyle = computed(() => {
|
||||||
|
const styleObj = mapThemeVarsToCSSVars(unref(themeVars))
|
||||||
|
return styleObj ? `${objToStyle(styleObj)}` : ''
|
||||||
|
})
|
||||||
|
|
||||||
|
provide(USE_CONFIG_PROVIDER_KEY, {
|
||||||
|
themeStyle
|
||||||
|
})
|
||||||
|
}
|
||||||
138
node_modules/wot-design-uni/components/composables/useCountDown.ts
generated
vendored
Normal file
138
node_modules/wot-design-uni/components/composables/useCountDown.ts
generated
vendored
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
import { ref, computed, onBeforeUnmount } from 'vue'
|
||||||
|
import { isDef } from '../common/util'
|
||||||
|
import { useRaf } from './useRaf'
|
||||||
|
|
||||||
|
// 定义倒计时时间的数据结构
|
||||||
|
export type CurrentTime = {
|
||||||
|
days: number
|
||||||
|
hours: number
|
||||||
|
total: number
|
||||||
|
minutes: number
|
||||||
|
seconds: number
|
||||||
|
milliseconds: number
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定义倒计时的配置项
|
||||||
|
export type UseCountDownOptions = {
|
||||||
|
time: number // 倒计时总时间,单位为毫秒
|
||||||
|
millisecond?: boolean // 是否开启毫秒级倒计时,默认为 false
|
||||||
|
onChange?: (current: CurrentTime) => void // 倒计时每次变化时的回调函数
|
||||||
|
onFinish?: () => void // 倒计时结束时的回调函数
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定义常量
|
||||||
|
const SECOND = 1000
|
||||||
|
const MINUTE = 60 * SECOND
|
||||||
|
const HOUR = 60 * MINUTE
|
||||||
|
const DAY = 24 * HOUR
|
||||||
|
|
||||||
|
// 将时间转换为倒计时数据结构
|
||||||
|
function parseTime(time: number): CurrentTime {
|
||||||
|
const days = Math.floor(time / DAY)
|
||||||
|
const hours = Math.floor((time % DAY) / HOUR)
|
||||||
|
const minutes = Math.floor((time % HOUR) / MINUTE)
|
||||||
|
const seconds = Math.floor((time % MINUTE) / SECOND)
|
||||||
|
const milliseconds = Math.floor(time % SECOND)
|
||||||
|
|
||||||
|
return {
|
||||||
|
total: time,
|
||||||
|
days,
|
||||||
|
hours,
|
||||||
|
minutes,
|
||||||
|
seconds,
|
||||||
|
milliseconds
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断两个时间是否在同一秒内
|
||||||
|
function isSameSecond(time1: number, time2: number): boolean {
|
||||||
|
return Math.floor(time1 / 1000) === Math.floor(time2 / 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定义 useCountDown 函数
|
||||||
|
export function useCountDown(options: UseCountDownOptions) {
|
||||||
|
let endTime: number // 结束时间
|
||||||
|
let counting: boolean // 是否计时中
|
||||||
|
|
||||||
|
const { start: startRaf, cancel: cancelRaf } = useRaf(tick)
|
||||||
|
|
||||||
|
const remain = ref(options.time) // 剩余时间
|
||||||
|
const current = computed(() => parseTime(remain.value)) // 当前倒计时数据
|
||||||
|
|
||||||
|
// 暂停倒计时
|
||||||
|
const pause = () => {
|
||||||
|
counting = false
|
||||||
|
cancelRaf()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取当前剩余时间
|
||||||
|
const getCurrentRemain = () => Math.max(endTime - Date.now(), 0)
|
||||||
|
|
||||||
|
// 设置剩余时间
|
||||||
|
const setRemain = (value: number) => {
|
||||||
|
remain.value = value
|
||||||
|
isDef(options.onChange) && options.onChange(current.value)
|
||||||
|
if (value === 0) {
|
||||||
|
pause()
|
||||||
|
isDef(options.onFinish) && options.onFinish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 每毫秒更新一次倒计时
|
||||||
|
const microTick = () => {
|
||||||
|
if (counting) {
|
||||||
|
setRemain(getCurrentRemain())
|
||||||
|
if (remain.value > 0) {
|
||||||
|
startRaf()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 每秒更新一次倒计时
|
||||||
|
const macroTick = () => {
|
||||||
|
if (counting) {
|
||||||
|
const remainRemain = getCurrentRemain()
|
||||||
|
if (!isSameSecond(remainRemain, remain.value) || remainRemain === 0) {
|
||||||
|
setRemain(remainRemain)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (remain.value > 0) {
|
||||||
|
startRaf()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据配置项选择更新方式
|
||||||
|
function tick() {
|
||||||
|
if (options.millisecond) {
|
||||||
|
microTick()
|
||||||
|
} else {
|
||||||
|
macroTick()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 开始倒计时
|
||||||
|
const start = () => {
|
||||||
|
if (!counting) {
|
||||||
|
endTime = Date.now() + remain.value
|
||||||
|
counting = true
|
||||||
|
startRaf()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置倒计时
|
||||||
|
const reset = (totalTime: number = options.time) => {
|
||||||
|
pause()
|
||||||
|
remain.value = totalTime
|
||||||
|
}
|
||||||
|
|
||||||
|
// 在组件卸载前暂停倒计时
|
||||||
|
onBeforeUnmount(pause)
|
||||||
|
|
||||||
|
return {
|
||||||
|
start,
|
||||||
|
pause,
|
||||||
|
reset,
|
||||||
|
current
|
||||||
|
}
|
||||||
|
}
|
||||||
37
node_modules/wot-design-uni/components/composables/useLockScroll.ts
generated
vendored
Normal file
37
node_modules/wot-design-uni/components/composables/useLockScroll.ts
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import { onBeforeUnmount, onDeactivated, ref, watch } from 'vue'
|
||||||
|
|
||||||
|
export function useLockScroll(shouldLock: () => boolean) {
|
||||||
|
const scrollLockCount = ref(0)
|
||||||
|
|
||||||
|
const lock = () => {
|
||||||
|
if (scrollLockCount.value === 0) {
|
||||||
|
document.getElementsByTagName('body')[0].style.overflow = 'hidden'
|
||||||
|
}
|
||||||
|
scrollLockCount.value++
|
||||||
|
}
|
||||||
|
|
||||||
|
const unlock = () => {
|
||||||
|
if (scrollLockCount.value > 0) {
|
||||||
|
scrollLockCount.value--
|
||||||
|
if (scrollLockCount.value === 0) {
|
||||||
|
document.getElementsByTagName('body')[0].style.overflow = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const destroy = () => {
|
||||||
|
shouldLock() && unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(shouldLock, (value) => {
|
||||||
|
value ? lock() : unlock()
|
||||||
|
})
|
||||||
|
|
||||||
|
onDeactivated(destroy)
|
||||||
|
onBeforeUnmount(destroy)
|
||||||
|
|
||||||
|
return {
|
||||||
|
lock,
|
||||||
|
unlock
|
||||||
|
}
|
||||||
|
}
|
||||||
41
node_modules/wot-design-uni/components/composables/useParent.ts
generated
vendored
Normal file
41
node_modules/wot-design-uni/components/composables/useParent.ts
generated
vendored
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import {
|
||||||
|
ref,
|
||||||
|
inject,
|
||||||
|
computed,
|
||||||
|
onUnmounted,
|
||||||
|
type InjectionKey,
|
||||||
|
getCurrentInstance,
|
||||||
|
type ComponentPublicInstance,
|
||||||
|
type ComponentInternalInstance
|
||||||
|
} from 'vue'
|
||||||
|
|
||||||
|
type ParentProvide<T> = T & {
|
||||||
|
link(child: ComponentInternalInstance): void
|
||||||
|
unlink(child: ComponentInternalInstance): void
|
||||||
|
children: ComponentPublicInstance[]
|
||||||
|
internalChildren: ComponentInternalInstance[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useParent<T>(key: InjectionKey<ParentProvide<T>>) {
|
||||||
|
const parent = inject(key, null)
|
||||||
|
|
||||||
|
if (parent) {
|
||||||
|
const instance = getCurrentInstance()!
|
||||||
|
const { link, unlink, internalChildren } = parent
|
||||||
|
|
||||||
|
link(instance)
|
||||||
|
onUnmounted(() => unlink(instance))
|
||||||
|
|
||||||
|
const index = computed(() => internalChildren.indexOf(instance))
|
||||||
|
|
||||||
|
return {
|
||||||
|
parent,
|
||||||
|
index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
parent: null,
|
||||||
|
index: ref(-1)
|
||||||
|
}
|
||||||
|
}
|
||||||
176
node_modules/wot-design-uni/components/composables/usePopover.ts
generated
vendored
Normal file
176
node_modules/wot-design-uni/components/composables/usePopover.ts
generated
vendored
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
import { getCurrentInstance, ref } from 'vue'
|
||||||
|
import { getRect, isObj } from '../common/util'
|
||||||
|
|
||||||
|
export function usePopover(visibleArrow = true) {
|
||||||
|
const { proxy } = getCurrentInstance() as any
|
||||||
|
const popStyle = ref<string>('')
|
||||||
|
const arrowStyle = ref<string>('')
|
||||||
|
const showStyle = ref<string>('')
|
||||||
|
const arrowClass = ref<string>('')
|
||||||
|
const popWidth = ref<number>(0)
|
||||||
|
const popHeight = ref<number>(0)
|
||||||
|
const left = ref<number>(0)
|
||||||
|
const bottom = ref<number>(0)
|
||||||
|
const width = ref<number>(0)
|
||||||
|
const height = ref<number>(0)
|
||||||
|
const top = ref<number>(0)
|
||||||
|
|
||||||
|
function noop() {}
|
||||||
|
|
||||||
|
function init(
|
||||||
|
placement:
|
||||||
|
| 'top'
|
||||||
|
| 'top-start'
|
||||||
|
| 'top-end'
|
||||||
|
| 'bottom'
|
||||||
|
| 'bottom-start'
|
||||||
|
| 'bottom-end'
|
||||||
|
| 'left'
|
||||||
|
| 'left-start'
|
||||||
|
| 'left-end'
|
||||||
|
| 'right'
|
||||||
|
| 'right-start'
|
||||||
|
| 'right-end',
|
||||||
|
visibleArrow: boolean,
|
||||||
|
selector: string
|
||||||
|
) {
|
||||||
|
// 初始化 class
|
||||||
|
if (visibleArrow) {
|
||||||
|
const arrowClassArr = [
|
||||||
|
`wd-${selector}__arrow`,
|
||||||
|
placement === 'bottom' || placement === 'bottom-start' || placement === 'bottom-end' ? `wd-${selector}__arrow-up` : '',
|
||||||
|
placement === 'left' || placement === 'left-start' || placement === 'left-end' ? `wd-${selector}__arrow-right` : '',
|
||||||
|
placement === 'right' || placement === 'right-start' || placement === 'right-end' ? `wd-${selector}__arrow-left` : '',
|
||||||
|
placement === 'top' || placement === 'top-start' || placement === 'top-end' ? `wd-${selector}__arrow-down` : ''
|
||||||
|
]
|
||||||
|
arrowClass.value = arrowClassArr.join(' ')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化数据获取
|
||||||
|
getRect('#target', false, proxy).then((rect) => {
|
||||||
|
if (!rect) return
|
||||||
|
left.value = rect.left as number
|
||||||
|
bottom.value = rect.bottom as number
|
||||||
|
width.value = rect.width as number
|
||||||
|
height.value = rect.height as number
|
||||||
|
top.value = rect.top as number
|
||||||
|
})
|
||||||
|
// 用透明度可在初始化时获取到pop尺寸
|
||||||
|
getRect('#pos', false, proxy).then((rect) => {
|
||||||
|
if (!rect) return
|
||||||
|
popWidth.value = rect.width as number
|
||||||
|
popHeight.value = rect.height as number
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function control(
|
||||||
|
placement:
|
||||||
|
| 'top'
|
||||||
|
| 'top-start'
|
||||||
|
| 'top-end'
|
||||||
|
| 'bottom'
|
||||||
|
| 'bottom-start'
|
||||||
|
| 'bottom-end'
|
||||||
|
| 'left'
|
||||||
|
| 'left-start'
|
||||||
|
| 'left-end'
|
||||||
|
| 'right'
|
||||||
|
| 'right-start'
|
||||||
|
| 'right-end',
|
||||||
|
offset: number | number[] | Record<'x' | 'y', number>
|
||||||
|
) {
|
||||||
|
// arrow size
|
||||||
|
const arrowSize = visibleArrow ? 9 : 0
|
||||||
|
// 上下位(纵轴)对应的距离左边的距离
|
||||||
|
const verticalX = width.value / 2
|
||||||
|
// 上下位(纵轴)对应的距离底部的距离
|
||||||
|
const verticalY = arrowSize + height.value + 5
|
||||||
|
// 左右位(横轴)对应的距离左边的距离
|
||||||
|
const horizontalX = width.value + arrowSize + 5
|
||||||
|
// 左右位(横轴)对应的距离底部的距离
|
||||||
|
const horizontalY = height.value / 2
|
||||||
|
|
||||||
|
let offsetX = 0
|
||||||
|
let offsetY = 0
|
||||||
|
if (Array.isArray(offset)) {
|
||||||
|
offsetX = (verticalX - 17 > 0 ? 0 : verticalX - 25) + offset[0]
|
||||||
|
offsetY = (horizontalY - 17 > 0 ? 0 : horizontalY - 25) + (offset[1] ? offset[1] : offset[0])
|
||||||
|
} else if (isObj(offset)) {
|
||||||
|
offsetX = (verticalX - 17 > 0 ? 0 : verticalX - 25) + offset.x
|
||||||
|
offsetY = (horizontalY - 17 > 0 ? 0 : horizontalY - 25) + offset.y
|
||||||
|
} else {
|
||||||
|
offsetX = (verticalX - 17 > 0 ? 0 : verticalX - 25) + offset
|
||||||
|
offsetY = (horizontalY - 17 > 0 ? 0 : horizontalY - 25) + offset
|
||||||
|
}
|
||||||
|
// const offsetX = (verticalX - 17 > 0 ? 0 : verticalX - 25) + offset
|
||||||
|
// const offsetY = (horizontalY - 17 > 0 ? 0 : horizontalY - 25) + offset
|
||||||
|
|
||||||
|
const placements = new Map([
|
||||||
|
// 上
|
||||||
|
['top', [`left: ${verticalX}px; bottom: ${verticalY}px; transform: translateX(-50%);`, 'left: 50%;']],
|
||||||
|
[
|
||||||
|
'top-start',
|
||||||
|
[
|
||||||
|
`left: ${offsetX}px; bottom: ${verticalY}px;`,
|
||||||
|
`left: ${(popWidth.value >= width.value ? width.value / 2 : popWidth.value - 25) - offsetX}px;`
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'top-end',
|
||||||
|
[
|
||||||
|
`right: ${offsetX}px; bottom: ${verticalY}px;`,
|
||||||
|
`right: ${(popWidth.value >= width.value ? width.value / 2 : popWidth.value - 25) - offsetX}px; transform: translateX(50%);`
|
||||||
|
]
|
||||||
|
],
|
||||||
|
// 下
|
||||||
|
['bottom', [`left: ${verticalX}px; top: ${verticalY}px; transform: translateX(-50%);`, 'left: 50%;']],
|
||||||
|
[
|
||||||
|
'bottom-start',
|
||||||
|
[`left: ${offsetX}px; top: ${verticalY}px;`, `left: ${(popWidth.value >= width.value ? width.value / 2 : popWidth.value - 25) - offsetX}px;`]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'bottom-end',
|
||||||
|
[
|
||||||
|
`right: ${offsetX}px; top: ${verticalY}px;`,
|
||||||
|
`right: ${(popWidth.value >= width.value ? width.value / 2 : popWidth.value - 25) - offsetX}px; transform: translateX(50%);`
|
||||||
|
]
|
||||||
|
],
|
||||||
|
// 左
|
||||||
|
['left', [`right: ${horizontalX}px; top: ${horizontalY}px; transform: translateY(-50%);`, 'top: 50%']],
|
||||||
|
[
|
||||||
|
'left-start',
|
||||||
|
[
|
||||||
|
`right: ${horizontalX}px; top: ${offsetY}px;`,
|
||||||
|
`top: ${(popHeight.value >= height.value ? height.value / 2 : popHeight.value - 20) - offsetY}px;`
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'left-end',
|
||||||
|
[
|
||||||
|
`right: ${horizontalX}px; bottom: ${offsetY}px;`,
|
||||||
|
`bottom: ${(popHeight.value >= height.value ? height.value / 2 : popHeight.value - 20) - offsetY}px; transform: translateY(50%);`
|
||||||
|
]
|
||||||
|
],
|
||||||
|
// 右
|
||||||
|
['right', [`left: ${horizontalX}px; top: ${horizontalY}px; transform: translateY(-50%);`, 'top: 50%']],
|
||||||
|
[
|
||||||
|
'right-start',
|
||||||
|
[
|
||||||
|
`left: ${horizontalX}px; top: ${offsetY}px;`,
|
||||||
|
`top: ${(popHeight.value >= height.value ? height.value / 2 : popHeight.value - 20) - offsetY}px;`
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'right-end',
|
||||||
|
[
|
||||||
|
`left: ${horizontalX}px; bottom: ${offsetY}px;`,
|
||||||
|
`bottom: ${(popHeight.value >= height.value ? height.value / 2 : popHeight.value - 20) - offsetY}px; transform: translateY(50%);`
|
||||||
|
]
|
||||||
|
]
|
||||||
|
])
|
||||||
|
popStyle.value = placements.get(placement)![0]
|
||||||
|
arrowStyle.value = placements.get(placement)![1]
|
||||||
|
}
|
||||||
|
|
||||||
|
return { popStyle, arrowStyle, showStyle, arrowClass, init, control, noop }
|
||||||
|
}
|
||||||
52
node_modules/wot-design-uni/components/composables/useQueue.ts
generated
vendored
Normal file
52
node_modules/wot-design-uni/components/composables/useQueue.ts
generated
vendored
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import { type Ref, provide, ref } from 'vue'
|
||||||
|
|
||||||
|
export const queueKey = '__QUEUE_KEY__'
|
||||||
|
|
||||||
|
export interface Queue {
|
||||||
|
queue: Ref<any[]>
|
||||||
|
pushToQueue: (comp: any) => void
|
||||||
|
removeFromQueue: (comp: any) => void
|
||||||
|
closeOther: (comp: any) => void
|
||||||
|
closeOutside: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useQueue() {
|
||||||
|
const queue = ref<any[]>([])
|
||||||
|
|
||||||
|
function pushToQueue(comp: any) {
|
||||||
|
queue.value.push(comp)
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeFromQueue(comp: any) {
|
||||||
|
queue.value = queue.value.filter((item) => {
|
||||||
|
return item.$.uid !== comp.$.uid
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeOther(comp: any) {
|
||||||
|
queue.value.forEach((item) => {
|
||||||
|
if (item.$.uid !== comp.$.uid) {
|
||||||
|
item.$.exposed.close()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeOutside() {
|
||||||
|
queue.value.forEach((item) => {
|
||||||
|
item.$.exposed.close()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
provide(queueKey, {
|
||||||
|
queue,
|
||||||
|
pushToQueue,
|
||||||
|
removeFromQueue,
|
||||||
|
closeOther,
|
||||||
|
closeOutside
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
closeOther,
|
||||||
|
closeOutside
|
||||||
|
}
|
||||||
|
}
|
||||||
37
node_modules/wot-design-uni/components/composables/useRaf.ts
generated
vendored
Normal file
37
node_modules/wot-design-uni/components/composables/useRaf.ts
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import { ref, onUnmounted } from 'vue'
|
||||||
|
import { isDef, isH5, isNumber } from '../common/util'
|
||||||
|
|
||||||
|
// 定义回调函数类型
|
||||||
|
type RafCallback = (time: number) => void
|
||||||
|
|
||||||
|
export function useRaf(callback: RafCallback) {
|
||||||
|
const requestRef = ref<number | null | ReturnType<typeof setTimeout>>(null)
|
||||||
|
|
||||||
|
// 启动动画帧
|
||||||
|
const start = () => {
|
||||||
|
const handle = (time: number) => {
|
||||||
|
callback(time)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isH5) {
|
||||||
|
requestRef.value = requestAnimationFrame(handle)
|
||||||
|
} else {
|
||||||
|
requestRef.value = setTimeout(() => handle(Date.now()), 1000 / 30)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取消动画帧
|
||||||
|
const cancel = () => {
|
||||||
|
if (isH5 && isNumber(requestRef.value)) {
|
||||||
|
cancelAnimationFrame(requestRef.value!)
|
||||||
|
} else if (isDef(requestRef.value)) {
|
||||||
|
clearTimeout(requestRef.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
cancel()
|
||||||
|
})
|
||||||
|
|
||||||
|
return { start, cancel }
|
||||||
|
}
|
||||||
43
node_modules/wot-design-uni/components/composables/useTouch.ts
generated
vendored
Normal file
43
node_modules/wot-design-uni/components/composables/useTouch.ts
generated
vendored
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
export function useTouch() {
|
||||||
|
const direction = ref<string>('')
|
||||||
|
const deltaX = ref<number>(0)
|
||||||
|
const deltaY = ref<number>(0)
|
||||||
|
const offsetX = ref<number>(0)
|
||||||
|
const offsetY = ref<number>(0)
|
||||||
|
const startX = ref<number>(0)
|
||||||
|
const startY = ref<number>(0)
|
||||||
|
|
||||||
|
function touchStart(event: any) {
|
||||||
|
const touch = event.touches[0]
|
||||||
|
direction.value = ''
|
||||||
|
deltaX.value = 0
|
||||||
|
deltaY.value = 0
|
||||||
|
offsetX.value = 0
|
||||||
|
offsetY.value = 0
|
||||||
|
startX.value = touch.clientX
|
||||||
|
startY.value = touch.clientY
|
||||||
|
}
|
||||||
|
|
||||||
|
function touchMove(event: any) {
|
||||||
|
const touch = event.touches[0]
|
||||||
|
deltaX.value = touch.clientX - startX.value
|
||||||
|
deltaY.value = touch.clientY - startY.value
|
||||||
|
offsetX.value = Math.abs(deltaX.value)
|
||||||
|
offsetY.value = Math.abs(deltaY.value)
|
||||||
|
direction.value = offsetX.value > offsetY.value ? 'horizontal' : offsetX.value < offsetY.value ? 'vertical' : ''
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
touchStart,
|
||||||
|
touchMove,
|
||||||
|
direction,
|
||||||
|
deltaX,
|
||||||
|
deltaY,
|
||||||
|
offsetX,
|
||||||
|
offsetY,
|
||||||
|
startX,
|
||||||
|
startY
|
||||||
|
}
|
||||||
|
}
|
||||||
12
node_modules/wot-design-uni/components/composables/useTranslate.ts
generated
vendored
Normal file
12
node_modules/wot-design-uni/components/composables/useTranslate.ts
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { camelCase, getPropByPath, isDef, isFunction } from '../common/util'
|
||||||
|
import Locale from '../../locale'
|
||||||
|
|
||||||
|
export const useTranslate = (name?: string) => {
|
||||||
|
const prefix = name ? camelCase(name) + '.' : ''
|
||||||
|
const translate = (key: string, ...args: unknown[]) => {
|
||||||
|
const currentMessages = Locale.messages()
|
||||||
|
const message = getPropByPath(currentMessages, prefix + key)
|
||||||
|
return isFunction(message) ? message(...args) : isDef(message) ? message : `${prefix}${key}`
|
||||||
|
}
|
||||||
|
return { translate }
|
||||||
|
}
|
||||||
364
node_modules/wot-design-uni/components/composables/useUpload.ts
generated
vendored
Normal file
364
node_modules/wot-design-uni/components/composables/useUpload.ts
generated
vendored
Normal file
@@ -0,0 +1,364 @@
|
|||||||
|
import { isArray, isDef, isFunction } from '../common/util'
|
||||||
|
import type { ChooseFile, ChooseFileOption, UploadFileItem, UploadMethod, UploadStatusType } from '../wd-upload/types'
|
||||||
|
|
||||||
|
export const UPLOAD_STATUS: Record<string, UploadStatusType> = {
|
||||||
|
PENDING: 'pending',
|
||||||
|
LOADING: 'loading',
|
||||||
|
SUCCESS: 'success',
|
||||||
|
FAIL: 'fail'
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UseUploadReturn {
|
||||||
|
// 开始上传文件
|
||||||
|
startUpload: (file: UploadFileItem, options: UseUploadOptions) => UniApp.UploadTask | void | Promise<void>
|
||||||
|
// 中断上传
|
||||||
|
abort: (task?: UniApp.UploadTask) => void
|
||||||
|
// 上传状态常量
|
||||||
|
UPLOAD_STATUS: Record<string, UploadStatusType>
|
||||||
|
// 选择文件
|
||||||
|
chooseFile: (options: ChooseFileOption) => Promise<ChooseFile[]>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UseUploadOptions {
|
||||||
|
// 上传地址
|
||||||
|
action: string
|
||||||
|
// 请求头
|
||||||
|
header?: Record<string, any>
|
||||||
|
// 文件对应的 key
|
||||||
|
name?: string
|
||||||
|
// 其它表单数据
|
||||||
|
formData?: Record<string, any>
|
||||||
|
// 文件类型 仅支付宝支持且在支付宝平台必填
|
||||||
|
fileType?: 'image' | 'video' | 'audio'
|
||||||
|
// 成功状态码
|
||||||
|
statusCode?: number
|
||||||
|
// 文件状态的key
|
||||||
|
statusKey?: string
|
||||||
|
// 自定义上传方法
|
||||||
|
uploadMethod?: UploadMethod
|
||||||
|
// 上传成功回调
|
||||||
|
onSuccess?: (res: UniApp.UploadFileSuccessCallbackResult, file: UploadFileItem, formData: Record<string, any>) => void
|
||||||
|
// 上传失败回调
|
||||||
|
onError?: (res: UniApp.GeneralCallbackResult, file: UploadFileItem, formData: Record<string, any>) => void
|
||||||
|
// 上传进度回调
|
||||||
|
onProgress?: (res: UniApp.OnProgressUpdateResult, file: UploadFileItem) => void
|
||||||
|
// 是否自动中断之前的上传任务
|
||||||
|
abortPrevious?: boolean
|
||||||
|
// 根据文件拓展名过滤(H5支持全部类型过滤,微信小程序支持all和file时过滤,其余平台不支持)
|
||||||
|
extension?: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useUpload(): UseUploadReturn {
|
||||||
|
let currentTask: UniApp.UploadTask | null = null
|
||||||
|
|
||||||
|
// 中断上传
|
||||||
|
const abort = (task?: UniApp.UploadTask) => {
|
||||||
|
if (task) {
|
||||||
|
task.abort()
|
||||||
|
} else if (currentTask) {
|
||||||
|
currentTask.abort()
|
||||||
|
currentTask = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认上传方法
|
||||||
|
*/
|
||||||
|
const defaultUpload: UploadMethod = (file, formData, options) => {
|
||||||
|
// 如果配置了自动中断,则中断之前的上传任务
|
||||||
|
if (options.abortPrevious) {
|
||||||
|
abort()
|
||||||
|
}
|
||||||
|
|
||||||
|
const uploadTask = uni.uploadFile({
|
||||||
|
url: options.action,
|
||||||
|
header: options.header,
|
||||||
|
name: options.name,
|
||||||
|
fileName: options.name,
|
||||||
|
fileType: options.fileType,
|
||||||
|
formData,
|
||||||
|
filePath: file.url,
|
||||||
|
success(res) {
|
||||||
|
if (res.statusCode === options.statusCode) {
|
||||||
|
// 上传成功
|
||||||
|
options.onSuccess(res, file, formData)
|
||||||
|
} else {
|
||||||
|
// 上传失败
|
||||||
|
options.onError({ ...res, errMsg: res.errMsg || '' }, file, formData)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fail(err) {
|
||||||
|
// 上传失败
|
||||||
|
options.onError(err, file, formData)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
currentTask = uploadTask
|
||||||
|
|
||||||
|
// 获取当前文件加载的百分比
|
||||||
|
uploadTask.onProgressUpdate((res) => {
|
||||||
|
options.onProgress(res, file)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 返回上传任务实例,让外部可以控制上传过程
|
||||||
|
return uploadTask
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开始上传文件
|
||||||
|
*/
|
||||||
|
const startUpload = (file: UploadFileItem, options: UseUploadOptions) => {
|
||||||
|
const {
|
||||||
|
uploadMethod,
|
||||||
|
formData = {},
|
||||||
|
action,
|
||||||
|
name = 'file',
|
||||||
|
header = {},
|
||||||
|
fileType = 'image',
|
||||||
|
statusCode = 200,
|
||||||
|
statusKey = 'status',
|
||||||
|
abortPrevious = false
|
||||||
|
} = options
|
||||||
|
|
||||||
|
// 设置上传中状态
|
||||||
|
file[statusKey] = UPLOAD_STATUS.LOADING
|
||||||
|
|
||||||
|
const uploadOptions = {
|
||||||
|
action,
|
||||||
|
header,
|
||||||
|
name,
|
||||||
|
fileName: name,
|
||||||
|
fileType,
|
||||||
|
statusCode,
|
||||||
|
abortPrevious,
|
||||||
|
onSuccess: (res: UniApp.UploadFileSuccessCallbackResult, file: UploadFileItem, formData: Record<string, any>) => {
|
||||||
|
// 更新文件状态
|
||||||
|
file[statusKey] = UPLOAD_STATUS.SUCCESS
|
||||||
|
currentTask = null
|
||||||
|
options.onSuccess?.(res, file, formData)
|
||||||
|
},
|
||||||
|
onError: (error: UniApp.GeneralCallbackResult, file: UploadFileItem, formData: Record<string, any>) => {
|
||||||
|
// 更新文件状态和错误信息
|
||||||
|
file[statusKey] = UPLOAD_STATUS.FAIL
|
||||||
|
file.error = error.errMsg
|
||||||
|
currentTask = null
|
||||||
|
options.onError?.(error, file, formData)
|
||||||
|
},
|
||||||
|
onProgress: (res: UniApp.OnProgressUpdateResult, file: UploadFileItem) => {
|
||||||
|
// 更新上传进度
|
||||||
|
file.percent = res.progress
|
||||||
|
options.onProgress?.(res, file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回上传任务实例,支持外部获取uploadTask进行操作
|
||||||
|
if (isFunction(uploadMethod)) {
|
||||||
|
return uploadMethod(file, formData, uploadOptions)
|
||||||
|
} else {
|
||||||
|
return defaultUpload(file, formData, uploadOptions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化图片信息
|
||||||
|
*/
|
||||||
|
function formatImage(res: UniApp.ChooseImageSuccessCallbackResult): ChooseFile[] {
|
||||||
|
// #ifdef MP-DINGTALK
|
||||||
|
// 钉钉文件在files中
|
||||||
|
res.tempFiles = isDef((res as any).files) ? (res as any).files : res.tempFiles
|
||||||
|
// #endif
|
||||||
|
if (isArray(res.tempFiles)) {
|
||||||
|
return res.tempFiles.map((item: any) => ({
|
||||||
|
path: item.path || '',
|
||||||
|
name: item.name || '',
|
||||||
|
size: item.size,
|
||||||
|
type: 'image',
|
||||||
|
thumb: item.path || ''
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
path: (res.tempFiles as any).path || '',
|
||||||
|
name: (res.tempFiles as any).name || '',
|
||||||
|
size: (res.tempFiles as any).size,
|
||||||
|
type: 'image',
|
||||||
|
thumb: (res.tempFiles as any).path || ''
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化视频信息
|
||||||
|
*/
|
||||||
|
function formatVideo(res: UniApp.ChooseVideoSuccess): ChooseFile[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
path: res.tempFilePath || (res as any).filePath || '',
|
||||||
|
name: res.name || '',
|
||||||
|
size: res.size,
|
||||||
|
type: 'video',
|
||||||
|
thumb: (res as any).thumbTempFilePath || '',
|
||||||
|
duration: res.duration
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化媒体信息
|
||||||
|
*/
|
||||||
|
function formatMedia(res: UniApp.ChooseMediaSuccessCallbackResult): ChooseFile[] {
|
||||||
|
return res.tempFiles.map((item) => ({
|
||||||
|
type: item.fileType,
|
||||||
|
path: item.tempFilePath,
|
||||||
|
thumb: item.fileType === 'video' ? item.thumbTempFilePath : item.tempFilePath,
|
||||||
|
size: item.size,
|
||||||
|
duration: item.duration
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 选择文件
|
||||||
|
*/
|
||||||
|
function chooseFile({
|
||||||
|
multiple,
|
||||||
|
sizeType,
|
||||||
|
sourceType,
|
||||||
|
maxCount,
|
||||||
|
accept,
|
||||||
|
compressed,
|
||||||
|
maxDuration,
|
||||||
|
camera,
|
||||||
|
extension
|
||||||
|
}: ChooseFileOption): Promise<ChooseFile[]> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
switch (accept) {
|
||||||
|
case 'image':
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
uni.chooseMedia({
|
||||||
|
count: multiple ? maxCount : 1,
|
||||||
|
mediaType: ['image'],
|
||||||
|
sourceType,
|
||||||
|
sizeType,
|
||||||
|
camera,
|
||||||
|
success: (res) => resolve(formatMedia(res)),
|
||||||
|
fail: reject
|
||||||
|
})
|
||||||
|
// #endif
|
||||||
|
// #ifndef MP-WEIXIN
|
||||||
|
uni.chooseImage({
|
||||||
|
count: multiple ? maxCount : 1,
|
||||||
|
sizeType,
|
||||||
|
sourceType,
|
||||||
|
// #ifdef H5
|
||||||
|
extension,
|
||||||
|
// #endif
|
||||||
|
success: (res) => resolve(formatImage(res)),
|
||||||
|
fail: reject
|
||||||
|
})
|
||||||
|
// #endif
|
||||||
|
break
|
||||||
|
case 'video':
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
uni.chooseMedia({
|
||||||
|
count: multiple ? maxCount : 1,
|
||||||
|
mediaType: ['video'],
|
||||||
|
sourceType,
|
||||||
|
camera,
|
||||||
|
maxDuration,
|
||||||
|
success: (res) => resolve(formatMedia(res)),
|
||||||
|
fail: reject
|
||||||
|
})
|
||||||
|
// #endif
|
||||||
|
// #ifndef MP-WEIXIN
|
||||||
|
uni.chooseVideo({
|
||||||
|
sourceType,
|
||||||
|
compressed,
|
||||||
|
maxDuration,
|
||||||
|
camera,
|
||||||
|
// #ifdef H5
|
||||||
|
extension,
|
||||||
|
// #endif
|
||||||
|
success: (res) => resolve(formatVideo(res)),
|
||||||
|
fail: reject
|
||||||
|
})
|
||||||
|
// #endif
|
||||||
|
break
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
case 'media':
|
||||||
|
uni.chooseMedia({
|
||||||
|
count: multiple ? maxCount : 1,
|
||||||
|
sourceType,
|
||||||
|
sizeType,
|
||||||
|
camera,
|
||||||
|
maxDuration,
|
||||||
|
success: (res) => resolve(formatMedia(res)),
|
||||||
|
fail: reject
|
||||||
|
})
|
||||||
|
break
|
||||||
|
case 'file':
|
||||||
|
uni.chooseMessageFile({
|
||||||
|
count: multiple ? (isDef(maxCount) ? maxCount : 100) : 1,
|
||||||
|
type: accept,
|
||||||
|
extension,
|
||||||
|
success: (res) => resolve(res.tempFiles),
|
||||||
|
fail: reject
|
||||||
|
})
|
||||||
|
break
|
||||||
|
// #endif
|
||||||
|
case 'all':
|
||||||
|
// #ifdef H5
|
||||||
|
uni.chooseFile({
|
||||||
|
count: multiple ? maxCount : 1,
|
||||||
|
type: accept,
|
||||||
|
extension,
|
||||||
|
success: (res) => resolve(res.tempFiles as ChooseFile[]),
|
||||||
|
fail: reject
|
||||||
|
})
|
||||||
|
// #endif
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
uni.chooseMessageFile({
|
||||||
|
count: multiple ? Number(maxCount) : 1,
|
||||||
|
type: accept,
|
||||||
|
extension,
|
||||||
|
success: (res) => resolve(res.tempFiles),
|
||||||
|
fail: reject
|
||||||
|
})
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
uni.chooseMedia({
|
||||||
|
count: multiple ? maxCount : 1,
|
||||||
|
mediaType: ['image'],
|
||||||
|
sourceType,
|
||||||
|
sizeType,
|
||||||
|
camera,
|
||||||
|
success: (res) => resolve(formatMedia(res)),
|
||||||
|
fail: reject
|
||||||
|
})
|
||||||
|
// #endif
|
||||||
|
// #ifndef MP-WEIXIN
|
||||||
|
uni.chooseImage({
|
||||||
|
count: multiple ? maxCount : 1,
|
||||||
|
sizeType,
|
||||||
|
sourceType,
|
||||||
|
// #ifdef H5
|
||||||
|
extension,
|
||||||
|
// #endif
|
||||||
|
success: (res) => resolve(formatImage(res)),
|
||||||
|
fail: reject
|
||||||
|
})
|
||||||
|
// #endif
|
||||||
|
break
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
startUpload,
|
||||||
|
abort,
|
||||||
|
UPLOAD_STATUS,
|
||||||
|
chooseFile
|
||||||
|
}
|
||||||
|
}
|
||||||
204
node_modules/wot-design-uni/components/wd-action-sheet/index.scss
generated
vendored
Normal file
204
node_modules/wot-design-uni/components/wd-action-sheet/index.scss
generated
vendored
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
@import '../common/abstracts/variable';
|
||||||
|
@import '../common/abstracts/mixin';
|
||||||
|
|
||||||
|
.wot-theme-dark {
|
||||||
|
@include b(action-sheet) {
|
||||||
|
background-color: $-dark-background2;
|
||||||
|
color: $-dark-color;
|
||||||
|
|
||||||
|
@include e(action) {
|
||||||
|
color: $-dark-color;
|
||||||
|
background: $-dark-background2;
|
||||||
|
|
||||||
|
&:not(.wd-action-sheet__action--disabled):not(.wd-action-sheet__action--loading):active {
|
||||||
|
background: $-dark-background4;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include m(disabled) {
|
||||||
|
color: $-dark-color-gray;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(subname) {
|
||||||
|
color: $-dark-color3;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(cancel) {
|
||||||
|
color: $-dark-color;
|
||||||
|
background: $-dark-background4;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background: $-dark-background5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.wd-action-sheet__close) {
|
||||||
|
color: $-dark-color3;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(panel-title) {
|
||||||
|
color: $-dark-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(header) {
|
||||||
|
color: $-dark-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.wd-action-sheet__popup) {
|
||||||
|
border-radius: $-action-sheet-radius $-action-sheet-radius 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include b(action-sheet) {
|
||||||
|
background-color: $-color-white;
|
||||||
|
padding-bottom: 1px;
|
||||||
|
|
||||||
|
@include edeep(popup) {
|
||||||
|
border-radius: $-action-sheet-radius $-action-sheet-radius 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(actions) {
|
||||||
|
padding: 8px 0;
|
||||||
|
max-height: 50vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(action) {
|
||||||
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: $-action-sheet-action-height;
|
||||||
|
line-height: $-action-sheet-action-height;
|
||||||
|
color: $-action-sheet-color;
|
||||||
|
font-size: $-action-sheet-fs;
|
||||||
|
text-align: center;
|
||||||
|
border: none;
|
||||||
|
background: $-action-sheet-bg;
|
||||||
|
outline: none;
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(&--disabled):not(&--loading):active {
|
||||||
|
background: $-action-sheet-active-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include m(disabled) {
|
||||||
|
color: $-action-sheet-disabled-color;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include m(loading) {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
line-height: initial;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include edeep(action-loading){
|
||||||
|
width: $-action-sheet-loading-size;
|
||||||
|
height: $-action-sheet-loading-size;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(name) {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(subname) {
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: 4px;
|
||||||
|
font-size: $-action-sheet-subname-fs;
|
||||||
|
color: $-action-sheet-subname-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(cancel) {
|
||||||
|
display: block;
|
||||||
|
width: calc(100% - 48px);
|
||||||
|
line-height: $-action-sheet-cancel-height;
|
||||||
|
padding: 0;
|
||||||
|
color: $-action-sheet-cancel-color;
|
||||||
|
font-size: $-action-sheet-fs;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: $-action-sheet-cancel-radius;
|
||||||
|
border: none;
|
||||||
|
background: $-action-sheet-cancel-bg;
|
||||||
|
outline: none;
|
||||||
|
margin: 0 auto 24px;
|
||||||
|
font-weight: $-action-sheet-weight;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background: $-action-sheet-active-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(header) {
|
||||||
|
color: $-action-sheet-color;
|
||||||
|
position: relative;
|
||||||
|
height: $-action-sheet-title-height;
|
||||||
|
line-height: $-action-sheet-title-height;
|
||||||
|
text-align: center;
|
||||||
|
font-size: $-action-sheet-title-fs;
|
||||||
|
font-weight: $-action-sheet-weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include edeep(close) {
|
||||||
|
position: absolute;
|
||||||
|
top: $-action-sheet-close-top;
|
||||||
|
right: $-action-sheet-close-right;
|
||||||
|
color: $-action-sheet-close-color;
|
||||||
|
font-size: $-action-sheet-close-fs;
|
||||||
|
transform: rotate(-45deg);
|
||||||
|
line-height: 1.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(panels) {
|
||||||
|
height: 84px;
|
||||||
|
overflow-y: hidden;
|
||||||
|
|
||||||
|
&:first-of-type {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-of-type {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(panels-content) {
|
||||||
|
display: flex;
|
||||||
|
overflow-x: auto;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(panel) {
|
||||||
|
width: 88px;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
display: inline-block;
|
||||||
|
padding: $-action-sheet-panel-padding;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(panel-img) {
|
||||||
|
display: block;
|
||||||
|
width: $-action-sheet-panel-img-fs;
|
||||||
|
height: $-action-sheet-panel-img-fs;
|
||||||
|
margin: 0 auto;
|
||||||
|
margin-bottom: 7px;
|
||||||
|
border-radius: $-action-sheet-panel-img-radius;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(panel-title) {
|
||||||
|
font-size: $-action-sheet-subname-fs;
|
||||||
|
line-height: 1.2;
|
||||||
|
text-align: center;
|
||||||
|
color: $-action-sheet-color;
|
||||||
|
@include lineEllipsis;
|
||||||
|
}
|
||||||
|
}
|
||||||
118
node_modules/wot-design-uni/components/wd-action-sheet/types.ts
generated
vendored
Normal file
118
node_modules/wot-design-uni/components/wd-action-sheet/types.ts
generated
vendored
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
import type { ExtractPropTypes } from 'vue'
|
||||||
|
import { baseProps, makeArrayProp, makeBooleanProp, makeNumberProp, makeRequiredProp, makeStringProp } from '../common/props'
|
||||||
|
|
||||||
|
export type Action = {
|
||||||
|
/**
|
||||||
|
* 选项名称
|
||||||
|
*/
|
||||||
|
name: string
|
||||||
|
/**
|
||||||
|
* 描述信息
|
||||||
|
*/
|
||||||
|
subname?: string
|
||||||
|
/**
|
||||||
|
* 颜色
|
||||||
|
*/
|
||||||
|
color?: string
|
||||||
|
/**
|
||||||
|
* 禁用
|
||||||
|
*/
|
||||||
|
disabled?: boolean
|
||||||
|
/**
|
||||||
|
* 加载中状态
|
||||||
|
*/
|
||||||
|
loading?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Panel = {
|
||||||
|
/**
|
||||||
|
* 图片地址
|
||||||
|
*/
|
||||||
|
iconUrl: string
|
||||||
|
/**
|
||||||
|
* 标题内容
|
||||||
|
*/
|
||||||
|
title: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const actionSheetProps = {
|
||||||
|
...baseProps,
|
||||||
|
/**
|
||||||
|
* header 头部样式
|
||||||
|
* @default ''
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
customHeaderClass: makeStringProp(''),
|
||||||
|
/**
|
||||||
|
* 设置菜单显示隐藏
|
||||||
|
* @default false
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
modelValue: { ...makeBooleanProp(false), ...makeRequiredProp(Boolean) },
|
||||||
|
/**
|
||||||
|
* 菜单选项
|
||||||
|
* @default []
|
||||||
|
* @type {Action[]}
|
||||||
|
*/
|
||||||
|
actions: makeArrayProp<Action>(),
|
||||||
|
/**
|
||||||
|
* 自定义面板项,可以为字符串数组,也可以为对象数组,如果为二维数组,则为多行展示
|
||||||
|
* @default []
|
||||||
|
* @type {Array<Panel | Panel[]>}
|
||||||
|
*/
|
||||||
|
panels: makeArrayProp<Panel | Panel[]>(),
|
||||||
|
/**
|
||||||
|
* 标题
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
title: String,
|
||||||
|
/**
|
||||||
|
* 取消按钮文案
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
cancelText: String,
|
||||||
|
/**
|
||||||
|
* 点击选项后是否关闭菜单
|
||||||
|
* @default true
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
closeOnClickAction: makeBooleanProp(true),
|
||||||
|
/**
|
||||||
|
* 点击遮罩是否关闭
|
||||||
|
* @default true
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
closeOnClickModal: makeBooleanProp(true),
|
||||||
|
/**
|
||||||
|
* 弹框动画持续时间
|
||||||
|
* @default 200
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
duration: makeNumberProp(200),
|
||||||
|
/**
|
||||||
|
* 菜单层级
|
||||||
|
* @default 10
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
zIndex: makeNumberProp(10),
|
||||||
|
/**
|
||||||
|
* 弹层内容懒渲染,触发展示时才渲染内容
|
||||||
|
* @default true
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
lazyRender: makeBooleanProp(true),
|
||||||
|
/**
|
||||||
|
* 弹出面板是否设置底部安全距离(iphone X 类型的机型)
|
||||||
|
* @default true
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
safeAreaInsetBottom: makeBooleanProp(true),
|
||||||
|
/**
|
||||||
|
* 是否从页面中脱离出来,用于解决各种 fixed 失效问题 (H5: teleport, APP: renderjs, 小程序: root-portal)
|
||||||
|
* 类型:boolean
|
||||||
|
* 默认值:false
|
||||||
|
*/
|
||||||
|
rootPortal: makeBooleanProp(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ActionSheetProps = ExtractPropTypes<typeof actionSheetProps>
|
||||||
155
node_modules/wot-design-uni/components/wd-action-sheet/wd-action-sheet.vue
generated
vendored
Normal file
155
node_modules/wot-design-uni/components/wd-action-sheet/wd-action-sheet.vue
generated
vendored
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
<template>
|
||||||
|
<view>
|
||||||
|
<wd-popup
|
||||||
|
custom-class="wd-action-sheet__popup"
|
||||||
|
:custom-style="`${(actions && actions.length) || (panels && panels.length) ? 'background: transparent;' : ''}`"
|
||||||
|
v-model="showPopup"
|
||||||
|
:duration="duration"
|
||||||
|
position="bottom"
|
||||||
|
:close-on-click-modal="closeOnClickModal"
|
||||||
|
:safe-area-inset-bottom="safeAreaInsetBottom"
|
||||||
|
:lazy-render="lazyRender"
|
||||||
|
:root-portal="rootPortal"
|
||||||
|
@enter="handleOpen"
|
||||||
|
@close="close"
|
||||||
|
@after-enter="handleOpened"
|
||||||
|
@after-leave="handleClosed"
|
||||||
|
@click-modal="handleClickModal"
|
||||||
|
:z-index="zIndex"
|
||||||
|
>
|
||||||
|
<view
|
||||||
|
:class="`wd-action-sheet ${customClass}`"
|
||||||
|
:style="`${
|
||||||
|
(actions && actions.length) || (panels && panels.length)
|
||||||
|
? 'margin: 0 10px calc(var(--window-bottom) + 10px) 10px; border-radius: 16px;'
|
||||||
|
: 'margin-bottom: var(--window-bottom);'
|
||||||
|
} ${customStyle}`"
|
||||||
|
>
|
||||||
|
<view v-if="title" :class="`wd-action-sheet__header ${customHeaderClass}`">
|
||||||
|
{{ title }}
|
||||||
|
<wd-icon custom-class="wd-action-sheet__close" name="add" @click="close" />
|
||||||
|
</view>
|
||||||
|
<view class="wd-action-sheet__actions" v-if="actions && actions.length">
|
||||||
|
<button
|
||||||
|
v-for="(action, rowIndex) in actions"
|
||||||
|
:key="rowIndex"
|
||||||
|
:class="`wd-action-sheet__action ${action.disabled ? 'wd-action-sheet__action--disabled' : ''} ${
|
||||||
|
action.loading ? 'wd-action-sheet__action--loading' : ''
|
||||||
|
}`"
|
||||||
|
:style="`color: ${action.color}`"
|
||||||
|
@click="select(rowIndex, 'action')"
|
||||||
|
>
|
||||||
|
<wd-loading custom-class="`wd-action-sheet__action-loading" v-if="action.loading" />
|
||||||
|
<view v-else class="wd-action-sheet__name">{{ action.name }}</view>
|
||||||
|
<view v-if="!action.loading && action.subname" class="wd-action-sheet__subname">{{ action.subname }}</view>
|
||||||
|
</button>
|
||||||
|
</view>
|
||||||
|
<view v-if="formatPanels && formatPanels.length">
|
||||||
|
<view v-for="(panel, rowIndex) in formatPanels" :key="rowIndex" class="wd-action-sheet__panels">
|
||||||
|
<view class="wd-action-sheet__panels-content">
|
||||||
|
<view v-for="(col, colIndex) in panel" :key="colIndex" class="wd-action-sheet__panel" @click="select(rowIndex, 'panels', colIndex)">
|
||||||
|
<image class="wd-action-sheet__panel-img" :src="(col as any).iconUrl" />
|
||||||
|
<view class="wd-action-sheet__panel-title">{{ (col as any).title }}</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<slot />
|
||||||
|
<button v-if="cancelText" class="wd-action-sheet__cancel" @click="handleCancel">{{ cancelText }}</button>
|
||||||
|
</view>
|
||||||
|
</wd-popup>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
name: 'wd-action-sheet',
|
||||||
|
options: {
|
||||||
|
addGlobalClass: true,
|
||||||
|
virtualHost: true,
|
||||||
|
styleIsolation: 'shared'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import wdPopup from '../wd-popup/wd-popup.vue'
|
||||||
|
import wdIcon from '../wd-icon/wd-icon.vue'
|
||||||
|
import wdLoading from '../wd-loading/wd-loading.vue'
|
||||||
|
import { watch, ref } from 'vue'
|
||||||
|
import { actionSheetProps, type Panel } from './types'
|
||||||
|
import { isArray } from '../common/util'
|
||||||
|
|
||||||
|
const props = defineProps(actionSheetProps)
|
||||||
|
const emit = defineEmits(['select', 'click-modal', 'cancel', 'closed', 'close', 'open', 'opened', 'update:modelValue'])
|
||||||
|
|
||||||
|
const formatPanels = ref<Array<Panel> | Array<Panel[]>>([])
|
||||||
|
|
||||||
|
const showPopup = ref<boolean>(false)
|
||||||
|
|
||||||
|
watch(() => props.panels, computedValue, { deep: true, immediate: true })
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.modelValue,
|
||||||
|
(newValue) => {
|
||||||
|
showPopup.value = newValue
|
||||||
|
},
|
||||||
|
{ deep: true, immediate: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
function isPanelArray() {
|
||||||
|
return props.panels.length && !isArray(props.panels[0])
|
||||||
|
}
|
||||||
|
function computedValue() {
|
||||||
|
formatPanels.value = isPanelArray() ? [props.panels as Panel[]] : (props.panels as Panel[][])
|
||||||
|
}
|
||||||
|
|
||||||
|
function select(rowIndex: number, type: 'action' | 'panels', colIndex?: number) {
|
||||||
|
if (type === 'action') {
|
||||||
|
if (props.actions[rowIndex].disabled || props.actions[rowIndex].loading) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
emit('select', {
|
||||||
|
item: props.actions[rowIndex],
|
||||||
|
index: rowIndex
|
||||||
|
})
|
||||||
|
} else if (isPanelArray()) {
|
||||||
|
emit('select', {
|
||||||
|
item: props.panels[Number(colIndex)],
|
||||||
|
index: colIndex
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
emit('select', {
|
||||||
|
item: (props.panels as Panel[][])[rowIndex][Number(colIndex)],
|
||||||
|
rowIndex,
|
||||||
|
colIndex
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (props.closeOnClickAction) {
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function handleClickModal() {
|
||||||
|
emit('click-modal')
|
||||||
|
}
|
||||||
|
function handleCancel() {
|
||||||
|
emit('cancel')
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
function close() {
|
||||||
|
emit('update:modelValue', false)
|
||||||
|
emit('close')
|
||||||
|
}
|
||||||
|
function handleOpen() {
|
||||||
|
emit('open')
|
||||||
|
}
|
||||||
|
function handleOpened() {
|
||||||
|
emit('opened')
|
||||||
|
}
|
||||||
|
function handleClosed() {
|
||||||
|
emit('closed')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import './index.scss';
|
||||||
|
</style>
|
||||||
17
node_modules/wot-design-uni/components/wd-avatar-group/index.scss
generated
vendored
Normal file
17
node_modules/wot-design-uni/components/wd-avatar-group/index.scss
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
@import '../common/abstracts/variable';
|
||||||
|
@import '../common/abstracts/mixin';
|
||||||
|
@import '../wd-avatar/common';
|
||||||
|
|
||||||
|
@include b(avatar-group) {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0;
|
||||||
|
|
||||||
|
@include e(item) {
|
||||||
|
// 样式已在 common.scss 中定义
|
||||||
|
}
|
||||||
|
|
||||||
|
@include edeep(collapse) {
|
||||||
|
font-size: $-avatar-group-collapse-font-size;
|
||||||
|
}
|
||||||
|
}
|
||||||
63
node_modules/wot-design-uni/components/wd-avatar-group/types.ts
generated
vendored
Normal file
63
node_modules/wot-design-uni/components/wd-avatar-group/types.ts
generated
vendored
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* @Author: weisheng
|
||||||
|
* @Date: 2025-12-30
|
||||||
|
* @LastEditTime: 2025-12-30
|
||||||
|
* @LastEditors: weisheng
|
||||||
|
* @Description: AvatarGroup 头像组类型定义
|
||||||
|
* @FilePath: /wot-design-uni/src/uni_modules/wot-design-uni/components/wd-avatar-group/types.ts
|
||||||
|
* 记得注释
|
||||||
|
*/
|
||||||
|
import type { ExtractPropTypes, PropType } from 'vue'
|
||||||
|
import type { InjectionKey } from 'vue'
|
||||||
|
import { baseProps, makeStringProp, makeNumericProp } from '../common/props'
|
||||||
|
import type { AvatarShape, AvatarSize } from '../wd-avatar/types'
|
||||||
|
|
||||||
|
export type AvatarGroupCascadingValue = 'left-up' | 'right-up'
|
||||||
|
|
||||||
|
export type AvatarGroupProvide = {
|
||||||
|
props: AvatarGroupProps
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AVATAR_GROUP_KEY: InjectionKey<AvatarGroupProvide> = Symbol('wd-avatar-group')
|
||||||
|
|
||||||
|
export const avatarGroupProps = {
|
||||||
|
...baseProps,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 最多显示的头像数量
|
||||||
|
* 类型: string | number
|
||||||
|
* 默认值: undefined
|
||||||
|
*/
|
||||||
|
maxCount: makeNumericProp(undefined),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 头像层叠方向
|
||||||
|
* 可选值: left-up(左侧叠层) / right-up(右侧叠层)
|
||||||
|
* 类型: string
|
||||||
|
* 默认值: 'left-up'
|
||||||
|
*/
|
||||||
|
cascading: makeStringProp<AvatarGroupCascadingValue>('left-up'),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统一设置组内所有头像的形状
|
||||||
|
* 类型: string
|
||||||
|
* 默认值: undefined
|
||||||
|
*/
|
||||||
|
shape: String as PropType<AvatarShape>,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统一设置组内所有头像的尺寸
|
||||||
|
* 类型: string | number
|
||||||
|
* 默认值: undefined
|
||||||
|
*/
|
||||||
|
size: [String, Number] as PropType<number | string | AvatarSize>,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 超出最大数量时折叠头像显示的内容
|
||||||
|
* 类型: string
|
||||||
|
* 默认值: undefined
|
||||||
|
*/
|
||||||
|
collapseAvatar: makeStringProp('')
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AvatarGroupProps = ExtractPropTypes<typeof avatarGroupProps>
|
||||||
98
node_modules/wot-design-uni/components/wd-avatar-group/wd-avatar-group.vue
generated
vendored
Normal file
98
node_modules/wot-design-uni/components/wd-avatar-group/wd-avatar-group.vue
generated
vendored
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
<!--
|
||||||
|
* @Author: North
|
||||||
|
* @Date: 2026-01-01
|
||||||
|
* @LastEditTime: 2026-01-01
|
||||||
|
* @LastEditors: North
|
||||||
|
* @Description: AvatarGroup 头像组组件,用于将多个头像组合展示
|
||||||
|
* @FilePath: /wot-design-uni/src/uni_modules/wot-design-uni/components/wd-avatar-group/wd-avatar-group.vue
|
||||||
|
-->
|
||||||
|
<template>
|
||||||
|
<view :class="rootClass" :style="customStyle">
|
||||||
|
<slot></slot>
|
||||||
|
<!-- 折叠头像 -->
|
||||||
|
<view v-if="showCollapse" class="wd-avatar-group__item wd-avatar-group__collapse" :style="collapseStyle">
|
||||||
|
<wd-avatar _internal :text="collapseText" :shape="props.shape" :size="props.size" bg-color="#ebedf0" color="#969799" />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
name: 'wd-avatar-group',
|
||||||
|
options: {
|
||||||
|
addGlobalClass: true,
|
||||||
|
virtualHost: true,
|
||||||
|
styleIsolation: 'shared'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed, type CSSProperties, type ComponentPublicInstance } from 'vue'
|
||||||
|
import wdAvatar from '../wd-avatar/wd-avatar.vue'
|
||||||
|
import { avatarGroupProps, AVATAR_GROUP_KEY, type AvatarGroupProvide } from './types'
|
||||||
|
import { useChildren } from '../composables/useChildren'
|
||||||
|
|
||||||
|
const props = defineProps(avatarGroupProps)
|
||||||
|
|
||||||
|
const { children, linkChildren } = useChildren<ComponentPublicInstance, AvatarGroupProvide>(AVATAR_GROUP_KEY)
|
||||||
|
|
||||||
|
linkChildren({ props })
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根节点类名
|
||||||
|
*/
|
||||||
|
const rootClass = computed(() => {
|
||||||
|
return `wd-avatar-group wd-avatar-group--${props.cascading} ${props.customClass}`
|
||||||
|
})
|
||||||
|
|
||||||
|
const maxCountValue = computed(() => {
|
||||||
|
if (!props.maxCount) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
const count = typeof props.maxCount === 'number' ? props.maxCount : parseInt(props.maxCount, 10)
|
||||||
|
return isNaN(count) || count <= 0 ? 0 : count
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否显示折叠头像
|
||||||
|
*/
|
||||||
|
const showCollapse = computed(() => {
|
||||||
|
return maxCountValue.value > 0 && children.length > maxCountValue.value
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 剩余未显示的数量
|
||||||
|
*/
|
||||||
|
const restCount = computed(() => {
|
||||||
|
if (maxCountValue.value <= 0) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return Math.max(0, children.length - maxCountValue.value)
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 折叠头像文本
|
||||||
|
*/
|
||||||
|
const collapseText = computed(() => {
|
||||||
|
return props.collapseAvatar || `+${restCount.value}`
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 折叠头像样式
|
||||||
|
*/
|
||||||
|
const collapseStyle = computed(() => {
|
||||||
|
const style: CSSProperties = {}
|
||||||
|
if (props.cascading === 'left-up') {
|
||||||
|
const count = maxCountValue.value > 0 ? maxCountValue.value : children.length
|
||||||
|
style.zIndex = count + 1
|
||||||
|
} else {
|
||||||
|
style.zIndex = 0
|
||||||
|
}
|
||||||
|
return style
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import './index.scss';
|
||||||
|
</style>
|
||||||
19
node_modules/wot-design-uni/components/wd-avatar/common.scss
generated
vendored
Normal file
19
node_modules/wot-design-uni/components/wd-avatar/common.scss
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
/**
|
||||||
|
* Avatar Group Item 共享样式
|
||||||
|
* 用于 wd-avatar 和 wd-avatar-group 组件中的重叠效果
|
||||||
|
*/
|
||||||
|
.wd-avatar-group__item {
|
||||||
|
position: relative;
|
||||||
|
flex-shrink: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
// 第一个元素不需要负 margin
|
||||||
|
&:first-child {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 其他元素使用负 margin 实现重叠效果
|
||||||
|
& + & {
|
||||||
|
margin-left: $-avatar-group-overlap;
|
||||||
|
}
|
||||||
|
}
|
||||||
75
node_modules/wot-design-uni/components/wd-avatar/index.scss
generated
vendored
Normal file
75
node_modules/wot-design-uni/components/wd-avatar/index.scss
generated
vendored
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
@import '../common/abstracts/variable';
|
||||||
|
@import '../common/abstracts/mixin';
|
||||||
|
@import './common';
|
||||||
|
|
||||||
|
.wot-theme-dark {
|
||||||
|
@include b(avatar) {
|
||||||
|
background-color: $-dark-background4;
|
||||||
|
color: $-dark-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include b(avatar) {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: $-avatar-size;
|
||||||
|
height: $-avatar-size;
|
||||||
|
color: $-avatar-text-color;
|
||||||
|
font-weight: $-avatar-font-weight;
|
||||||
|
font-size: $-avatar-font-size;
|
||||||
|
line-height: $-avatar-line-height;
|
||||||
|
text-align: center;
|
||||||
|
background-color: $-avatar-bg-color;
|
||||||
|
border-radius: $-avatar-border-radius;
|
||||||
|
overflow: hidden;
|
||||||
|
user-select: none;
|
||||||
|
vertical-align: middle;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
@include when(round) {
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(square) {
|
||||||
|
border-radius: $-avatar-border-radius;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(large) {
|
||||||
|
width: $-avatar-size-large;
|
||||||
|
height: $-avatar-size-large;
|
||||||
|
font-size: $-avatar-font-size-large;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(medium) {
|
||||||
|
width: $-avatar-size-medium;
|
||||||
|
height: $-avatar-size-medium;
|
||||||
|
font-size: $-avatar-font-size-medium;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(normal) {
|
||||||
|
width: $-avatar-size;
|
||||||
|
height: $-avatar-size;
|
||||||
|
font-size: $-avatar-font-size;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(small) {
|
||||||
|
width: $-avatar-size-small;
|
||||||
|
height: $-avatar-size-small;
|
||||||
|
font-size: $-avatar-font-size-small;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(text) {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include edeep(icon) {
|
||||||
|
font-size: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include edeep(img) {
|
||||||
|
border-radius: inherit;
|
||||||
|
}
|
||||||
|
}
|
||||||
90
node_modules/wot-design-uni/components/wd-avatar/types.ts
generated
vendored
Normal file
90
node_modules/wot-design-uni/components/wd-avatar/types.ts
generated
vendored
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
/*
|
||||||
|
* @Author: weisheng
|
||||||
|
* @Date: 2025-12-30
|
||||||
|
* @LastEditTime: 2025-12-30
|
||||||
|
* @LastEditors: weisheng
|
||||||
|
* @Description: Avatar 头像组件类型定义
|
||||||
|
* @FilePath: /wot-design-uni/src/uni_modules/wot-design-uni/components/wd-avatar/types.ts
|
||||||
|
* 记得注释
|
||||||
|
*/
|
||||||
|
import type { ExtractPropTypes, PropType } from 'vue'
|
||||||
|
import { baseProps, makeNumericProp, makeStringProp, numericProp } from '../common/props'
|
||||||
|
import type { ImageMode } from '../wd-img/types'
|
||||||
|
|
||||||
|
export type AvatarSize = 'large' | 'medium' | 'normal' | 'small'
|
||||||
|
export type AvatarShape = 'square' | 'round'
|
||||||
|
|
||||||
|
export const avatarProps = {
|
||||||
|
...baseProps,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图片地址
|
||||||
|
* 类型: string
|
||||||
|
* 默认值: 空字符串
|
||||||
|
*/
|
||||||
|
src: makeStringProp(''),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文本内容
|
||||||
|
* 类型: string
|
||||||
|
* 默认值: 空字符串
|
||||||
|
*/
|
||||||
|
text: makeStringProp(''),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 头像尺寸,支持预设尺寸(large/medium/normal/small)或带单位的字符串(如 40px、100rpx)
|
||||||
|
* 类型: string | number
|
||||||
|
* 默认值: 'normal'
|
||||||
|
*/
|
||||||
|
size: makeNumericProp('normal'),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 头像形状,可选值: round(圆形) / square(方形)
|
||||||
|
* 类型: string
|
||||||
|
* 默认值: 'round'
|
||||||
|
*/
|
||||||
|
shape: makeStringProp<AvatarShape>('round'),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 背景颜色
|
||||||
|
* 类型: string
|
||||||
|
* 默认值: 空字符串
|
||||||
|
*/
|
||||||
|
bgColor: makeStringProp(''),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文字颜色
|
||||||
|
* 类型: string
|
||||||
|
* 默认值: 空字符串
|
||||||
|
*/
|
||||||
|
color: makeStringProp(''),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图标名称,使用 wd-icon 组件
|
||||||
|
* 类型: string
|
||||||
|
* 默认值: 空字符串
|
||||||
|
*/
|
||||||
|
icon: makeStringProp(''),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图片加载失败时的占位文本
|
||||||
|
* 类型: string
|
||||||
|
* 默认值: 空字符串
|
||||||
|
*/
|
||||||
|
alt: makeStringProp(''),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图片填充模式,同 uni-app image 组件的 mode
|
||||||
|
* 类型: ImageMode
|
||||||
|
* 默认值: 'aspectFill'
|
||||||
|
*/
|
||||||
|
mode: makeStringProp<ImageMode>('aspectFill'),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 内部使用,不注册到 parent
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_internal: Boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AvatarProps = ExtractPropTypes<typeof avatarProps>
|
||||||
203
node_modules/wot-design-uni/components/wd-avatar/wd-avatar.vue
generated
vendored
Normal file
203
node_modules/wot-design-uni/components/wd-avatar/wd-avatar.vue
generated
vendored
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
<!--
|
||||||
|
* @Author: North
|
||||||
|
* @Date: 2026-01-01
|
||||||
|
* @LastEditTime: 2026-01-01
|
||||||
|
* @LastEditors: North
|
||||||
|
* @Description: Avatar 头像组件,支持图片、文本或图标展示
|
||||||
|
* @FilePath: /wot-design-uni/src/uni_modules/wot-design-uni/components/wd-avatar/wd-avatar.vue
|
||||||
|
-->
|
||||||
|
<template>
|
||||||
|
<view v-if="isShow" :class="rootClass" :style="rootStyle" @click="handleClick">
|
||||||
|
<!-- 默认插槽优先 -->
|
||||||
|
<slot v-if="hasDefaultSlot"></slot>
|
||||||
|
<!-- 图片 -->
|
||||||
|
<wd-img v-else-if="src" :src="src" :width="imgSize" :height="imgSize" :mode="props.mode" custom-class="wd-avatar__img" @error="handleError" />
|
||||||
|
<!-- 文本 -->
|
||||||
|
<text v-else-if="text" class="wd-avatar__text">{{ text }}</text>
|
||||||
|
<!-- 图标 -->
|
||||||
|
<wd-icon v-else-if="icon" :name="icon" custom-class="wd-avatar__icon" />
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
name: 'wd-avatar',
|
||||||
|
options: {
|
||||||
|
addGlobalClass: true,
|
||||||
|
virtualHost: true,
|
||||||
|
styleIsolation: 'shared'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed, useSlots, type CSSProperties, ref } from 'vue'
|
||||||
|
import wdIcon from '../wd-icon/wd-icon.vue'
|
||||||
|
import wdImg from '../wd-img/wd-img.vue'
|
||||||
|
import { addUnit, isDef, objToStyle } from '../common/util'
|
||||||
|
import { avatarProps, type AvatarSize } from './types'
|
||||||
|
import { useParent } from '../composables/useParent'
|
||||||
|
import { AVATAR_GROUP_KEY } from '../wd-avatar-group/types'
|
||||||
|
|
||||||
|
const props = defineProps(avatarProps)
|
||||||
|
const emit = defineEmits(['error', 'click'])
|
||||||
|
const slots = useSlots()
|
||||||
|
|
||||||
|
// _internal 用于 avatar-group 内部的溢出计数头像,跳过父组件上下文
|
||||||
|
const { parent, index } = props._internal ? { parent: null, index: ref(-1) } : useParent(AVATAR_GROUP_KEY)
|
||||||
|
|
||||||
|
const SIZE_MAP: Record<AvatarSize, number> = {
|
||||||
|
large: 76,
|
||||||
|
medium: 64,
|
||||||
|
normal: 54,
|
||||||
|
small: 48
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否显示该头像
|
||||||
|
*/
|
||||||
|
const isShow = computed(() => {
|
||||||
|
if (!parent) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// 在 avatar-group 中,根据 maxCount 判断
|
||||||
|
const maxCount = parent.props.maxCount
|
||||||
|
if (!isDef(maxCount)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
const count = typeof maxCount === 'number' ? maxCount : parseInt(maxCount, 10)
|
||||||
|
// 检查 count 是否为有效数字
|
||||||
|
if (isNaN(count) || count <= 0) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return index.value < count
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取实际尺寸
|
||||||
|
* 在 avatar-group 中优先使用 parent 的 size
|
||||||
|
*/
|
||||||
|
const actualSize = computed(() => {
|
||||||
|
if (parent && isDef(parent.props.size)) {
|
||||||
|
return parent.props.size
|
||||||
|
}
|
||||||
|
return props.size
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取实际像素尺寸
|
||||||
|
*/
|
||||||
|
const imgSize = computed(() => {
|
||||||
|
const size = actualSize.value
|
||||||
|
if (!isDef(size)) {
|
||||||
|
return SIZE_MAP.normal
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof size === 'string' && size in SIZE_MAP) {
|
||||||
|
return SIZE_MAP[size as AvatarSize]
|
||||||
|
}
|
||||||
|
|
||||||
|
return size
|
||||||
|
})
|
||||||
|
|
||||||
|
const hasDefaultSlot = computed(() => !!slots.default)
|
||||||
|
|
||||||
|
const rootClass = computed(() => {
|
||||||
|
const classes = ['wd-avatar', props.customClass]
|
||||||
|
|
||||||
|
// 形状类 - 在 avatar-group 中优先使用 parent 的 shape
|
||||||
|
const shape = parent && isDef(parent.props.shape) ? parent.props.shape : props.shape
|
||||||
|
classes.push(`wd-avatar--${shape}`)
|
||||||
|
|
||||||
|
// 尺寸类仅预设尺寸
|
||||||
|
const size = actualSize.value
|
||||||
|
if (typeof size === 'string' && ['large', 'medium', 'normal', 'small'].includes(size)) {
|
||||||
|
classes.push(`wd-avatar--${size}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 在 avatar-group 中时,添加 item 类
|
||||||
|
if (parent) {
|
||||||
|
classes.push('wd-avatar-group__item')
|
||||||
|
}
|
||||||
|
|
||||||
|
return classes.join(' ')
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根节点样式
|
||||||
|
*/
|
||||||
|
const rootStyle = computed(() => {
|
||||||
|
const style: CSSProperties = {}
|
||||||
|
|
||||||
|
let size = ''
|
||||||
|
const sizeValue = actualSize.value
|
||||||
|
if (typeof sizeValue === 'string' && sizeValue in SIZE_MAP) {
|
||||||
|
size = addUnit(SIZE_MAP[sizeValue as AvatarSize])
|
||||||
|
} else if (isDef(sizeValue)) {
|
||||||
|
size = addUnit(sizeValue)
|
||||||
|
}
|
||||||
|
if (size) {
|
||||||
|
style.width = size
|
||||||
|
style.height = size
|
||||||
|
style.fontSize = `calc(${size} * 0.45)`
|
||||||
|
|
||||||
|
if (parent) {
|
||||||
|
style['--wot-avatar-group-overlap' as any] = `calc(${size} * -0.22)`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 形状 - 在 avatar-group 中优先使用 parent 的 shape
|
||||||
|
const shape = parent && isDef(parent.props.shape) ? parent.props.shape : props.shape
|
||||||
|
if (shape === 'round') {
|
||||||
|
style.borderRadius = '50%'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理层叠效果的 z-index
|
||||||
|
if (parent) {
|
||||||
|
const cascading = parent.props.cascading
|
||||||
|
if (cascading === 'left-up') {
|
||||||
|
// 左侧在上,越后面越大
|
||||||
|
style.zIndex = index.value + 1
|
||||||
|
} else if (cascading === 'right-up') {
|
||||||
|
// 右侧在上,越前面越大
|
||||||
|
const maxCount = parent.props.maxCount
|
||||||
|
let count = parent.children?.length ?? 0
|
||||||
|
|
||||||
|
if (isDef(maxCount)) {
|
||||||
|
const parsedCount = typeof maxCount === 'number' ? maxCount : parseInt(maxCount, 10)
|
||||||
|
if (!isNaN(parsedCount) && parsedCount > 0) {
|
||||||
|
count = parsedCount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
style.zIndex = count - index.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.color) {
|
||||||
|
style.color = props.color
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.bgColor) {
|
||||||
|
style.backgroundColor = props.bgColor
|
||||||
|
// 有背景色但无文字色时,默认白色
|
||||||
|
if (!props.color) {
|
||||||
|
style.color = '#fff'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${objToStyle(style)} ${props.customStyle}`
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleError = (event: any) => {
|
||||||
|
emit('error', event)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleClick = () => {
|
||||||
|
emit('click')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import './index.scss';
|
||||||
|
</style>
|
||||||
25
node_modules/wot-design-uni/components/wd-backtop/index.scss
generated
vendored
Normal file
25
node_modules/wot-design-uni/components/wd-backtop/index.scss
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
@import '../common/abstracts/variable';
|
||||||
|
@import '../common/abstracts/mixin';
|
||||||
|
|
||||||
|
@include b(backtop) {
|
||||||
|
position: fixed;
|
||||||
|
background-color: $-backtop-bg;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
color: $-color-gray-8;
|
||||||
|
|
||||||
|
@include edeep(backicon) {
|
||||||
|
font-size: $-backtop-icon-size;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(circle) {
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(square) {
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
37
node_modules/wot-design-uni/components/wd-backtop/types.ts
generated
vendored
Normal file
37
node_modules/wot-design-uni/components/wd-backtop/types.ts
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import { baseProps, makeNumberProp, makeRequiredProp, makeStringProp } from '../common/props'
|
||||||
|
|
||||||
|
export const backtopProps = {
|
||||||
|
...baseProps,
|
||||||
|
/**
|
||||||
|
* 页面滚动距离
|
||||||
|
*/
|
||||||
|
scrollTop: makeRequiredProp(Number),
|
||||||
|
/**
|
||||||
|
* 距离顶部多少距离时显示
|
||||||
|
*/
|
||||||
|
top: makeNumberProp(300),
|
||||||
|
/**
|
||||||
|
* 返回顶部滚动时间
|
||||||
|
*/
|
||||||
|
duration: makeNumberProp(100),
|
||||||
|
/**
|
||||||
|
* 层级
|
||||||
|
*/
|
||||||
|
zIndex: makeNumberProp(10),
|
||||||
|
/**
|
||||||
|
* icon样式
|
||||||
|
*/
|
||||||
|
iconStyle: makeStringProp(''),
|
||||||
|
/**
|
||||||
|
* 形状
|
||||||
|
*/
|
||||||
|
shape: makeStringProp('circle'),
|
||||||
|
/**
|
||||||
|
* 距离屏幕底部距离
|
||||||
|
*/
|
||||||
|
bottom: makeNumberProp(100),
|
||||||
|
/**
|
||||||
|
* 距离屏幕右边距离
|
||||||
|
*/
|
||||||
|
right: makeNumberProp(20)
|
||||||
|
}
|
||||||
45
node_modules/wot-design-uni/components/wd-backtop/wd-backtop.vue
generated
vendored
Normal file
45
node_modules/wot-design-uni/components/wd-backtop/wd-backtop.vue
generated
vendored
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
<template>
|
||||||
|
<wd-transition :show="show" name="fade">
|
||||||
|
<view
|
||||||
|
:class="`wd-backtop ${customClass} is-${shape}`"
|
||||||
|
:style="`z-index: ${zIndex}; bottom: ${bottom}px; right: ${right}px; ${customStyle}`"
|
||||||
|
@click="handleBacktop"
|
||||||
|
>
|
||||||
|
<slot v-if="$slots.default"></slot>
|
||||||
|
<wd-icon v-else custom-class="wd-backtop__backicon" name="backtop" :custom-style="iconStyle" />
|
||||||
|
</view>
|
||||||
|
</wd-transition>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
name: 'wd-backtop',
|
||||||
|
options: {
|
||||||
|
addGlobalClass: true,
|
||||||
|
virtualHost: true,
|
||||||
|
styleIsolation: 'shared'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import wdTransition from '../wd-transition/wd-transition.vue'
|
||||||
|
import wdIcon from '../wd-icon/wd-icon.vue'
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import { backtopProps } from './types'
|
||||||
|
|
||||||
|
const props = defineProps(backtopProps)
|
||||||
|
|
||||||
|
const show = computed(() => props.scrollTop > props.top)
|
||||||
|
|
||||||
|
function handleBacktop() {
|
||||||
|
uni.pageScrollTo({
|
||||||
|
scrollTop: 0,
|
||||||
|
duration: props.duration
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import './index.scss';
|
||||||
|
</style>
|
||||||
63
node_modules/wot-design-uni/components/wd-badge/index.scss
generated
vendored
Normal file
63
node_modules/wot-design-uni/components/wd-badge/index.scss
generated
vendored
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
@import './../common/abstracts/_mixin.scss';
|
||||||
|
@import './../common/abstracts/variable.scss';
|
||||||
|
|
||||||
|
.wot-theme-dark {
|
||||||
|
@include b(badge) {
|
||||||
|
@include e(content) {
|
||||||
|
border-color: $-dark-background2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@include b(badge) {
|
||||||
|
position: relative;
|
||||||
|
vertical-align: middle;
|
||||||
|
display: inline-block;
|
||||||
|
|
||||||
|
@include e(content) {
|
||||||
|
display: inline-block;
|
||||||
|
box-sizing: content-box;
|
||||||
|
height: $-badge-height;
|
||||||
|
line-height: $-badge-height;
|
||||||
|
padding: $-badge-padding;
|
||||||
|
background-color: $-badge-bg;
|
||||||
|
border-radius: calc($-badge-height / 2 + 2px);
|
||||||
|
color: $-badge-color;
|
||||||
|
font-size: $-badge-fs;
|
||||||
|
text-align: center;
|
||||||
|
white-space: nowrap;
|
||||||
|
border: $-badge-border;
|
||||||
|
font-weight: 500;
|
||||||
|
|
||||||
|
@include when(fixed) {
|
||||||
|
position: absolute;
|
||||||
|
top: 0px;
|
||||||
|
right: 0px;
|
||||||
|
transform: translateY(-50%) translateX(50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(dot) {
|
||||||
|
height: $-badge-dot-size;
|
||||||
|
width: $-badge-dot-size;
|
||||||
|
padding: 0;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@each $type in (primary, success, warning, info, danger) {
|
||||||
|
@include m($type) {
|
||||||
|
@if $type == primary {
|
||||||
|
background-color: $-badge-primary;
|
||||||
|
} @else if $type == success {
|
||||||
|
background-color: $-badge-success;
|
||||||
|
} @else if $type == warning {
|
||||||
|
background-color: $-badge-warning;
|
||||||
|
} @else if $type == info {
|
||||||
|
background-color: $-badge-info;
|
||||||
|
} @else {
|
||||||
|
background-color: $-badge-danger;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
50
node_modules/wot-design-uni/components/wd-badge/types.ts
generated
vendored
Normal file
50
node_modules/wot-design-uni/components/wd-badge/types.ts
generated
vendored
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* @Author: weisheng
|
||||||
|
* @Date: 2024-03-15 11:36:12
|
||||||
|
* @LastEditTime: 2024-11-20 20:29:03
|
||||||
|
* @LastEditors: weisheng
|
||||||
|
* @Description:
|
||||||
|
* @FilePath: /wot-design-uni/src/uni_modules/wot-design-uni/components/wd-badge/types.ts
|
||||||
|
* 记得注释
|
||||||
|
*/
|
||||||
|
import type { ExtractPropTypes, PropType } from 'vue'
|
||||||
|
import { baseProps, makeBooleanProp, makeStringProp, numericProp } from '../common/props'
|
||||||
|
|
||||||
|
export type BadgeType = 'primary' | 'success' | 'warning' | 'danger' | 'info'
|
||||||
|
|
||||||
|
export const badgeProps = {
|
||||||
|
...baseProps,
|
||||||
|
/**
|
||||||
|
* 显示值
|
||||||
|
*/
|
||||||
|
modelValue: numericProp,
|
||||||
|
/** 当数值为 0 时,是否展示徽标 */
|
||||||
|
showZero: makeBooleanProp(false),
|
||||||
|
bgColor: String,
|
||||||
|
/**
|
||||||
|
* 最大值,超过最大值会显示 '{max}+',要求 value 是 Number 类型
|
||||||
|
*/
|
||||||
|
max: Number,
|
||||||
|
/**
|
||||||
|
* 是否为红色点状标注
|
||||||
|
*/
|
||||||
|
isDot: Boolean,
|
||||||
|
/**
|
||||||
|
* 是否隐藏 badge
|
||||||
|
*/
|
||||||
|
hidden: Boolean,
|
||||||
|
/**
|
||||||
|
* badge类型,可选值primary / success / warning / danger / info
|
||||||
|
*/
|
||||||
|
type: makeStringProp<BadgeType | undefined>(undefined),
|
||||||
|
/**
|
||||||
|
* 为正时,角标向下偏移对应的像素
|
||||||
|
*/
|
||||||
|
top: numericProp,
|
||||||
|
/**
|
||||||
|
* 为正时,角标向左偏移对应的像素
|
||||||
|
*/
|
||||||
|
right: numericProp
|
||||||
|
}
|
||||||
|
|
||||||
|
export type BadgeProps = ExtractPropTypes<typeof badgeProps>
|
||||||
61
node_modules/wot-design-uni/components/wd-badge/wd-badge.vue
generated
vendored
Normal file
61
node_modules/wot-design-uni/components/wd-badge/wd-badge.vue
generated
vendored
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
<template>
|
||||||
|
<view :class="['wd-badge', customClass]" :style="customStyle">
|
||||||
|
<slot></slot>
|
||||||
|
<view
|
||||||
|
v-if="shouldShowBadge"
|
||||||
|
:class="['wd-badge__content', 'is-fixed', type ? 'wd-badge__content--' + type : '', isDot ? 'is-dot' : '']"
|
||||||
|
:style="contentStyle"
|
||||||
|
>
|
||||||
|
{{ content }}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
name: 'wd-badge',
|
||||||
|
options: {
|
||||||
|
addGlobalClass: true,
|
||||||
|
virtualHost: true,
|
||||||
|
styleIsolation: 'shared'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed, type CSSProperties } from 'vue'
|
||||||
|
import { badgeProps } from './types'
|
||||||
|
import { addUnit, isDef, isNumber, objToStyle } from '../common/util'
|
||||||
|
|
||||||
|
const props = defineProps(badgeProps)
|
||||||
|
const content = computed(() => {
|
||||||
|
const { modelValue, max, isDot } = props
|
||||||
|
if (isDot) return ''
|
||||||
|
let value = modelValue
|
||||||
|
if (value && max && isNumber(value) && !Number.isNaN(value) && !Number.isNaN(max)) {
|
||||||
|
value = max < value ? `${max}+` : value
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
})
|
||||||
|
|
||||||
|
const contentStyle = computed(() => {
|
||||||
|
const style: CSSProperties = {}
|
||||||
|
if (isDef(props.bgColor)) {
|
||||||
|
style.backgroundColor = props.bgColor
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDef(props.top)) {
|
||||||
|
style.top = addUnit(props.top)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDef(props.right)) {
|
||||||
|
style.right = addUnit(props.right)
|
||||||
|
}
|
||||||
|
return objToStyle(style)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 是否展示徽标数字
|
||||||
|
const shouldShowBadge = computed(() => !props.hidden && (content.value || (content.value === 0 && props.showZero) || props.isDot))
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import './index.scss';
|
||||||
|
</style>
|
||||||
335
node_modules/wot-design-uni/components/wd-button/index.scss
generated
vendored
Normal file
335
node_modules/wot-design-uni/components/wd-button/index.scss
generated
vendored
Normal file
@@ -0,0 +1,335 @@
|
|||||||
|
@import './../common/abstracts/_mixin.scss';
|
||||||
|
@import './../common/abstracts/variable.scss';
|
||||||
|
|
||||||
|
|
||||||
|
.wot-theme-dark {
|
||||||
|
@include b(button) {
|
||||||
|
@include when(info) {
|
||||||
|
background: $-dark-background4;
|
||||||
|
color: $-dark-color3;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(plain) {
|
||||||
|
background: transparent;
|
||||||
|
|
||||||
|
@include when(info) {
|
||||||
|
color: $-dark-color;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
border-color: $-dark-background5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(text) {
|
||||||
|
@include when(disabled) {
|
||||||
|
color: $-dark-color-gray;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(icon) {
|
||||||
|
color: $-dark-color;
|
||||||
|
|
||||||
|
@include when(disabled) {
|
||||||
|
color: $-dark-color-gray;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include b(button) {
|
||||||
|
margin-left: initial;
|
||||||
|
margin-right: initial;
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
outline: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
background: transparent;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: none;
|
||||||
|
border-radius: 0;
|
||||||
|
color: $-button-normal-color;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
user-select: none;
|
||||||
|
font-weight: normal;
|
||||||
|
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: $-color-black;
|
||||||
|
border: inherit;
|
||||||
|
border-color: $-color-black;
|
||||||
|
border-radius: inherit;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
opacity: 0;
|
||||||
|
content: ' ';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
border: none;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(content) {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include m(active) {
|
||||||
|
&:active::before {
|
||||||
|
opacity: 0.15;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(disabled) {
|
||||||
|
opacity: $-button-disabled-opacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(loading) {
|
||||||
|
margin-right: 5px;
|
||||||
|
animation: wd-rotate 0.8s linear infinite;
|
||||||
|
animation-duration: 2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(loading-svg) {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-size: cover;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(loading) {}
|
||||||
|
|
||||||
|
@include when(primary) {
|
||||||
|
background: $-button-primary-bg-color;
|
||||||
|
color: $-button-primary-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(success) {
|
||||||
|
background: $-button-success-bg-color;
|
||||||
|
color: $-button-success-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(info) {
|
||||||
|
background: $-button-info-bg-color;
|
||||||
|
color: $-button-info-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(warning) {
|
||||||
|
background: $-button-warning-bg-color;
|
||||||
|
color: $-button-warning-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(error) {
|
||||||
|
background: $-button-error-bg-color;
|
||||||
|
color: $-button-error-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(small) {
|
||||||
|
height: $-button-small-height;
|
||||||
|
padding: $-button-small-padding;
|
||||||
|
border-radius: $-button-small-radius;
|
||||||
|
font-size: $-button-small-fs;
|
||||||
|
font-weight: normal;
|
||||||
|
|
||||||
|
.wd-button__loading {
|
||||||
|
width: $-button-small-loading;
|
||||||
|
height: $-button-small-loading;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(medium) {
|
||||||
|
height: $-button-medium-height;
|
||||||
|
padding: $-button-medium-padding;
|
||||||
|
border-radius: $-button-medium-radius;
|
||||||
|
font-size: $-button-medium-fs;
|
||||||
|
min-width: 120px;
|
||||||
|
|
||||||
|
|
||||||
|
@include when(round) {
|
||||||
|
|
||||||
|
@include when(icon) {
|
||||||
|
min-width: 0;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(text) {
|
||||||
|
border-radius: 0;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.wd-button__loading {
|
||||||
|
width: $-button-medium-loading;
|
||||||
|
height: $-button-medium-loading;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(large) {
|
||||||
|
height: $-button-large-height;
|
||||||
|
padding: $-button-large-padding;
|
||||||
|
border-radius: $-button-large-radius;
|
||||||
|
font-size: $-button-large-fs;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
border-radius: $-button-large-radius;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wd-button__loading {
|
||||||
|
width: $-button-large-loading;
|
||||||
|
height: $-button-large-loading;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@include when(round) {
|
||||||
|
border-radius: 999px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(text) {
|
||||||
|
color: $-button-primary-bg-color;
|
||||||
|
min-width: 0;
|
||||||
|
padding: 4px 0;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.wd-button--active {
|
||||||
|
opacity: $-button-text-hover-opacity;
|
||||||
|
|
||||||
|
&:active::before {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(disabled) {
|
||||||
|
color: $-button-normal-disabled-color;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(plain) {
|
||||||
|
background: $-button-plain-bg-color;
|
||||||
|
border: 1px solid currentColor;
|
||||||
|
|
||||||
|
@include when(primary) {
|
||||||
|
color: $-button-primary-bg-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(success) {
|
||||||
|
color: $-button-success-bg-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(info) {
|
||||||
|
color: $-button-info-plain-normal-color;
|
||||||
|
border-color: $-button-info-plain-border-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(warning) {
|
||||||
|
color: $-button-warning-bg-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(error) {
|
||||||
|
color: $-button-error-bg-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(hairline) {
|
||||||
|
border-width: 0;
|
||||||
|
|
||||||
|
&.is-plain {
|
||||||
|
@include halfPixelBorderSurround();
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
border-radius: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
border-color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-round {
|
||||||
|
&::after {
|
||||||
|
border-radius: inherit !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-large {
|
||||||
|
&::after {
|
||||||
|
border-radius: calc(2 * $-button-large-radius);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-medium {
|
||||||
|
&::after {
|
||||||
|
border-radius: calc(2 * $-button-medium-radius);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-small {
|
||||||
|
&::after {
|
||||||
|
border-radius: calc(2 * $-button-small-radius);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(block) {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(icon) {
|
||||||
|
width: $-button-icon-size;
|
||||||
|
height: $-button-icon-size;
|
||||||
|
padding: 0;
|
||||||
|
border-radius: 50%;
|
||||||
|
color: $-button-icon-color;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.wd-button__icon) {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(disabled) {
|
||||||
|
color: $-button-icon-disabled-color;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include edeep(icon) {
|
||||||
|
display: block;
|
||||||
|
margin-right: 6px;
|
||||||
|
font-size: $-button-icon-fs;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(text) {
|
||||||
|
user-select: none;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes wd-rotate {
|
||||||
|
from {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
142
node_modules/wot-design-uni/components/wd-button/types.ts
generated
vendored
Normal file
142
node_modules/wot-design-uni/components/wd-button/types.ts
generated
vendored
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
/*
|
||||||
|
* @Author: weisheng
|
||||||
|
* @Date: 2024-03-15 11:36:12
|
||||||
|
* @LastEditTime: 2024-11-04 21:33:52
|
||||||
|
* @LastEditors: weisheng
|
||||||
|
* @Description:
|
||||||
|
* @FilePath: \wot-design-uni\src\uni_modules\wot-design-uni\components\wd-button\types.ts
|
||||||
|
* 记得注释
|
||||||
|
*/
|
||||||
|
import type { ExtractPropTypes, PropType } from 'vue'
|
||||||
|
import { baseProps, makeBooleanProp, makeStringProp } from '../common/props'
|
||||||
|
|
||||||
|
export type ButtonType = 'primary' | 'success' | 'info' | 'warning' | 'error' | 'default' | 'text' | 'icon'
|
||||||
|
export type ButtonSize = 'small' | 'medium' | 'large'
|
||||||
|
export type ButtonLang = 'zh_CN' | 'zh_TW' | 'en'
|
||||||
|
|
||||||
|
export type ButtonOpenType =
|
||||||
|
| 'feedback'
|
||||||
|
| 'share'
|
||||||
|
| 'getUserInfo'
|
||||||
|
| 'contact'
|
||||||
|
| 'getPhoneNumber'
|
||||||
|
| 'getRealtimePhoneNumber'
|
||||||
|
| 'launchApp'
|
||||||
|
| 'openSetting'
|
||||||
|
| 'chooseAvatar'
|
||||||
|
| 'getAuthorize'
|
||||||
|
| 'lifestyle'
|
||||||
|
| 'contactShare'
|
||||||
|
| 'openGroupProfile'
|
||||||
|
| 'openGuildProfile'
|
||||||
|
| 'openPublicProfile'
|
||||||
|
| 'shareMessageToFriend'
|
||||||
|
| 'addFriend'
|
||||||
|
| 'addColorSign'
|
||||||
|
| 'addGroupApp'
|
||||||
|
| 'addToFavorites'
|
||||||
|
| 'chooseAddress'
|
||||||
|
| 'chooseInvoiceTitle'
|
||||||
|
| 'login'
|
||||||
|
| 'subscribe'
|
||||||
|
| 'favorite'
|
||||||
|
| 'watchLater'
|
||||||
|
| 'openProfile'
|
||||||
|
| 'agreePrivacyAuthorization'
|
||||||
|
|
||||||
|
export type ButtonScope = 'phoneNumber' | 'userInfo'
|
||||||
|
|
||||||
|
export const buttonProps = {
|
||||||
|
...baseProps,
|
||||||
|
/**
|
||||||
|
* 幽灵按钮
|
||||||
|
*/
|
||||||
|
plain: makeBooleanProp(false),
|
||||||
|
/**
|
||||||
|
* 圆角按钮
|
||||||
|
*/
|
||||||
|
round: makeBooleanProp(true),
|
||||||
|
/**
|
||||||
|
* 禁用按钮
|
||||||
|
*/
|
||||||
|
disabled: makeBooleanProp(false),
|
||||||
|
/**
|
||||||
|
* 是否细边框
|
||||||
|
*/
|
||||||
|
hairline: makeBooleanProp(false),
|
||||||
|
/**
|
||||||
|
* 块状按钮
|
||||||
|
*/
|
||||||
|
block: makeBooleanProp(false),
|
||||||
|
/**
|
||||||
|
* 按钮类型,可选值:primary / success / info / warning / error / text / icon
|
||||||
|
*/
|
||||||
|
type: makeStringProp<ButtonType>('primary'),
|
||||||
|
/**
|
||||||
|
* 按钮尺寸,可选值:small / medium / large
|
||||||
|
*/
|
||||||
|
size: makeStringProp<ButtonSize>('medium'),
|
||||||
|
/**
|
||||||
|
* 图标类名
|
||||||
|
*/
|
||||||
|
icon: String,
|
||||||
|
/**
|
||||||
|
* 类名前缀,用于使用自定义图标,用法参考Icon组件
|
||||||
|
*/
|
||||||
|
classPrefix: makeStringProp('wd-icon'),
|
||||||
|
/**
|
||||||
|
* 加载中按钮
|
||||||
|
*/
|
||||||
|
loading: makeBooleanProp(false),
|
||||||
|
/**
|
||||||
|
* 加载图标颜色
|
||||||
|
*/
|
||||||
|
loadingColor: String,
|
||||||
|
/**
|
||||||
|
* 开放能力
|
||||||
|
*/
|
||||||
|
openType: String as PropType<ButtonOpenType>,
|
||||||
|
/**
|
||||||
|
* 指定是否阻止本节点的祖先节点出现点击态
|
||||||
|
*/
|
||||||
|
hoverStopPropagation: Boolean,
|
||||||
|
/**
|
||||||
|
* 指定返回用户信息的语言,zh_CN 简体中文,zh_TW 繁体中文,en 英文
|
||||||
|
*/
|
||||||
|
lang: String as PropType<ButtonLang>,
|
||||||
|
/**
|
||||||
|
* 会话来源,open-type="contact"时有效
|
||||||
|
*/
|
||||||
|
sessionFrom: String,
|
||||||
|
/**
|
||||||
|
* 会话内消息卡片标题,open-type="contact"时有效
|
||||||
|
*/
|
||||||
|
sendMessageTitle: String,
|
||||||
|
/**
|
||||||
|
* 会话内消息卡片点击跳转小程序路径,open-type="contact"时有效
|
||||||
|
*/
|
||||||
|
sendMessagePath: String,
|
||||||
|
/**
|
||||||
|
* 会话内消息卡片图片,open-type="contact"时有效
|
||||||
|
*/
|
||||||
|
sendMessageImg: String,
|
||||||
|
/**
|
||||||
|
* 打开 APP 时,向 APP 传递的参数,open-type=launchApp时有效
|
||||||
|
*/
|
||||||
|
appParameter: String,
|
||||||
|
/**
|
||||||
|
* 是否显示会话内消息卡片,设置此参数为 true,用户进入客服会话会在右下角显示"可能要发送的小程序"提示,用户点击后可以快速发送小程序消息,open-type="contact"时有效
|
||||||
|
*/
|
||||||
|
showMessageCard: Boolean,
|
||||||
|
/**
|
||||||
|
* 按钮的唯一标识,可用于设置隐私同意授权按钮的id
|
||||||
|
*/
|
||||||
|
buttonId: String,
|
||||||
|
/**
|
||||||
|
* 支付宝小程序,当 open-type 为 getAuthorize 时有效。
|
||||||
|
* 可选值:'phoneNumber' | 'userInfo'
|
||||||
|
*/
|
||||||
|
scope: String as PropType<ButtonScope>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ButtonProps = ExtractPropTypes<typeof buttonProps>
|
||||||
199
node_modules/wot-design-uni/components/wd-button/wd-button.vue
generated
vendored
Normal file
199
node_modules/wot-design-uni/components/wd-button/wd-button.vue
generated
vendored
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
<template>
|
||||||
|
<button
|
||||||
|
:id="buttonId"
|
||||||
|
:hover-class="`${disabled || loading ? '' : 'wd-button--active'}`"
|
||||||
|
:style="customStyle"
|
||||||
|
:class="[
|
||||||
|
'wd-button',
|
||||||
|
'is-' + type,
|
||||||
|
'is-' + size,
|
||||||
|
round ? 'is-round' : '',
|
||||||
|
hairline ? 'is-hairline' : '',
|
||||||
|
plain ? 'is-plain' : '',
|
||||||
|
disabled ? 'is-disabled' : '',
|
||||||
|
block ? 'is-block' : '',
|
||||||
|
loading ? 'is-loading' : '',
|
||||||
|
customClass
|
||||||
|
]"
|
||||||
|
:hover-start-time="hoverStartTime"
|
||||||
|
:hover-stay-time="hoverStayTime"
|
||||||
|
:open-type="openTypeValue"
|
||||||
|
:send-message-title="sendMessageTitle"
|
||||||
|
:send-message-path="sendMessagePath"
|
||||||
|
:send-message-img="sendMessageImg"
|
||||||
|
:app-parameter="appParameter"
|
||||||
|
:show-message-card="showMessageCard"
|
||||||
|
:session-from="sessionFrom"
|
||||||
|
:lang="lang"
|
||||||
|
:hover-stop-propagation="hoverStopPropagation"
|
||||||
|
:scope="scope"
|
||||||
|
@click="handleClick"
|
||||||
|
@getAuthorize="handleGetAuthorize"
|
||||||
|
@getuserinfo="handleGetuserinfo"
|
||||||
|
@contact="handleConcat"
|
||||||
|
@getphonenumber="handleGetphonenumber"
|
||||||
|
@getrealtimephonenumber="handleGetrealtimephonenumber"
|
||||||
|
@error="handleError"
|
||||||
|
@launchapp="handleLaunchapp"
|
||||||
|
@opensetting="handleOpensetting"
|
||||||
|
@chooseavatar="handleChooseavatar"
|
||||||
|
@agreeprivacyauthorization="handleAgreePrivacyAuthorization"
|
||||||
|
>
|
||||||
|
<view class="wd-button__content">
|
||||||
|
<view v-if="loading" class="wd-button__loading">
|
||||||
|
<view class="wd-button__loading-svg" :style="loadingStyle"></view>
|
||||||
|
</view>
|
||||||
|
<wd-icon v-else-if="icon" custom-class="wd-button__icon" :name="icon" :classPrefix="classPrefix"></wd-icon>
|
||||||
|
<view class="wd-button__text"><slot /></view>
|
||||||
|
</view>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
name: 'wd-button',
|
||||||
|
options: {
|
||||||
|
addGlobalClass: true,
|
||||||
|
virtualHost: true,
|
||||||
|
styleIsolation: 'shared'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import wdIcon from '../wd-icon/wd-icon.vue'
|
||||||
|
import { computed, watch } from 'vue'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import base64 from '../common/base64'
|
||||||
|
import { buttonProps } from './types'
|
||||||
|
|
||||||
|
const loadingIcon = (color = '#4D80F0', reverse = true) => {
|
||||||
|
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 42 42"><defs><linearGradient x1="100%" y1="0%" x2="0%" y2="0%" id="a"><stop stop-color="${
|
||||||
|
reverse ? color : '#fff'
|
||||||
|
}" offset="0%" stop-opacity="0"/><stop stop-color="${
|
||||||
|
reverse ? color : '#fff'
|
||||||
|
}" offset="100%"/></linearGradient></defs><g fill="none" fill-rule="evenodd"><path d="M21 1c11.046 0 20 8.954 20 20s-8.954 20-20 20S1 32.046 1 21 9.954 1 21 1zm0 7C13.82 8 8 13.82 8 21s5.82 13 13 13 13-5.82 13-13S28.18 8 21 8z" fill="${
|
||||||
|
reverse ? '#fff' : color
|
||||||
|
}"/><path d="M4.599 21c0 9.044 7.332 16.376 16.376 16.376 9.045 0 16.376-7.332 16.376-16.376" stroke="url(#a)" stroke-width="3.5" stroke-linecap="round"/></g></svg>`
|
||||||
|
}
|
||||||
|
const props = defineProps(buttonProps)
|
||||||
|
const emit = defineEmits([
|
||||||
|
'click',
|
||||||
|
'getuserinfo',
|
||||||
|
'contact',
|
||||||
|
'getphonenumber',
|
||||||
|
'getrealtimephonenumber',
|
||||||
|
'error',
|
||||||
|
'launchapp',
|
||||||
|
'opensetting',
|
||||||
|
'chooseavatar',
|
||||||
|
'agreeprivacyauthorization'
|
||||||
|
])
|
||||||
|
|
||||||
|
const hoverStartTime = ref<number>(20)
|
||||||
|
const hoverStayTime = ref<number>(70)
|
||||||
|
const loadingIconSvg = ref<string>('')
|
||||||
|
|
||||||
|
const loadingStyle = computed(() => {
|
||||||
|
return `background-image: url(${loadingIconSvg.value});`
|
||||||
|
})
|
||||||
|
|
||||||
|
const openTypeValue = computed(() => {
|
||||||
|
return props.disabled || props.loading ? undefined : props.openType
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.loading,
|
||||||
|
() => {
|
||||||
|
buildLoadingSvg()
|
||||||
|
},
|
||||||
|
{ deep: true, immediate: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
function handleClick(event: any) {
|
||||||
|
if (!props.disabled && !props.loading) {
|
||||||
|
emit('click', event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 支付宝小程序授权
|
||||||
|
* @param event
|
||||||
|
*/
|
||||||
|
function handleGetAuthorize(event: any) {
|
||||||
|
if (props.scope === 'phoneNumber') {
|
||||||
|
handleGetphonenumber(event)
|
||||||
|
} else if (props.scope === 'userInfo') {
|
||||||
|
handleGetuserinfo(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleGetuserinfo(event: any) {
|
||||||
|
emit('getuserinfo', event.detail)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleConcat(event: any) {
|
||||||
|
emit('contact', event.detail)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleGetphonenumber(event: any) {
|
||||||
|
emit('getphonenumber', event.detail)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleGetrealtimephonenumber(event: any) {
|
||||||
|
emit('getrealtimephonenumber', event.detail)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleError(event: any) {
|
||||||
|
emit('error', event.detail)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleLaunchapp(event: any) {
|
||||||
|
emit('launchapp', event.detail)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleOpensetting(event: any) {
|
||||||
|
emit('opensetting', event.detail)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleChooseavatar(event: any) {
|
||||||
|
emit('chooseavatar', event.detail)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleAgreePrivacyAuthorization(event: any) {
|
||||||
|
emit('agreeprivacyauthorization', event.detail)
|
||||||
|
}
|
||||||
|
function buildLoadingSvg() {
|
||||||
|
const { loadingColor, type, plain } = props
|
||||||
|
let color = loadingColor
|
||||||
|
if (!color) {
|
||||||
|
switch (type) {
|
||||||
|
case 'primary':
|
||||||
|
color = '#4D80F0'
|
||||||
|
break
|
||||||
|
case 'success':
|
||||||
|
color = '#34d19d'
|
||||||
|
break
|
||||||
|
case 'info':
|
||||||
|
color = '#333'
|
||||||
|
break
|
||||||
|
case 'warning':
|
||||||
|
color = '#f0883a'
|
||||||
|
break
|
||||||
|
case 'error':
|
||||||
|
color = '#fa4350'
|
||||||
|
break
|
||||||
|
case 'default':
|
||||||
|
color = '#333'
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const svg = loadingIcon(color, !plain)
|
||||||
|
loadingIconSvg.value = `"data:image/svg+xml;base64,${base64(svg)}"`
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import './index.scss';
|
||||||
|
</style>
|
||||||
9
node_modules/wot-design-uni/components/wd-calendar-view/index.scss
generated
vendored
Normal file
9
node_modules/wot-design-uni/components/wd-calendar-view/index.scss
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/*
|
||||||
|
* @Author: weisheng
|
||||||
|
* @Date: 2023-06-12 10:04:19
|
||||||
|
* @LastEditTime: 2023-07-15 16:16:34
|
||||||
|
* @LastEditors: weisheng
|
||||||
|
* @Description:
|
||||||
|
* @FilePath: \wot-design-uni\src\uni_modules\wot-design-uni\components\wd-calendar-view\index.scss
|
||||||
|
* 记得注释
|
||||||
|
*/
|
||||||
162
node_modules/wot-design-uni/components/wd-calendar-view/month/index.scss
generated
vendored
Normal file
162
node_modules/wot-design-uni/components/wd-calendar-view/month/index.scss
generated
vendored
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
@import '../../common/abstracts/variable';
|
||||||
|
@import '../../common/abstracts/mixin';
|
||||||
|
|
||||||
|
.wot-theme-dark {
|
||||||
|
@include b(month) {
|
||||||
|
@include e(title) {
|
||||||
|
color: $-dark-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(days) {
|
||||||
|
color: $-dark-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(day) {
|
||||||
|
@include when(disabled) {
|
||||||
|
.wd-month__day-text {
|
||||||
|
color: $-dark-color-gray;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include b(month) {
|
||||||
|
@include e(title) {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 45px;
|
||||||
|
font-size: $-calendar-panel-title-fs;
|
||||||
|
color: $-calendar-panel-title-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(days) {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
font-size: $-calendar-day-fs;
|
||||||
|
color: $-calendar-day-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(day) {
|
||||||
|
position: relative;
|
||||||
|
width: 14.285%;
|
||||||
|
height: $-calendar-day-height;
|
||||||
|
line-height: $-calendar-day-height;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: $-calendar-item-margin-bottom;
|
||||||
|
|
||||||
|
@include when(disabled) {
|
||||||
|
.wd-month__day-text {
|
||||||
|
color: $-calendar-disabled-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(current) {
|
||||||
|
color: $-calendar-active-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(selected, multiple-selected) {
|
||||||
|
.wd-month__day-container {
|
||||||
|
border-radius: $-calendar-active-border;
|
||||||
|
background: $-calendar-active-color;
|
||||||
|
color: $-calendar-selected-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(middle) {
|
||||||
|
.wd-month__day-container {
|
||||||
|
background: $-calendar-range-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@include when(multiple-middle) {
|
||||||
|
.wd-month__day-container {
|
||||||
|
background: $-calendar-active-color;
|
||||||
|
color: $-calendar-selected-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(start) {
|
||||||
|
&::after {
|
||||||
|
position: absolute;
|
||||||
|
content: '';
|
||||||
|
height: $-calendar-day-height;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
left: 50%;
|
||||||
|
background: $-calendar-range-color;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-without-end::after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wd-month__day-container {
|
||||||
|
background: $-calendar-active-color;
|
||||||
|
color: $-calendar-selected-color;
|
||||||
|
border-radius: $-calendar-active-border 0 0 $-calendar-active-border;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(end) {
|
||||||
|
&::after {
|
||||||
|
position: absolute;
|
||||||
|
content: '';
|
||||||
|
height: $-calendar-day-height;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 50%;
|
||||||
|
background: $-calendar-range-color;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wd-month__day-container {
|
||||||
|
background: $-calendar-active-color;
|
||||||
|
color: $-calendar-selected-color;
|
||||||
|
border-radius: 0 $-calendar-active-border $-calendar-active-border 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(same) {
|
||||||
|
.wd-month__day-container {
|
||||||
|
background: $-calendar-active-color;
|
||||||
|
color: $-calendar-selected-color;
|
||||||
|
border-radius: $-calendar-active-border;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(last-row){
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(day-container) {
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(day-text) {
|
||||||
|
font-weight: $-calendar-day-fw;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(day-top) {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
line-height: 1.1;
|
||||||
|
font-size: $-calendar-info-fs;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(day-bottom) {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 10px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
line-height: 1.1;
|
||||||
|
font-size: $-calendar-info-fs;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
389
node_modules/wot-design-uni/components/wd-calendar-view/month/month.vue
generated
vendored
Normal file
389
node_modules/wot-design-uni/components/wd-calendar-view/month/month.vue
generated
vendored
Normal file
@@ -0,0 +1,389 @@
|
|||||||
|
<template>
|
||||||
|
<view>
|
||||||
|
<wd-toast selector="wd-month" />
|
||||||
|
<view class="month">
|
||||||
|
<view class="wd-month">
|
||||||
|
<view class="wd-month__title" v-if="showTitle">{{ monthTitle(date) }}</view>
|
||||||
|
<view class="wd-month__days">
|
||||||
|
<view
|
||||||
|
v-for="(item, index) in days"
|
||||||
|
:key="index"
|
||||||
|
:class="`wd-month__day ${item.disabled ? 'is-disabled' : ''} ${item.isLastRow ? 'is-last-row' : ''} ${
|
||||||
|
item.type ? dayTypeClass(item.type) : ''
|
||||||
|
}`"
|
||||||
|
:style="index === 0 ? firstDayStyle : ''"
|
||||||
|
@click="handleDateClick(index)"
|
||||||
|
>
|
||||||
|
<view class="wd-month__day-container">
|
||||||
|
<view class="wd-month__day-top">{{ item.topInfo }}</view>
|
||||||
|
<view class="wd-month__day-text">
|
||||||
|
{{ item.text }}
|
||||||
|
</view>
|
||||||
|
<view class="wd-month__day-bottom">{{ item.bottomInfo }}</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
options: {
|
||||||
|
addGlobalClass: true,
|
||||||
|
virtualHost: true,
|
||||||
|
styleIsolation: 'shared'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import wdToast from '../../wd-toast/wd-toast.vue'
|
||||||
|
import { computed, ref, watch, type CSSProperties } from 'vue'
|
||||||
|
import {
|
||||||
|
compareDate,
|
||||||
|
formatMonthTitle,
|
||||||
|
getDateByDefaultTime,
|
||||||
|
getDayByOffset,
|
||||||
|
getDayOffset,
|
||||||
|
getItemClass,
|
||||||
|
getMonthEndDay,
|
||||||
|
getNextDay,
|
||||||
|
getPrevDay,
|
||||||
|
getWeekRange
|
||||||
|
} from '../utils'
|
||||||
|
import { useToast } from '../../wd-toast'
|
||||||
|
import { deepClone, isArray, isFunction, objToStyle } from '../../common/util'
|
||||||
|
import { useTranslate } from '../../composables/useTranslate'
|
||||||
|
import type { CalendarDayItem, CalendarDayType } from '../types'
|
||||||
|
import { monthProps } from './types'
|
||||||
|
|
||||||
|
const props = defineProps(monthProps)
|
||||||
|
const emit = defineEmits(['change'])
|
||||||
|
|
||||||
|
const { translate } = useTranslate('calendar-view')
|
||||||
|
|
||||||
|
const days = ref<Array<CalendarDayItem>>([])
|
||||||
|
|
||||||
|
const toast = useToast('wd-month')
|
||||||
|
|
||||||
|
const offset = computed(() => {
|
||||||
|
const firstDayOfWeek = props.firstDayOfWeek >= 7 ? props.firstDayOfWeek % 7 : props.firstDayOfWeek
|
||||||
|
const offset = (7 + new Date(props.date).getDay() - firstDayOfWeek) % 7
|
||||||
|
return offset
|
||||||
|
})
|
||||||
|
|
||||||
|
const dayTypeClass = computed(() => {
|
||||||
|
return (monthType: CalendarDayType) => {
|
||||||
|
return getItemClass(monthType, props.value, props.type)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const monthTitle = computed(() => {
|
||||||
|
return (date: number) => {
|
||||||
|
return formatMonthTitle(date)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const firstDayStyle = computed(() => {
|
||||||
|
const dayStyle: CSSProperties = {}
|
||||||
|
dayStyle.marginLeft = `${(100 / 7) * offset.value}%`
|
||||||
|
return objToStyle(dayStyle)
|
||||||
|
})
|
||||||
|
|
||||||
|
const isLastRow = (date: number) => {
|
||||||
|
const currentDate = new Date(date)
|
||||||
|
const currentDay = currentDate.getDate()
|
||||||
|
const daysInMonth = getMonthEndDay(currentDate.getFullYear(), currentDate.getMonth() + 1)
|
||||||
|
const totalDaysShown = offset.value + daysInMonth
|
||||||
|
const totalRows = Math.ceil(totalDaysShown / 7)
|
||||||
|
return Math.ceil((offset.value + currentDay) / 7) === totalRows
|
||||||
|
}
|
||||||
|
watch(
|
||||||
|
[() => props.type, () => props.date, () => props.value, () => props.minDate, () => props.maxDate, () => props.formatter],
|
||||||
|
() => {
|
||||||
|
setDays()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deep: true,
|
||||||
|
immediate: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
function setDays() {
|
||||||
|
const dayList: Array<CalendarDayItem> = []
|
||||||
|
const date = new Date(props.date)
|
||||||
|
const year = date.getFullYear()
|
||||||
|
const month = date.getMonth()
|
||||||
|
const totalDay = getMonthEndDay(year, month + 1)
|
||||||
|
let value = props.value
|
||||||
|
if ((props.type === 'week' || props.type === 'weekrange') && value) {
|
||||||
|
value = getWeekValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let day = 1; day <= totalDay; day++) {
|
||||||
|
const date = new Date(year, month, day).getTime()
|
||||||
|
let type: CalendarDayType = getDayType(date, value as number | number[] | null)
|
||||||
|
if (!type && compareDate(date, Date.now()) === 0) {
|
||||||
|
type = 'current'
|
||||||
|
}
|
||||||
|
const dayObj = getFormatterDate(date, day, type)
|
||||||
|
dayList.push(dayObj)
|
||||||
|
}
|
||||||
|
days.value = dayList
|
||||||
|
}
|
||||||
|
function getDayType(date: number, value: number | number[] | null): CalendarDayType {
|
||||||
|
switch (props.type) {
|
||||||
|
case 'date':
|
||||||
|
case 'datetime':
|
||||||
|
return getDateType(date)
|
||||||
|
case 'dates':
|
||||||
|
return getDatesType(date)
|
||||||
|
case 'daterange':
|
||||||
|
case 'datetimerange':
|
||||||
|
return getDatetimeType(date, value)
|
||||||
|
case 'week':
|
||||||
|
return getWeektimeType(date, value)
|
||||||
|
case 'weekrange':
|
||||||
|
return getWeektimeType(date, value)
|
||||||
|
default:
|
||||||
|
return getDateType(date)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function getDateType(date: number): CalendarDayType {
|
||||||
|
if (props.value && compareDate(date, props.value as number) === 0) {
|
||||||
|
return 'selected'
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDatesType(date: number): CalendarDayType {
|
||||||
|
const { value } = props
|
||||||
|
let type: CalendarDayType = ''
|
||||||
|
|
||||||
|
if (!isArray(value)) return type
|
||||||
|
const isSelected = (day: number) => {
|
||||||
|
return value.some((item) => compareDate(day, item) === 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSelected(date)) {
|
||||||
|
const prevDay = getPrevDay(date)
|
||||||
|
const nextDay = getNextDay(date)
|
||||||
|
const prevSelected = isSelected(prevDay)
|
||||||
|
const nextSelected = isSelected(nextDay)
|
||||||
|
if (prevSelected && nextSelected) {
|
||||||
|
type = 'multiple-middle'
|
||||||
|
} else if (prevSelected) {
|
||||||
|
type = 'end'
|
||||||
|
} else if (nextSelected) {
|
||||||
|
type = 'start'
|
||||||
|
} else {
|
||||||
|
type = 'multiple-selected'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return type
|
||||||
|
}
|
||||||
|
function getDatetimeType(date: number, value: number | number[] | null) {
|
||||||
|
const [startDate, endDate] = isArray(value) ? value : []
|
||||||
|
|
||||||
|
if (startDate && compareDate(date, startDate) === 0) {
|
||||||
|
if (props.allowSameDay && endDate && compareDate(startDate, endDate) === 0) {
|
||||||
|
return 'same'
|
||||||
|
}
|
||||||
|
return 'start'
|
||||||
|
} else if (endDate && compareDate(date, endDate) === 0) {
|
||||||
|
return 'end'
|
||||||
|
} else if (startDate && endDate && compareDate(date, startDate) === 1 && compareDate(date, endDate) === -1) {
|
||||||
|
return 'middle'
|
||||||
|
} else {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function getWeektimeType(date: number, value: number | number[] | null) {
|
||||||
|
const [startDate, endDate] = isArray(value) ? value : []
|
||||||
|
|
||||||
|
if (startDate && compareDate(date, startDate) === 0) {
|
||||||
|
return 'start'
|
||||||
|
} else if (endDate && compareDate(date, endDate) === 0) {
|
||||||
|
return 'end'
|
||||||
|
} else if (startDate && endDate && compareDate(date, startDate) === 1 && compareDate(date, endDate) === -1) {
|
||||||
|
return 'middle'
|
||||||
|
} else {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function getWeekValue() {
|
||||||
|
if (props.type === 'week') {
|
||||||
|
return getWeekRange(props.value as number, props.firstDayOfWeek)
|
||||||
|
} else {
|
||||||
|
const [startDate, endDate] = (props.value as any) || []
|
||||||
|
|
||||||
|
if (startDate) {
|
||||||
|
const firstWeekRange = getWeekRange(startDate, props.firstDayOfWeek)
|
||||||
|
|
||||||
|
if (endDate) {
|
||||||
|
const endWeekRange = getWeekRange(endDate, props.firstDayOfWeek)
|
||||||
|
|
||||||
|
return [firstWeekRange[0], endWeekRange[1]]
|
||||||
|
} else {
|
||||||
|
return firstWeekRange
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function handleDateClick(index: number) {
|
||||||
|
const date = days.value[index]
|
||||||
|
switch (props.type) {
|
||||||
|
case 'date':
|
||||||
|
case 'datetime':
|
||||||
|
handleDateChange(date)
|
||||||
|
break
|
||||||
|
case 'dates':
|
||||||
|
handleDatesChange(date)
|
||||||
|
break
|
||||||
|
case 'daterange':
|
||||||
|
case 'datetimerange':
|
||||||
|
handleDateRangeChange(date)
|
||||||
|
break
|
||||||
|
case 'week':
|
||||||
|
handleWeekChange(date)
|
||||||
|
break
|
||||||
|
case 'weekrange':
|
||||||
|
handleWeekRangeChange(date)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
handleDateChange(date)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function getDate(date: number, isEnd: boolean = false) {
|
||||||
|
date = props.defaultTime && props.defaultTime.length > 0 ? getDateByDefaultTime(date, isEnd ? props.defaultTime[1] : props.defaultTime[0]) : date
|
||||||
|
|
||||||
|
if (date < props.minDate) return props.minDate
|
||||||
|
|
||||||
|
if (date > props.maxDate) return props.maxDate
|
||||||
|
|
||||||
|
return date
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDateChange(date: CalendarDayItem) {
|
||||||
|
if (date.disabled) return
|
||||||
|
|
||||||
|
if (date.type !== 'selected') {
|
||||||
|
emit('change', {
|
||||||
|
value: getDate(date.date),
|
||||||
|
type: 'start'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function handleDatesChange(date: CalendarDayItem) {
|
||||||
|
if (date.disabled) return
|
||||||
|
const currentValue = deepClone(isArray(props.value) ? props.value : [])
|
||||||
|
const dateIndex = currentValue.findIndex((item) => item && compareDate(item, date.date) === 0)
|
||||||
|
const value = dateIndex === -1 ? [...currentValue, getDate(date.date)] : currentValue.filter((_, index) => index !== dateIndex)
|
||||||
|
emit('change', { value })
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDateRangeChange(date: CalendarDayItem) {
|
||||||
|
if (date.disabled) return
|
||||||
|
|
||||||
|
let value: (number | null)[] = []
|
||||||
|
let type: CalendarDayType = ''
|
||||||
|
const [startDate, endDate] = deepClone(isArray(props.value) ? props.value : [])
|
||||||
|
const compare = compareDate(date.date, startDate)
|
||||||
|
|
||||||
|
// 禁止选择同个日期
|
||||||
|
if (!props.allowSameDay && compare === 0 && (props.type === 'daterange' || props.type === 'datetimerange') && !endDate) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startDate && !endDate && compare > -1) {
|
||||||
|
// 不能选择超过最大范围的日期
|
||||||
|
if (props.maxRange && getDayOffset(date.date, startDate) > props.maxRange) {
|
||||||
|
const maxEndDate = getDayByOffset(startDate, props.maxRange - 1)
|
||||||
|
value = [startDate, getDate(maxEndDate, true)]
|
||||||
|
toast.show({
|
||||||
|
msg: props.rangePrompt || translate('rangePrompt', props.maxRange)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
value = [startDate, getDate(date.date, true)]
|
||||||
|
}
|
||||||
|
} else if (props.type === 'datetimerange' && startDate && endDate) {
|
||||||
|
// 时间范围类型,且有开始时间和结束时间,需要支持重新点击开始日期和结束日期可以重新修改时间
|
||||||
|
if (compare === 0) {
|
||||||
|
type = 'start'
|
||||||
|
value = props.value as number[]
|
||||||
|
} else if (compareDate(date.date, endDate) === 0) {
|
||||||
|
type = 'end'
|
||||||
|
value = props.value as number[]
|
||||||
|
} else {
|
||||||
|
value = [getDate(date.date), null]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
value = [getDate(date.date), null]
|
||||||
|
}
|
||||||
|
|
||||||
|
emit('change', {
|
||||||
|
value,
|
||||||
|
type: type || (value[1] ? 'end' : 'start')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
function handleWeekChange(date: CalendarDayItem) {
|
||||||
|
const [weekStart] = getWeekRange(date.date, props.firstDayOfWeek)
|
||||||
|
|
||||||
|
// 周的第一天如果是禁用状态,则不可选中
|
||||||
|
if (getFormatterDate(weekStart, new Date(weekStart).getDate()).disabled) return
|
||||||
|
|
||||||
|
emit('change', {
|
||||||
|
value: getDate(weekStart) + 24 * 60 * 60 * 1000
|
||||||
|
})
|
||||||
|
}
|
||||||
|
function handleWeekRangeChange(date: CalendarDayItem) {
|
||||||
|
const [weekStartDate] = getWeekRange(date.date, props.firstDayOfWeek)
|
||||||
|
|
||||||
|
// 周的第一天如果是禁用状态,则不可选中
|
||||||
|
if (getFormatterDate(weekStartDate, new Date(weekStartDate).getDate()).disabled) return
|
||||||
|
|
||||||
|
let value: (number | null)[] = []
|
||||||
|
const [startDate, endDate] = deepClone(isArray(props.value) ? props.value : [])
|
||||||
|
const [startWeekStartDate] = startDate ? getWeekRange(startDate, props.firstDayOfWeek) : []
|
||||||
|
const compare = compareDate(weekStartDate, startWeekStartDate)
|
||||||
|
|
||||||
|
if (startDate && !endDate && compare > -1) {
|
||||||
|
if (!props.allowSameDay && compare === 0) return
|
||||||
|
|
||||||
|
value = [getDate(startWeekStartDate) + 24 * 60 * 60 * 1000, getDate(weekStartDate) + 24 * 60 * 60 * 1000]
|
||||||
|
} else {
|
||||||
|
value = [getDate(weekStartDate) + 24 * 60 * 60 * 1000, null]
|
||||||
|
}
|
||||||
|
|
||||||
|
emit('change', {
|
||||||
|
value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
function getFormatterDate(date: number, day: string | number, type?: CalendarDayType) {
|
||||||
|
let dayObj: CalendarDayItem = {
|
||||||
|
date: date,
|
||||||
|
text: day,
|
||||||
|
topInfo: '',
|
||||||
|
bottomInfo: '',
|
||||||
|
type,
|
||||||
|
disabled: compareDate(date, props.minDate) === -1 || compareDate(date, props.maxDate) === 1,
|
||||||
|
isLastRow: isLastRow(date)
|
||||||
|
}
|
||||||
|
if (props.formatter) {
|
||||||
|
if (isFunction(props.formatter)) {
|
||||||
|
dayObj = props.formatter(dayObj)
|
||||||
|
} else {
|
||||||
|
console.error('[wot-design] error(wd-calendar-view): the formatter prop of wd-calendar-view should be a function')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dayObj
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import './index.scss';
|
||||||
|
</style>
|
||||||
20
node_modules/wot-design-uni/components/wd-calendar-view/month/types.ts
generated
vendored
Normal file
20
node_modules/wot-design-uni/components/wd-calendar-view/month/types.ts
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import type { PropType } from 'vue'
|
||||||
|
import { makeBooleanProp, makeRequiredProp } from '../../common/props'
|
||||||
|
import type { CalendarFormatter, CalendarType } from '../types'
|
||||||
|
|
||||||
|
export const monthProps = {
|
||||||
|
type: makeRequiredProp(String as PropType<CalendarType>),
|
||||||
|
date: makeRequiredProp(Number),
|
||||||
|
value: makeRequiredProp([Number, Array, null] as PropType<number | (number | null)[] | null>),
|
||||||
|
minDate: makeRequiredProp(Number),
|
||||||
|
maxDate: makeRequiredProp(Number),
|
||||||
|
firstDayOfWeek: makeRequiredProp(Number),
|
||||||
|
formatter: Function as PropType<CalendarFormatter>,
|
||||||
|
maxRange: Number,
|
||||||
|
rangePrompt: String,
|
||||||
|
allowSameDay: makeBooleanProp(false),
|
||||||
|
defaultTime: {
|
||||||
|
type: [Array] as PropType<Array<number[]>>
|
||||||
|
},
|
||||||
|
showTitle: makeBooleanProp(true)
|
||||||
|
}
|
||||||
89
node_modules/wot-design-uni/components/wd-calendar-view/monthPanel/index.scss
generated
vendored
Normal file
89
node_modules/wot-design-uni/components/wd-calendar-view/monthPanel/index.scss
generated
vendored
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
@import '../../common/abstracts/variable';
|
||||||
|
@import '../../common/abstracts/mixin';
|
||||||
|
|
||||||
|
.wot-theme-dark {
|
||||||
|
@include b(month-panel) {
|
||||||
|
@include e(title) {
|
||||||
|
color: $-dark-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(weeks) {
|
||||||
|
box-shadow: 0px 4px 8px 0 rgba(255, 255, 255, 0.02);
|
||||||
|
color: $-dark-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(time-label) {
|
||||||
|
color: $-dark-color;
|
||||||
|
&::after{
|
||||||
|
background: $-dark-background4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include b(month-panel) {
|
||||||
|
font-size: $-calendar-fs;
|
||||||
|
|
||||||
|
@include e(title) {
|
||||||
|
padding: 5px 0;
|
||||||
|
text-align: center;
|
||||||
|
font-size: $-calendar-panel-title-fs;
|
||||||
|
color: $-calendar-panel-title-color;
|
||||||
|
padding: $-calendar-panel-padding;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(weeks) {
|
||||||
|
display: flex;
|
||||||
|
height: $-calendar-week-height;
|
||||||
|
line-height: $-calendar-week-height;
|
||||||
|
box-shadow: 0px 4px 8px 0 rgba(0, 0, 0, 0.02);
|
||||||
|
color: $-calendar-week-color;
|
||||||
|
font-size: $-calendar-week-fs;
|
||||||
|
padding: $-calendar-panel-padding;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(week) {
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(container) {
|
||||||
|
padding: $-calendar-panel-padding;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(time) {
|
||||||
|
display: flex;
|
||||||
|
box-shadow: 0px -4px 8px 0px rgba(0, 0, 0, 0.02);
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(time-label) {
|
||||||
|
position: relative;
|
||||||
|
flex: 1;
|
||||||
|
font-size: $-picker-column-fs;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 125px;
|
||||||
|
color: $-picker-column-color;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
position: absolute;
|
||||||
|
content: '';
|
||||||
|
height: 35px;
|
||||||
|
top: 50%;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
background: $-picker-column-select-bg;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(time-text) {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(time-picker) {
|
||||||
|
flex: 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
374
node_modules/wot-design-uni/components/wd-calendar-view/monthPanel/month-panel.vue
generated
vendored
Normal file
374
node_modules/wot-design-uni/components/wd-calendar-view/monthPanel/month-panel.vue
generated
vendored
Normal file
@@ -0,0 +1,374 @@
|
|||||||
|
<template>
|
||||||
|
<view class="wd-month-panel">
|
||||||
|
<view v-if="showPanelTitle" class="wd-month-panel__title">
|
||||||
|
{{ title }}
|
||||||
|
</view>
|
||||||
|
<view class="wd-month-panel__weeks">
|
||||||
|
<view v-for="item in 7" :key="item" class="wd-month-panel__week">{{ weekLabel(item + firstDayOfWeek) }}</view>
|
||||||
|
</view>
|
||||||
|
<scroll-view
|
||||||
|
:class="`wd-month-panel__container ${!!timeType ? 'wd-month-panel__container--time' : ''}`"
|
||||||
|
:style="`height: ${scrollHeight}px`"
|
||||||
|
scroll-y
|
||||||
|
@scroll="monthScroll"
|
||||||
|
:scroll-top="scrollTop"
|
||||||
|
>
|
||||||
|
<view v-for="(item, index) in months" :key="index" :id="`month${index}`">
|
||||||
|
<month
|
||||||
|
:type="type"
|
||||||
|
:date="item.date"
|
||||||
|
:value="value"
|
||||||
|
:min-date="minDate"
|
||||||
|
:max-date="maxDate"
|
||||||
|
:first-day-of-week="firstDayOfWeek"
|
||||||
|
:formatter="formatter"
|
||||||
|
:max-range="maxRange"
|
||||||
|
:range-prompt="rangePrompt"
|
||||||
|
:allow-same-day="allowSameDay"
|
||||||
|
:default-time="defaultTime"
|
||||||
|
:showTitle="index !== 0"
|
||||||
|
@change="handleDateChange"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
<view v-if="timeType" class="wd-month-panel__time">
|
||||||
|
<view v-if="type === 'datetimerange'" class="wd-month-panel__time-label">
|
||||||
|
<view class="wd-month-panel__time-text">{{ timeType === 'start' ? translate('startTime') : translate('endTime') }}</view>
|
||||||
|
</view>
|
||||||
|
<view class="wd-month-panel__time-picker">
|
||||||
|
<wd-picker-view
|
||||||
|
v-if="timeData.length"
|
||||||
|
v-model="timeValue"
|
||||||
|
:columns="timeData"
|
||||||
|
:columns-height="125"
|
||||||
|
:immediate-change="immediateChange"
|
||||||
|
@change="handleTimeChange"
|
||||||
|
@pickstart="handlePickStart"
|
||||||
|
@pickend="handlePickEnd"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
options: {
|
||||||
|
addGlobalClass: true,
|
||||||
|
virtualHost: true,
|
||||||
|
styleIsolation: 'shared'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import wdPickerView from '../../wd-picker-view/wd-picker-view.vue'
|
||||||
|
import { computed, ref, watch, onMounted } from 'vue'
|
||||||
|
import { debounce, isArray, isEqual, isNumber, pause } from '../../common/util'
|
||||||
|
import { compareMonth, formatMonthTitle, getMonthEndDay, getMonths, getTimeData, getWeekLabel } from '../utils'
|
||||||
|
import Month from '../month/month.vue'
|
||||||
|
import { monthPanelProps, type MonthInfo, type MonthPanelTimeType, type MonthPanelExpose } from './types'
|
||||||
|
import { useTranslate } from '../../composables/useTranslate'
|
||||||
|
import type { CalendarItem } from '../types'
|
||||||
|
|
||||||
|
const props = defineProps(monthPanelProps)
|
||||||
|
const emit = defineEmits(['change', 'pickstart', 'pickend'])
|
||||||
|
|
||||||
|
const { translate } = useTranslate('calendar-view')
|
||||||
|
|
||||||
|
const scrollTop = ref<number>(0) // 滚动位置
|
||||||
|
const scrollIndex = ref<number>(0) // 当前显示的月份索引
|
||||||
|
const timeValue = ref<number[]>([]) // 当前选中的时分秒
|
||||||
|
|
||||||
|
const timeType = ref<MonthPanelTimeType>('') // 当前时间类型,是开始还是结束
|
||||||
|
const innerValue = ref<string | number | (number | null)[]>('') // 内部保存一个值,用于判断新老值,避免监听器触发
|
||||||
|
|
||||||
|
const handleChange = debounce((value) => {
|
||||||
|
emit('change', {
|
||||||
|
value
|
||||||
|
})
|
||||||
|
}, 50)
|
||||||
|
|
||||||
|
// 时间picker的列数据
|
||||||
|
const timeData = computed<Array<CalendarItem[]>>(() => {
|
||||||
|
let timeColumns: Array<CalendarItem[]> = []
|
||||||
|
if (props.type === 'datetime' && isNumber(props.value)) {
|
||||||
|
const date = new Date(props.value)
|
||||||
|
date.setHours(timeValue.value[0])
|
||||||
|
date.setMinutes(timeValue.value[1])
|
||||||
|
date.setSeconds(props.hideSecond ? 0 : timeValue.value[2])
|
||||||
|
const dateTime = date.getTime()
|
||||||
|
timeColumns = getTime(dateTime) || []
|
||||||
|
} else if (isArray(props.value) && props.type === 'datetimerange') {
|
||||||
|
const [start, end] = props.value!
|
||||||
|
const dataValue = timeType.value === 'start' ? start : end
|
||||||
|
const date = new Date(dataValue || '')
|
||||||
|
date.setHours(timeValue.value[0])
|
||||||
|
date.setMinutes(timeValue.value[1])
|
||||||
|
date.setSeconds(props.hideSecond ? 0 : timeValue.value[2])
|
||||||
|
const dateTime = date.getTime()
|
||||||
|
const finalValue = [start, end]
|
||||||
|
if (timeType.value === 'start') {
|
||||||
|
finalValue[0] = dateTime
|
||||||
|
} else {
|
||||||
|
finalValue[1] = dateTime
|
||||||
|
}
|
||||||
|
timeColumns = getTime(finalValue, timeType.value) || []
|
||||||
|
}
|
||||||
|
return timeColumns
|
||||||
|
})
|
||||||
|
|
||||||
|
// 标题
|
||||||
|
const title = computed(() => {
|
||||||
|
return formatMonthTitle(months.value[scrollIndex.value].date)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 周标题
|
||||||
|
const weekLabel = computed(() => {
|
||||||
|
return (index: number) => {
|
||||||
|
return getWeekLabel(index - 1)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 滚动区域的高度
|
||||||
|
const scrollHeight = computed(() => {
|
||||||
|
const scrollHeight: number = timeType.value ? props.panelHeight - 125 : props.panelHeight
|
||||||
|
return scrollHeight
|
||||||
|
})
|
||||||
|
|
||||||
|
// 月份日期和月份高度
|
||||||
|
const months = computed<MonthInfo[]>(() => {
|
||||||
|
return getMonths(props.minDate, props.maxDate).map((month, index) => {
|
||||||
|
const offset = (7 + new Date(month).getDay() - props.firstDayOfWeek) % 7
|
||||||
|
const totalDay = getMonthEndDay(new Date(month).getFullYear(), new Date(month).getMonth() + 1)
|
||||||
|
const rows = Math.ceil((offset + totalDay) / 7)
|
||||||
|
return {
|
||||||
|
height: rows * 64 + (rows - 1) * 4 + (index === 0 ? 0 : 45), // 每行64px高度,除最后一行外每行加4px margin,加上标题45px
|
||||||
|
date: month
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.type,
|
||||||
|
(val) => {
|
||||||
|
if (
|
||||||
|
(val === 'datetime' && props.value) ||
|
||||||
|
(val === 'datetimerange' && isArray(props.value) && props.value && props.value.length > 0 && props.value[0])
|
||||||
|
) {
|
||||||
|
setTime(props.value, 'start')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deep: true,
|
||||||
|
immediate: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.value,
|
||||||
|
(val) => {
|
||||||
|
if (isEqual(val, innerValue.value)) return
|
||||||
|
|
||||||
|
if ((props.type === 'datetime' && val) || (props.type === 'datetimerange' && val && isArray(val) && val.length > 0 && val[0])) {
|
||||||
|
setTime(val, 'start')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deep: true,
|
||||||
|
immediate: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
scrollIntoView()
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使当前日期或者选中日期滚动到可视区域
|
||||||
|
*/
|
||||||
|
async function scrollIntoView() {
|
||||||
|
// 等待渲染完毕
|
||||||
|
await pause()
|
||||||
|
let activeDate: number | null = 0
|
||||||
|
if (isArray(props.value)) {
|
||||||
|
// 对数组按时间排序,取第一个值
|
||||||
|
const sortedValue = [...props.value].sort((a, b) => (a || 0) - (b || 0))
|
||||||
|
activeDate = sortedValue[0]
|
||||||
|
} else if (isNumber(props.value)) {
|
||||||
|
activeDate = props.value
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!activeDate) {
|
||||||
|
activeDate = Date.now()
|
||||||
|
}
|
||||||
|
|
||||||
|
let top: number = 0
|
||||||
|
let activeMonthIndex = -1
|
||||||
|
for (let index = 0; index < months.value.length; index++) {
|
||||||
|
if (compareMonth(months.value[index].date, activeDate) === 0) {
|
||||||
|
activeMonthIndex = index
|
||||||
|
// 找到选中月份后,计算选中日期在月份中的位置
|
||||||
|
const date = new Date(activeDate)
|
||||||
|
const day = date.getDate()
|
||||||
|
const firstDay = new Date(date.getFullYear(), date.getMonth(), 1)
|
||||||
|
const offset = (7 + firstDay.getDay() - props.firstDayOfWeek) % 7
|
||||||
|
const row = Math.floor((offset + day - 1) / 7)
|
||||||
|
// 每行高度64px,每行加4px margin
|
||||||
|
top += row * 64 + row * 4
|
||||||
|
break
|
||||||
|
}
|
||||||
|
top += months.value[index] ? Number(months.value[index].height) : 0
|
||||||
|
}
|
||||||
|
scrollTop.value = 0
|
||||||
|
if (top > 0) {
|
||||||
|
await pause()
|
||||||
|
// 如果不是第一个月才加45
|
||||||
|
scrollTop.value = top + (activeMonthIndex > 0 ? 45 : 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 获取时间 picker 的数据
|
||||||
|
* @param {timestamp|array} value 当前时间
|
||||||
|
* @param {string} type 类型,是开始还是结束
|
||||||
|
*/
|
||||||
|
function getTime(value: number | (number | null)[], type?: string) {
|
||||||
|
if (props.type === 'datetime') {
|
||||||
|
return getTimeData({
|
||||||
|
date: value as number,
|
||||||
|
minDate: props.minDate,
|
||||||
|
maxDate: props.maxDate,
|
||||||
|
filter: props.timeFilter,
|
||||||
|
isHideSecond: props.hideSecond
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
if (type === 'start' && isArray(props.value)) {
|
||||||
|
return getTimeData({
|
||||||
|
date: (value as Array<number>)[0],
|
||||||
|
minDate: props.minDate,
|
||||||
|
maxDate: props.value[1] ? props.value[1] : props.maxDate,
|
||||||
|
filter: props.timeFilter,
|
||||||
|
isHideSecond: props.hideSecond
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
return getTimeData({
|
||||||
|
date: (value as Array<number>)[1],
|
||||||
|
minDate: (value as Array<number>)[0],
|
||||||
|
maxDate: props.maxDate,
|
||||||
|
filter: props.timeFilter,
|
||||||
|
isHideSecond: props.hideSecond
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 获取 date 的时分秒
|
||||||
|
* @param {timestamp} date 时间
|
||||||
|
* @param {string} type 类型,是开始还是结束
|
||||||
|
*/
|
||||||
|
function getTimeValue(date: number | (number | null)[], type: MonthPanelTimeType) {
|
||||||
|
let dateValue: Date = new Date()
|
||||||
|
if (props.type === 'datetime') {
|
||||||
|
dateValue = new Date(date as number)
|
||||||
|
} else if (isArray(date)) {
|
||||||
|
if (type === 'start') {
|
||||||
|
dateValue = new Date(date[0] || '')
|
||||||
|
} else {
|
||||||
|
dateValue = new Date(date[1] || '')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const hour = dateValue.getHours()
|
||||||
|
const minute = dateValue.getMinutes()
|
||||||
|
const second = dateValue.getSeconds()
|
||||||
|
return props.hideSecond ? [hour, minute] : [hour, minute, second]
|
||||||
|
}
|
||||||
|
|
||||||
|
function setTime(value: number | (number | null)[], type?: MonthPanelTimeType) {
|
||||||
|
if (isArray(value) && value[0] && value[1] && type === 'start' && timeType.value === 'start') {
|
||||||
|
type = 'end'
|
||||||
|
}
|
||||||
|
timeType.value = type || ''
|
||||||
|
timeValue.value = getTimeValue(value, type || '')
|
||||||
|
}
|
||||||
|
function handleDateChange({ value, type }: { value: number | (number | null)[]; type?: MonthPanelTimeType }) {
|
||||||
|
if (!isEqual(value, props.value)) {
|
||||||
|
// 内部保存一个值,用于判断新老值,避免监听器触发
|
||||||
|
innerValue.value = value
|
||||||
|
handleChange(value)
|
||||||
|
}
|
||||||
|
// datetime 和 datetimerange 类型,需要计算 timeData 并做展示
|
||||||
|
if (props.type.indexOf('time') > -1) {
|
||||||
|
setTime(value, type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function handleTimeChange({ value }: { value: any[] }) {
|
||||||
|
if (!props.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (props.type === 'datetime' && isNumber(props.value)) {
|
||||||
|
const date = new Date(props.value)
|
||||||
|
date.setHours(value[0])
|
||||||
|
date.setMinutes(value[1])
|
||||||
|
date.setSeconds(props.hideSecond ? 0 : value[2])
|
||||||
|
const dateTime = date.getTime()
|
||||||
|
handleChange(dateTime)
|
||||||
|
} else if (isArray(props.value) && props.type === 'datetimerange') {
|
||||||
|
const [start, end] = props.value!
|
||||||
|
const dataValue = timeType.value === 'start' ? start : end
|
||||||
|
const date = new Date(dataValue || '')
|
||||||
|
date.setHours(value[0])
|
||||||
|
date.setMinutes(value[1])
|
||||||
|
date.setSeconds(props.hideSecond ? 0 : value[2])
|
||||||
|
const dateTime = date.getTime()
|
||||||
|
|
||||||
|
if (dateTime === dataValue) return
|
||||||
|
|
||||||
|
const finalValue = [start, end]
|
||||||
|
if (timeType.value === 'start') {
|
||||||
|
finalValue[0] = dateTime
|
||||||
|
} else {
|
||||||
|
finalValue[1] = dateTime
|
||||||
|
}
|
||||||
|
innerValue.value = finalValue // 内部保存一个值,用于判断新老值,避免监听器触发
|
||||||
|
handleChange(finalValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function handlePickStart() {
|
||||||
|
emit('pickstart')
|
||||||
|
}
|
||||||
|
function handlePickEnd() {
|
||||||
|
emit('pickend')
|
||||||
|
}
|
||||||
|
|
||||||
|
const monthScroll = (event: { detail: { scrollTop: number } }) => {
|
||||||
|
if (months.value.length <= 1) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const scrollTop = Math.max(0, event.detail.scrollTop)
|
||||||
|
doSetSubtitle(scrollTop)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置小标题
|
||||||
|
* scrollTop 滚动条位置
|
||||||
|
*/
|
||||||
|
function doSetSubtitle(scrollTop: number) {
|
||||||
|
let height: number = 0 // 月份高度和
|
||||||
|
for (let index = 0; index < months.value.length; index++) {
|
||||||
|
height = height + months.value[index].height
|
||||||
|
if (scrollTop < height) {
|
||||||
|
scrollIndex.value = index
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose<MonthPanelExpose>({
|
||||||
|
scrollIntoView
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import './index.scss';
|
||||||
|
</style>
|
||||||
96
node_modules/wot-design-uni/components/wd-calendar-view/monthPanel/types.ts
generated
vendored
Normal file
96
node_modules/wot-design-uni/components/wd-calendar-view/monthPanel/types.ts
generated
vendored
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
import type { ComponentPublicInstance, ExtractPropTypes, PropType } from 'vue'
|
||||||
|
import { makeBooleanProp, makeNumberProp, makeStringProp } from '../../common/props'
|
||||||
|
import type { CalendarFormatter, CalendarTimeFilter, CalendarType } from '../types'
|
||||||
|
|
||||||
|
const now = new Date()
|
||||||
|
const defaultMinDate = new Date(now.getFullYear(), now.getMonth() - 6, now.getDate()).getTime()
|
||||||
|
const defaultMaxDate = new Date(now.getFullYear(), now.getMonth() + 6, now.getDate(), 23, 59, 59).getTime()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 月份信息
|
||||||
|
*/
|
||||||
|
export interface MonthInfo {
|
||||||
|
date: number
|
||||||
|
height: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export const monthPanelProps = {
|
||||||
|
/**
|
||||||
|
* 日期类型
|
||||||
|
*/
|
||||||
|
type: makeStringProp<CalendarType>('date'),
|
||||||
|
/**
|
||||||
|
* 选中值,为 13 位时间戳或时间戳数组
|
||||||
|
*/
|
||||||
|
value: {
|
||||||
|
type: [Number, Array, null] as PropType<number | (number | null)[] | null>,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 最小日期,为 13 位时间戳
|
||||||
|
*/
|
||||||
|
minDate: makeNumberProp(defaultMinDate),
|
||||||
|
/**
|
||||||
|
* 最大日期,为 13 位时间戳
|
||||||
|
*/
|
||||||
|
maxDate: makeNumberProp(defaultMaxDate),
|
||||||
|
/**
|
||||||
|
* 周起始天
|
||||||
|
*/
|
||||||
|
firstDayOfWeek: makeNumberProp(0),
|
||||||
|
/**
|
||||||
|
* 日期格式化函数
|
||||||
|
*/
|
||||||
|
formatter: Function as PropType<CalendarFormatter>,
|
||||||
|
/**
|
||||||
|
* type 为范围选择时有效,最大日期范围
|
||||||
|
*/
|
||||||
|
maxRange: Number,
|
||||||
|
/**
|
||||||
|
* type 为范围选择时有效,选择超出最大日期范围时的错误提示文案
|
||||||
|
*/
|
||||||
|
rangePrompt: String,
|
||||||
|
/**
|
||||||
|
* type 为范围选择时有效,是否允许选择同一天
|
||||||
|
*/
|
||||||
|
allowSameDay: makeBooleanProp(false),
|
||||||
|
/**
|
||||||
|
* 是否展示面板标题,自动计算当前滚动的日期月份
|
||||||
|
*/
|
||||||
|
showPanelTitle: makeBooleanProp(false),
|
||||||
|
/**
|
||||||
|
* 选中日期所使用的当日内具体时刻
|
||||||
|
*/
|
||||||
|
defaultTime: {
|
||||||
|
type: [Array] as PropType<Array<number[]>>
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 可滚动面板的高度
|
||||||
|
*/
|
||||||
|
panelHeight: makeNumberProp(378),
|
||||||
|
/**
|
||||||
|
* type 为 'datetime' 或 'datetimerange' 时有效,用于过滤时间选择器的数据
|
||||||
|
*/
|
||||||
|
timeFilter: Function as PropType<CalendarTimeFilter>,
|
||||||
|
/**
|
||||||
|
* type 为 'datetime' 或 'datetimerange' 时有效,是否不展示秒修改
|
||||||
|
*/
|
||||||
|
hideSecond: makeBooleanProp(false),
|
||||||
|
/**
|
||||||
|
* 是否在手指松开时立即触发picker-view的 change 事件。若不开启则会在滚动动画结束后触发 change 事件,1.2.25版本起提供,仅微信小程序和支付宝小程序支持。
|
||||||
|
*/
|
||||||
|
immediateChange: makeBooleanProp(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
export type MonthPanelProps = ExtractPropTypes<typeof monthPanelProps>
|
||||||
|
|
||||||
|
export type MonthPanelTimeType = 'start' | 'end' | ''
|
||||||
|
|
||||||
|
export type MonthPanelExpose = {
|
||||||
|
/**
|
||||||
|
* 使当前日期或者选中日期滚动到可视区域
|
||||||
|
*/
|
||||||
|
scrollIntoView: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export type MonthPanelInstance = ComponentPublicInstance<MonthPanelProps, MonthPanelExpose>
|
||||||
113
node_modules/wot-design-uni/components/wd-calendar-view/types.ts
generated
vendored
Normal file
113
node_modules/wot-design-uni/components/wd-calendar-view/types.ts
generated
vendored
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
import type { ComponentPublicInstance, ExtractPropTypes, PropType } from 'vue'
|
||||||
|
import { baseProps, makeBooleanProp, makeNumberProp, makeRequiredProp, makeStringProp } from '../common/props'
|
||||||
|
|
||||||
|
const now = new Date()
|
||||||
|
const defaultMinDate = new Date(now.getFullYear(), now.getMonth() - 6, now.getDate()).getTime()
|
||||||
|
const defaultMaxDate = new Date(now.getFullYear(), now.getMonth() + 6, now.getDate(), 23, 59, 59).getTime()
|
||||||
|
|
||||||
|
export type CalendarType = 'date' | 'dates' | 'datetime' | 'week' | 'month' | 'daterange' | 'datetimerange' | 'weekrange' | 'monthrange'
|
||||||
|
|
||||||
|
export const calendarViewProps = {
|
||||||
|
...baseProps,
|
||||||
|
/**
|
||||||
|
* 选中值,为 13 位时间戳或时间戳数组
|
||||||
|
*/
|
||||||
|
modelValue: makeRequiredProp([Number, Array, null] as PropType<number | number[] | null>),
|
||||||
|
/**
|
||||||
|
* 日期类型
|
||||||
|
*/
|
||||||
|
type: makeStringProp<CalendarType>('date'),
|
||||||
|
/**
|
||||||
|
* 最小日期,为 13 位时间戳
|
||||||
|
*/
|
||||||
|
minDate: makeNumberProp(defaultMinDate),
|
||||||
|
/**
|
||||||
|
* 最大日期,为 13 位时间戳
|
||||||
|
*/
|
||||||
|
maxDate: makeNumberProp(defaultMaxDate),
|
||||||
|
/**
|
||||||
|
* 周起始天
|
||||||
|
*/
|
||||||
|
firstDayOfWeek: makeNumberProp(0),
|
||||||
|
/**
|
||||||
|
* 日期格式化函数
|
||||||
|
*/
|
||||||
|
formatter: Function as PropType<CalendarFormatter>,
|
||||||
|
/**
|
||||||
|
* type 为范围选择时有效,最大日期范围
|
||||||
|
*/
|
||||||
|
maxRange: Number,
|
||||||
|
/**
|
||||||
|
* type 为范围选择时有效,选择超出最大日期范围时的错误提示文案
|
||||||
|
*/
|
||||||
|
rangePrompt: String,
|
||||||
|
/**
|
||||||
|
* type 为范围选择时有效,是否允许选择同一天
|
||||||
|
*/
|
||||||
|
allowSameDay: makeBooleanProp(false),
|
||||||
|
// 是否展示面板标题,自动计算当前滚动的日期月份
|
||||||
|
showPanelTitle: makeBooleanProp(true),
|
||||||
|
/**
|
||||||
|
* 选中日期所使用的当日内具体时刻
|
||||||
|
*/
|
||||||
|
defaultTime: {
|
||||||
|
type: [String, Array] as PropType<string | string[]>,
|
||||||
|
default: '00:00:00'
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 可滚动面板的高度
|
||||||
|
*/
|
||||||
|
panelHeight: makeNumberProp(378),
|
||||||
|
/**
|
||||||
|
* type 为 'datetime' 或 'datetimerange' 时有效,用于过滤时间选择器的数据
|
||||||
|
*/
|
||||||
|
timeFilter: Function as PropType<CalendarTimeFilter>,
|
||||||
|
/**
|
||||||
|
* type 为 'datetime' 或 'datetimerange' 时有效,是否不展示秒修改
|
||||||
|
*/
|
||||||
|
hideSecond: makeBooleanProp(false),
|
||||||
|
/**
|
||||||
|
* 是否在手指松开时立即触发picker-view的 change 事件。若不开启则会在滚动动画结束后触发 change 事件,1.2.25版本起提供,仅微信小程序和支付宝小程序支持。
|
||||||
|
*/
|
||||||
|
immediateChange: makeBooleanProp(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CalendarViewProps = ExtractPropTypes<typeof calendarViewProps>
|
||||||
|
|
||||||
|
export type CalendarDayType = '' | 'start' | 'middle' | 'end' | 'selected' | 'same' | 'current' | 'multiple-middle' | 'multiple-selected'
|
||||||
|
|
||||||
|
export type CalendarDayItem = {
|
||||||
|
date: number
|
||||||
|
text?: number | string
|
||||||
|
topInfo?: string
|
||||||
|
bottomInfo?: string
|
||||||
|
type?: CalendarDayType
|
||||||
|
disabled?: boolean
|
||||||
|
isLastRow?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CalendarFormatter = (day: CalendarDayItem) => CalendarDayItem
|
||||||
|
|
||||||
|
export type CalendarTimeFilterOptionType = 'hour' | 'minute' | 'second'
|
||||||
|
|
||||||
|
export type CalendarTimeFilterOption = {
|
||||||
|
type: CalendarTimeFilterOptionType
|
||||||
|
values: CalendarItem[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CalendarTimeFilter = (option: CalendarTimeFilterOption) => CalendarItem[]
|
||||||
|
|
||||||
|
export type CalendarItem = {
|
||||||
|
label: string
|
||||||
|
value: number
|
||||||
|
disabled: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CalendarViewExpose = {
|
||||||
|
/**
|
||||||
|
* 使当前日期或者选中日期滚动到可视区域
|
||||||
|
*/
|
||||||
|
scrollIntoView: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CalendarViewInstance = ComponentPublicInstance<CalendarViewExpose, CalendarViewProps>
|
||||||
429
node_modules/wot-design-uni/components/wd-calendar-view/utils.ts
generated
vendored
Normal file
429
node_modules/wot-design-uni/components/wd-calendar-view/utils.ts
generated
vendored
Normal file
@@ -0,0 +1,429 @@
|
|||||||
|
import { computed } from 'vue'
|
||||||
|
import dayjs from '../../dayjs'
|
||||||
|
import { isArray, isFunction, padZero } from '../common/util'
|
||||||
|
import { useTranslate } from '../composables/useTranslate'
|
||||||
|
import type { CalendarDayType, CalendarItem, CalendarTimeFilter, CalendarType } from './types'
|
||||||
|
const { translate } = useTranslate('calendar-view')
|
||||||
|
|
||||||
|
const weeks = computed(() => {
|
||||||
|
return [
|
||||||
|
translate('weeks.sun'),
|
||||||
|
translate('weeks.mon'),
|
||||||
|
translate('weeks.tue'),
|
||||||
|
translate('weeks.wed'),
|
||||||
|
translate('weeks.thu'),
|
||||||
|
translate('weeks.fri'),
|
||||||
|
translate('weeks.sat')
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 比较两个时间的日期是否相等
|
||||||
|
* @param {timestamp} date1
|
||||||
|
* @param {timestamp} date2
|
||||||
|
*/
|
||||||
|
export function compareDate(date1: number, date2: number | null) {
|
||||||
|
const dateValue1 = new Date(date1)
|
||||||
|
const dateValue2 = new Date(date2 || '')
|
||||||
|
|
||||||
|
const year1 = dateValue1.getFullYear()
|
||||||
|
const year2 = dateValue2.getFullYear()
|
||||||
|
const month1 = dateValue1.getMonth()
|
||||||
|
const month2 = dateValue2.getMonth()
|
||||||
|
const day1 = dateValue1.getDate()
|
||||||
|
const day2 = dateValue2.getDate()
|
||||||
|
|
||||||
|
if (year1 === year2) {
|
||||||
|
if (month1 === month2) {
|
||||||
|
return day1 === day2 ? 0 : day1 > day2 ? 1 : -1
|
||||||
|
}
|
||||||
|
return month1 === month2 ? 0 : month1 > month2 ? 1 : -1
|
||||||
|
}
|
||||||
|
|
||||||
|
return year1 > year2 ? 1 : -1
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否是范围选择
|
||||||
|
* @param {string} type
|
||||||
|
*/
|
||||||
|
export function isRange(type: CalendarType) {
|
||||||
|
return type.indexOf('range') > -1
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 比较两个日期的月份是否相等
|
||||||
|
* @param {timestamp} date1
|
||||||
|
* @param {timestamp} date2
|
||||||
|
*/
|
||||||
|
export function compareMonth(date1: number, date2: number) {
|
||||||
|
const dateValue1 = new Date(date1)
|
||||||
|
const dateValue2 = new Date(date2)
|
||||||
|
|
||||||
|
const year1 = dateValue1.getFullYear()
|
||||||
|
const year2 = dateValue2.getFullYear()
|
||||||
|
const month1 = dateValue1.getMonth()
|
||||||
|
const month2 = dateValue2.getMonth()
|
||||||
|
|
||||||
|
if (year1 === year2) {
|
||||||
|
return month1 === month2 ? 0 : month1 > month2 ? 1 : -1
|
||||||
|
}
|
||||||
|
|
||||||
|
return year1 > year2 ? 1 : -1
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 比较两个日期的年份是否一致
|
||||||
|
* @param {timestamp} date1
|
||||||
|
* @param {timestamp} date2
|
||||||
|
*/
|
||||||
|
export function compareYear(date1: number, date2: number) {
|
||||||
|
const dateValue1 = new Date(date1)
|
||||||
|
const dateValue2 = new Date(date2)
|
||||||
|
|
||||||
|
const year1 = dateValue1.getFullYear()
|
||||||
|
const year2 = dateValue2.getFullYear()
|
||||||
|
|
||||||
|
return year1 === year2 ? 0 : year1 > year2 ? 1 : -1
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取一个月的最后一天
|
||||||
|
* @param {number} year
|
||||||
|
* @param {number} month
|
||||||
|
*/
|
||||||
|
export function getMonthEndDay(year: number, month: number) {
|
||||||
|
return 32 - new Date(year, month - 1, 32).getDate()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化年月
|
||||||
|
* @param {timestamp} date
|
||||||
|
*/
|
||||||
|
export function formatMonthTitle(date: number) {
|
||||||
|
return dayjs(date).format(translate('monthTitle'))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据下标获取星期
|
||||||
|
* @param {number} index
|
||||||
|
*/
|
||||||
|
export function getWeekLabel(index: number) {
|
||||||
|
if (index >= 7) {
|
||||||
|
index = index % 7
|
||||||
|
}
|
||||||
|
|
||||||
|
return weeks.value[index]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化年份
|
||||||
|
* @param {timestamp} date
|
||||||
|
*/
|
||||||
|
export function formatYearTitle(date: number) {
|
||||||
|
return dayjs(date).format(translate('yearTitle'))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据最小日期和最大日期获取这之间总共有几个月份
|
||||||
|
* @param {timestamp} minDate
|
||||||
|
* @param {timestamp} maxDate
|
||||||
|
*/
|
||||||
|
export function getMonths(minDate: number, maxDate: number) {
|
||||||
|
const months: number[] = []
|
||||||
|
const month = new Date(minDate)
|
||||||
|
month.setDate(1)
|
||||||
|
|
||||||
|
while (compareMonth(month.getTime(), maxDate) < 1) {
|
||||||
|
months.push(month.getTime())
|
||||||
|
month.setMonth(month.getMonth() + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return months
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据最小日期和最大日期获取这之间总共有几年
|
||||||
|
* @param {timestamp} minDate
|
||||||
|
* @param {timestamp} maxDate
|
||||||
|
*/
|
||||||
|
export function getYears(minDate: number, maxDate: number) {
|
||||||
|
const years: number[] = []
|
||||||
|
const year = new Date(minDate)
|
||||||
|
year.setMonth(0)
|
||||||
|
year.setDate(1)
|
||||||
|
|
||||||
|
while (compareYear(year.getTime(), maxDate) < 1) {
|
||||||
|
years.push(year.getTime())
|
||||||
|
year.setFullYear(year.getFullYear() + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return years
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取一个日期所在周的第一天和最后一天
|
||||||
|
* @param {timestamp} date
|
||||||
|
*/
|
||||||
|
export function getWeekRange(date: number, firstDayOfWeek: number) {
|
||||||
|
if (firstDayOfWeek >= 7) {
|
||||||
|
firstDayOfWeek = firstDayOfWeek % 7
|
||||||
|
}
|
||||||
|
|
||||||
|
const dateValue = new Date(date)
|
||||||
|
dateValue.setHours(0, 0, 0, 0)
|
||||||
|
const year = dateValue.getFullYear()
|
||||||
|
const month = dateValue.getMonth()
|
||||||
|
const day = dateValue.getDate()
|
||||||
|
const week = dateValue.getDay()
|
||||||
|
|
||||||
|
const weekStart = new Date(year, month, day - ((7 + week - firstDayOfWeek) % 7))
|
||||||
|
const weekEnd = new Date(year, month, day + 6 - ((7 + week - firstDayOfWeek) % 7))
|
||||||
|
|
||||||
|
return [weekStart.getTime(), weekEnd.getTime()]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取日期偏移量
|
||||||
|
* @param {timestamp} date1
|
||||||
|
* @param {timestamp} date2
|
||||||
|
*/
|
||||||
|
export function getDayOffset(date1: number, date2: number) {
|
||||||
|
return (date1 - date2) / (24 * 60 * 60 * 1000) + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取偏移日期
|
||||||
|
* @param {timestamp} date
|
||||||
|
* @param {number} offset
|
||||||
|
*/
|
||||||
|
export function getDayByOffset(date: number, offset: number) {
|
||||||
|
const dateValue = new Date(date)
|
||||||
|
dateValue.setDate(dateValue.getDate() + offset)
|
||||||
|
|
||||||
|
return dateValue.getTime()
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getPrevDay = (date: number) => getDayByOffset(date, -1)
|
||||||
|
export const getNextDay = (date: number) => getDayByOffset(date, 1)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取月份偏移量
|
||||||
|
* @param {timestamp} date1
|
||||||
|
* @param {timestamp} date2
|
||||||
|
*/
|
||||||
|
export function getMonthOffset(date1: number, date2: number) {
|
||||||
|
const dateValue1 = new Date(date1)
|
||||||
|
const dateValue2 = new Date(date2)
|
||||||
|
|
||||||
|
const year1 = dateValue1.getFullYear()
|
||||||
|
const year2 = dateValue2.getFullYear()
|
||||||
|
let month1 = dateValue1.getMonth()
|
||||||
|
const month2 = dateValue2.getMonth()
|
||||||
|
|
||||||
|
month1 = (year1 - year2) * 12 + month1
|
||||||
|
|
||||||
|
return month1 - month2 + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取偏移月份
|
||||||
|
* @param {timestamp} date
|
||||||
|
* @param {number} offset
|
||||||
|
*/
|
||||||
|
export function getMonthByOffset(date: number, offset: number) {
|
||||||
|
const dateValue = new Date(date)
|
||||||
|
dateValue.setMonth(dateValue.getMonth() + offset)
|
||||||
|
|
||||||
|
return dateValue.getTime()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取默认时间,格式化为数组
|
||||||
|
* @param {array|string|null} defaultTime
|
||||||
|
*/
|
||||||
|
export function getDefaultTime(defaultTime: string[] | string | null) {
|
||||||
|
if (isArray(defaultTime)) {
|
||||||
|
const startTime = (defaultTime[0] || '00:00:00').split(':').map((item: string) => {
|
||||||
|
return parseInt(item)
|
||||||
|
})
|
||||||
|
const endTime = (defaultTime[1] || '00:00:00').split(':').map((item) => {
|
||||||
|
return parseInt(item)
|
||||||
|
})
|
||||||
|
return [startTime, endTime]
|
||||||
|
} else {
|
||||||
|
const time = (defaultTime || '00:00:00').split(':').map((item) => {
|
||||||
|
return parseInt(item)
|
||||||
|
})
|
||||||
|
|
||||||
|
return [time, time]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据默认时间获取日期
|
||||||
|
* @param {timestamp} date
|
||||||
|
* @param {array} defaultTime
|
||||||
|
*/
|
||||||
|
export function getDateByDefaultTime(date: number, defaultTime: number[]) {
|
||||||
|
const dateValue = new Date(date)
|
||||||
|
dateValue.setHours(defaultTime[0])
|
||||||
|
dateValue.setMinutes(defaultTime[1])
|
||||||
|
dateValue.setSeconds(defaultTime[2])
|
||||||
|
|
||||||
|
return dateValue.getTime()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取经过 iteratee 格式化后的长度为 n 的数组
|
||||||
|
* @param {number} n
|
||||||
|
* @param {function} iteratee
|
||||||
|
*/
|
||||||
|
const times = (n: number, iteratee: (index: number) => CalendarItem) => {
|
||||||
|
let index: number = -1
|
||||||
|
const result: CalendarItem[] = Array(n < 0 ? 0 : n)
|
||||||
|
while (++index < n) {
|
||||||
|
result[index] = iteratee(index)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取时分秒
|
||||||
|
* @param {timestamp}} date
|
||||||
|
*/
|
||||||
|
const getTime = (date: number) => {
|
||||||
|
const dateValue = new Date(date)
|
||||||
|
return [dateValue.getHours(), dateValue.getMinutes(), dateValue.getSeconds()]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据最小最大日期获取时间数据,用于填入picker
|
||||||
|
* @param {*} param0
|
||||||
|
*/
|
||||||
|
export function getTimeData({
|
||||||
|
date,
|
||||||
|
minDate,
|
||||||
|
maxDate,
|
||||||
|
isHideSecond,
|
||||||
|
filter
|
||||||
|
}: {
|
||||||
|
date: number
|
||||||
|
minDate: number
|
||||||
|
maxDate: number
|
||||||
|
isHideSecond: boolean
|
||||||
|
filter?: CalendarTimeFilter
|
||||||
|
}) {
|
||||||
|
const compareMin = compareDate(date, minDate)
|
||||||
|
const compareMax = compareDate(date, maxDate)
|
||||||
|
|
||||||
|
let minHour = 0
|
||||||
|
let maxHour = 23
|
||||||
|
let minMinute = 0
|
||||||
|
let maxMinute = 59
|
||||||
|
let minSecond = 0
|
||||||
|
let maxSecond = 59
|
||||||
|
|
||||||
|
if (compareMin === 0) {
|
||||||
|
const minTime = getTime(minDate)
|
||||||
|
const currentTime = getTime(date)
|
||||||
|
|
||||||
|
minHour = minTime[0]
|
||||||
|
if (minTime[0] === currentTime[0]) {
|
||||||
|
minMinute = minTime[1]
|
||||||
|
|
||||||
|
if (minTime[1] === currentTime[1]) {
|
||||||
|
minSecond = minTime[2]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (compareMax === 0) {
|
||||||
|
const maxTime = getTime(maxDate)
|
||||||
|
const currentTime = getTime(date)
|
||||||
|
|
||||||
|
maxHour = maxTime[0]
|
||||||
|
if (maxTime[0] === currentTime[0]) {
|
||||||
|
maxMinute = maxTime[1]
|
||||||
|
|
||||||
|
if (maxTime[1] === currentTime[1]) {
|
||||||
|
maxSecond = maxTime[2]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let columns: CalendarItem[][] = []
|
||||||
|
let hours = times(24, (index) => {
|
||||||
|
return {
|
||||||
|
label: translate('hour', padZero(index)),
|
||||||
|
value: index,
|
||||||
|
disabled: index < minHour || index > maxHour
|
||||||
|
}
|
||||||
|
})
|
||||||
|
let minutes = times(60, (index) => {
|
||||||
|
return {
|
||||||
|
label: translate('minute', padZero(index)),
|
||||||
|
value: index,
|
||||||
|
disabled: index < minMinute || index > maxMinute
|
||||||
|
}
|
||||||
|
})
|
||||||
|
let seconds: CalendarItem[] = []
|
||||||
|
if (filter && isFunction(filter)) {
|
||||||
|
hours = filter({
|
||||||
|
type: 'hour',
|
||||||
|
values: hours
|
||||||
|
})
|
||||||
|
minutes = filter({
|
||||||
|
type: 'minute',
|
||||||
|
values: minutes
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isHideSecond) {
|
||||||
|
seconds = times(60, (index) => {
|
||||||
|
return {
|
||||||
|
label: translate('second', padZero(index)),
|
||||||
|
value: index,
|
||||||
|
disabled: index < minSecond || index > maxSecond
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (filter && isFunction(filter)) {
|
||||||
|
seconds = filter({
|
||||||
|
type: 'second',
|
||||||
|
values: seconds
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
columns = isHideSecond ? [hours, minutes] : [hours, minutes, seconds]
|
||||||
|
|
||||||
|
return columns
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前是第几周
|
||||||
|
* @param {timestamp} date
|
||||||
|
*/
|
||||||
|
export function getWeekNumber(date: number | Date) {
|
||||||
|
date = new Date(date)
|
||||||
|
date.setHours(0, 0, 0, 0)
|
||||||
|
// Thursday in current week decides the year.
|
||||||
|
date.setDate(date.getDate() + 3 - ((date.getDay() + 6) % 7))
|
||||||
|
// January 4 is always in week 1.
|
||||||
|
const week = new Date(date.getFullYear(), 0, 4)
|
||||||
|
// Adjust to Thursday in week 1 and count number of weeks from date to week 1.
|
||||||
|
// Rounding should be fine for Daylight Saving Time. Its shift should never be more than 12 hours.
|
||||||
|
return 1 + Math.round(((date.getTime() - week.getTime()) / 86400000 - 3 + ((week.getDay() + 6) % 7)) / 7)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getItemClass(monthType: CalendarDayType, value: number | null | (number | null)[], type: CalendarType) {
|
||||||
|
const classList = ['is-' + monthType]
|
||||||
|
|
||||||
|
if (type.indexOf('range') > -1 && isArray(value)) {
|
||||||
|
if (!value || !value[1]) {
|
||||||
|
classList.push('is-without-end')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return classList.join(' ')
|
||||||
|
}
|
||||||
111
node_modules/wot-design-uni/components/wd-calendar-view/wd-calendar-view.vue
generated
vendored
Normal file
111
node_modules/wot-design-uni/components/wd-calendar-view/wd-calendar-view.vue
generated
vendored
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
<template>
|
||||||
|
<view :class="`wd-calendar-view ${customClass}`">
|
||||||
|
<year-panel
|
||||||
|
v-if="type === 'month' || type === 'monthrange'"
|
||||||
|
ref="yearPanelRef"
|
||||||
|
:type="type"
|
||||||
|
:value="modelValue"
|
||||||
|
:min-date="minDate"
|
||||||
|
:max-date="maxDate"
|
||||||
|
:formatter="formatter"
|
||||||
|
:max-range="maxRange"
|
||||||
|
:range-prompt="rangePrompt"
|
||||||
|
:allow-same-day="allowSameDay"
|
||||||
|
:show-panel-title="showPanelTitle"
|
||||||
|
:default-time="formatDefauleTime"
|
||||||
|
:panel-height="panelHeight"
|
||||||
|
@change="handleChange"
|
||||||
|
/>
|
||||||
|
<month-panel
|
||||||
|
v-else
|
||||||
|
ref="monthPanelRef"
|
||||||
|
:type="type"
|
||||||
|
:value="modelValue"
|
||||||
|
:min-date="minDate"
|
||||||
|
:max-date="maxDate"
|
||||||
|
:first-day-of-week="firstDayOfWeek"
|
||||||
|
:formatter="formatter"
|
||||||
|
:max-range="maxRange"
|
||||||
|
:range-prompt="rangePrompt"
|
||||||
|
:allow-same-day="allowSameDay"
|
||||||
|
:show-panel-title="showPanelTitle"
|
||||||
|
:default-time="formatDefauleTime"
|
||||||
|
:panel-height="panelHeight"
|
||||||
|
:immediate-change="immediateChange"
|
||||||
|
:time-filter="timeFilter"
|
||||||
|
:hide-second="hideSecond"
|
||||||
|
@change="handleChange"
|
||||||
|
@pickstart="handlePickStart"
|
||||||
|
@pickend="handlePickEnd"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
name: 'wd-calendar-view',
|
||||||
|
options: {
|
||||||
|
addGlobalClass: true,
|
||||||
|
virtualHost: true,
|
||||||
|
styleIsolation: 'shared'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, watch } from 'vue'
|
||||||
|
import { getDefaultTime } from './utils'
|
||||||
|
import yearPanel from './yearPanel/year-panel.vue'
|
||||||
|
import MonthPanel from './monthPanel/month-panel.vue'
|
||||||
|
import { calendarViewProps, type CalendarViewExpose } from './types'
|
||||||
|
|
||||||
|
const props = defineProps(calendarViewProps)
|
||||||
|
const emit = defineEmits(['change', 'update:modelValue', 'pickstart', 'pickend'])
|
||||||
|
const formatDefauleTime = ref<number[][]>([])
|
||||||
|
|
||||||
|
const yearPanelRef = ref()
|
||||||
|
const monthPanelRef = ref()
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.defaultTime,
|
||||||
|
(newValue) => {
|
||||||
|
formatDefauleTime.value = getDefaultTime(newValue)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deep: true,
|
||||||
|
immediate: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使当前日期或者选中日期滚动到可视区域
|
||||||
|
*/
|
||||||
|
function scrollIntoView() {
|
||||||
|
const panel = getPanel()
|
||||||
|
panel.scrollIntoView && panel.scrollIntoView()
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPanel() {
|
||||||
|
return props.type.indexOf('month') > -1 ? yearPanelRef.value : monthPanelRef.value
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleChange({ value }: { value: number | number[] | null }) {
|
||||||
|
emit('update:modelValue', value)
|
||||||
|
emit('change', {
|
||||||
|
value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
function handlePickStart() {
|
||||||
|
emit('pickstart')
|
||||||
|
}
|
||||||
|
function handlePickEnd() {
|
||||||
|
emit('pickend')
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose<CalendarViewExpose>({
|
||||||
|
scrollIntoView
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import './index.scss';
|
||||||
|
</style>
|
||||||
153
node_modules/wot-design-uni/components/wd-calendar-view/year/index.scss
generated
vendored
Normal file
153
node_modules/wot-design-uni/components/wd-calendar-view/year/index.scss
generated
vendored
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
@import '../../common/abstracts/variable';
|
||||||
|
@import '../../common/abstracts/mixin';
|
||||||
|
|
||||||
|
.wot-theme-dark {
|
||||||
|
@include b(year) {
|
||||||
|
@include e(title) {
|
||||||
|
color: $-dark-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(months) {
|
||||||
|
color: $-dark-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(month) {
|
||||||
|
|
||||||
|
@include when(disabled) {
|
||||||
|
.wd-year__month-text {
|
||||||
|
color: $-dark-color-gray;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include b(year) {
|
||||||
|
@include e(title) {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 45px;
|
||||||
|
font-size: $-calendar-panel-title-fs;
|
||||||
|
color: $-calendar-panel-title-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(months) {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
font-size: $-calendar-day-fs;
|
||||||
|
color: $-calendar-day-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(month) {
|
||||||
|
position: relative;
|
||||||
|
width: 25%;
|
||||||
|
height: $-calendar-day-height;
|
||||||
|
line-height: $-calendar-day-height;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: $-calendar-item-margin-bottom;
|
||||||
|
|
||||||
|
@include when(disabled) {
|
||||||
|
.wd-year__month-text {
|
||||||
|
color: $-calendar-disabled-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(current) {
|
||||||
|
color: $-calendar-active-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(selected) {
|
||||||
|
color: #fff;
|
||||||
|
|
||||||
|
.wd-year__month-text {
|
||||||
|
border-radius: $-calendar-active-border;
|
||||||
|
background: $-calendar-active-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(middle) {
|
||||||
|
background: $-calendar-range-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(start) {
|
||||||
|
color: $-calendar-selected-color;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
left: 50%;
|
||||||
|
bottom: 0;
|
||||||
|
content: '';
|
||||||
|
background: $-calendar-range-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wd-year__month-text {
|
||||||
|
background: $-calendar-active-color;
|
||||||
|
border-radius: $-calendar-active-border 0 0 $-calendar-active-border;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-without-end::after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(end) {
|
||||||
|
color: $-calendar-selected-color;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 50%;
|
||||||
|
bottom: 0;
|
||||||
|
content: '';
|
||||||
|
background: $-calendar-range-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wd-year__month-text {
|
||||||
|
background: $-calendar-active-color;
|
||||||
|
border-radius: 0 $-calendar-active-border $-calendar-active-border 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(same) {
|
||||||
|
color: $-calendar-selected-color;
|
||||||
|
|
||||||
|
.wd-year__month-text {
|
||||||
|
background: $-calendar-active-color;
|
||||||
|
border-radius: $-calendar-active-border;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@include when(last-row){
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(month-text) {
|
||||||
|
width: $-calendar-month-width;
|
||||||
|
margin: 0 auto;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(month-top) {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
line-height: 1.1;
|
||||||
|
font-size: $-calendar-info-fs;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(month-bottom) {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 10px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
line-height: 1.1;
|
||||||
|
font-size: $-calendar-info-fs;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
20
node_modules/wot-design-uni/components/wd-calendar-view/year/types.ts
generated
vendored
Normal file
20
node_modules/wot-design-uni/components/wd-calendar-view/year/types.ts
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import type { PropType } from 'vue'
|
||||||
|
import { makeBooleanProp, makeRequiredProp } from '../../common/props'
|
||||||
|
import type { CalendarFormatter, CalendarType } from '../types'
|
||||||
|
|
||||||
|
export const yearProps = {
|
||||||
|
type: makeRequiredProp(String as PropType<CalendarType>),
|
||||||
|
date: makeRequiredProp(Number),
|
||||||
|
value: makeRequiredProp([Number, Array] as PropType<number | (number | null)[] | null>),
|
||||||
|
minDate: makeRequiredProp(Number),
|
||||||
|
maxDate: makeRequiredProp(Number),
|
||||||
|
// 日期格式化函数
|
||||||
|
formatter: Function as PropType<CalendarFormatter>,
|
||||||
|
maxRange: Number,
|
||||||
|
rangePrompt: String,
|
||||||
|
allowSameDay: makeBooleanProp(false),
|
||||||
|
defaultTime: {
|
||||||
|
type: [Array] as PropType<Array<number[]>>
|
||||||
|
},
|
||||||
|
showTitle: makeBooleanProp(true)
|
||||||
|
}
|
||||||
202
node_modules/wot-design-uni/components/wd-calendar-view/year/year.vue
generated
vendored
Normal file
202
node_modules/wot-design-uni/components/wd-calendar-view/year/year.vue
generated
vendored
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
<template>
|
||||||
|
<wd-toast selector="wd-year" />
|
||||||
|
|
||||||
|
<view class="wd-year year">
|
||||||
|
<view class="wd-year__title" v-if="showTitle">{{ yearTitle(date) }}</view>
|
||||||
|
<view class="wd-year__months">
|
||||||
|
<view
|
||||||
|
v-for="(item, index) in months"
|
||||||
|
:key="index"
|
||||||
|
:class="`wd-year__month ${item.disabled ? 'is-disabled' : ''} ${item.isLastRow ? 'is-last-row' : ''} ${
|
||||||
|
item.type ? monthTypeClass(item.type) : ''
|
||||||
|
}`"
|
||||||
|
@click="handleDateClick(index)"
|
||||||
|
>
|
||||||
|
<view class="wd-year__month-top">{{ item.topInfo }}</view>
|
||||||
|
<view class="wd-year__month-text">{{ getMonthLabel(item.date) }}</view>
|
||||||
|
<view class="wd-year__month-bottom">{{ item.bottomInfo }}</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
options: {
|
||||||
|
addGlobalClass: true,
|
||||||
|
virtualHost: true,
|
||||||
|
styleIsolation: 'shared'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import wdToast from '../../wd-toast/wd-toast.vue'
|
||||||
|
import { computed, ref, watch } from 'vue'
|
||||||
|
import { deepClone, isArray, isFunction } from '../../common/util'
|
||||||
|
import { compareMonth, formatYearTitle, getDateByDefaultTime, getItemClass, getMonthByOffset, getMonthOffset } from '../utils'
|
||||||
|
import { useToast } from '../../wd-toast'
|
||||||
|
import { useTranslate } from '../../composables/useTranslate'
|
||||||
|
import dayjs from '../../../dayjs'
|
||||||
|
import { yearProps } from './types'
|
||||||
|
import type { CalendarDayItem, CalendarDayType } from '../types'
|
||||||
|
|
||||||
|
const props = defineProps(yearProps)
|
||||||
|
const emit = defineEmits(['change'])
|
||||||
|
|
||||||
|
const toast = useToast('wd-year')
|
||||||
|
const { translate } = useTranslate('calendar-view')
|
||||||
|
|
||||||
|
const months = ref<CalendarDayItem[]>([])
|
||||||
|
|
||||||
|
const monthTypeClass = computed(() => {
|
||||||
|
return (monthType: CalendarDayType) => {
|
||||||
|
return getItemClass(monthType, props.value, props.type)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const yearTitle = computed(() => {
|
||||||
|
return (date: number) => {
|
||||||
|
return formatYearTitle(date)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(
|
||||||
|
[() => props.type, () => props.date, () => props.value, () => props.minDate, () => props.maxDate, () => props.formatter],
|
||||||
|
() => {
|
||||||
|
setMonths()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deep: true,
|
||||||
|
immediate: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
function getMonthLabel(date: number) {
|
||||||
|
return dayjs(date).format(translate('month', date))
|
||||||
|
}
|
||||||
|
|
||||||
|
function setMonths() {
|
||||||
|
const monthList: CalendarDayItem[] = []
|
||||||
|
const date = new Date(props.date)
|
||||||
|
const year = date.getFullYear()
|
||||||
|
const value = props.value
|
||||||
|
|
||||||
|
if (props.type.indexOf('range') > -1 && value && !isArray(value)) {
|
||||||
|
console.error('[wot-design] value should be array when type is range')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let month = 0; month < 12; month++) {
|
||||||
|
const date = new Date(year, month, 1).getTime()
|
||||||
|
let type: CalendarDayType = getMonthType(date)
|
||||||
|
if (!type && compareMonth(date, Date.now()) === 0) {
|
||||||
|
type = 'current'
|
||||||
|
}
|
||||||
|
const monthObj = getFormatterDate(date, month, type)
|
||||||
|
monthList.push(monthObj)
|
||||||
|
}
|
||||||
|
|
||||||
|
months.value = deepClone(monthList)
|
||||||
|
}
|
||||||
|
function getMonthType(date: number) {
|
||||||
|
if (props.type === 'monthrange' && isArray(props.value)) {
|
||||||
|
const [startDate, endDate] = props.value || []
|
||||||
|
|
||||||
|
if (startDate && compareMonth(date, startDate) === 0) {
|
||||||
|
if (endDate && compareMonth(startDate, endDate) === 0) {
|
||||||
|
return 'same'
|
||||||
|
}
|
||||||
|
return 'start'
|
||||||
|
} else if (endDate && compareMonth(date, endDate) === 0) {
|
||||||
|
return 'end'
|
||||||
|
} else if (startDate && endDate && compareMonth(date, startDate) === 1 && compareMonth(date, endDate) === -1) {
|
||||||
|
return 'middle'
|
||||||
|
} else {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (props.value && compareMonth(date, props.value as number) === 0) {
|
||||||
|
return 'selected'
|
||||||
|
} else {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function handleDateClick(index: number) {
|
||||||
|
const date = months.value[index]
|
||||||
|
|
||||||
|
if (date.disabled) return
|
||||||
|
|
||||||
|
switch (props.type) {
|
||||||
|
case 'month':
|
||||||
|
handleMonthChange(date)
|
||||||
|
break
|
||||||
|
case 'monthrange':
|
||||||
|
handleMonthRangeChange(date)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
handleMonthChange(date)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function getDate(date: number) {
|
||||||
|
return props.defaultTime && props.defaultTime.length > 0 ? getDateByDefaultTime(date, props.defaultTime[0]) : date
|
||||||
|
}
|
||||||
|
function handleMonthChange(date: CalendarDayItem) {
|
||||||
|
if (date.type !== 'selected') {
|
||||||
|
emit('change', {
|
||||||
|
value: getDate(date.date)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function handleMonthRangeChange(date: CalendarDayItem) {
|
||||||
|
let value: (number | null)[] = []
|
||||||
|
const [startDate, endDate] = isArray(props.value) ? props.value || [] : []
|
||||||
|
const compare = compareMonth(date.date, startDate!)
|
||||||
|
|
||||||
|
// 禁止选择同个日期
|
||||||
|
if (!props.allowSameDay && !endDate && compare === 0) return
|
||||||
|
|
||||||
|
if (startDate && !endDate && compare > -1) {
|
||||||
|
if (props.maxRange && getMonthOffset(date.date, startDate) > props.maxRange) {
|
||||||
|
const maxEndDate = getMonthByOffset(startDate, props.maxRange - 1)
|
||||||
|
value = [startDate, getDate(maxEndDate)]
|
||||||
|
toast.show({
|
||||||
|
msg: props.rangePrompt || translate('rangePromptMonth', props.maxRange)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
value = [startDate, getDate(date.date)]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
value = [getDate(date.date), null]
|
||||||
|
}
|
||||||
|
emit('change', {
|
||||||
|
value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFormatterDate(date: number, month: number, type?: CalendarDayType) {
|
||||||
|
let monthObj: CalendarDayItem = {
|
||||||
|
date: date,
|
||||||
|
text: month + 1,
|
||||||
|
topInfo: '',
|
||||||
|
bottomInfo: '',
|
||||||
|
type,
|
||||||
|
disabled: compareMonth(date, props.minDate) === -1 || compareMonth(date, props.maxDate) === 1,
|
||||||
|
isLastRow: month >= 8
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.formatter) {
|
||||||
|
if (isFunction(props.formatter)) {
|
||||||
|
monthObj = props.formatter(monthObj)
|
||||||
|
} else {
|
||||||
|
console.error('[wot-design] error(wd-calendar-view): the formatter prop of wd-calendar-view should be a function')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return monthObj
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import './index.scss';
|
||||||
|
</style>
|
||||||
24
node_modules/wot-design-uni/components/wd-calendar-view/yearPanel/index.scss
generated
vendored
Normal file
24
node_modules/wot-design-uni/components/wd-calendar-view/yearPanel/index.scss
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
@import '../../common/abstracts/variable';
|
||||||
|
@import '../../common/abstracts/mixin';
|
||||||
|
|
||||||
|
.wot-theme-dark {
|
||||||
|
@include b(year-panel) {
|
||||||
|
@include e(title) {
|
||||||
|
color: $-dark-color;
|
||||||
|
box-shadow: 0px 4px 8px 0 rgba(255, 255,255, 0.02);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include b(year-panel) {
|
||||||
|
font-size: $-calendar-fs;
|
||||||
|
padding: $-calendar-panel-padding;
|
||||||
|
|
||||||
|
@include e(title) {
|
||||||
|
padding: 5px 0;
|
||||||
|
text-align: center;
|
||||||
|
font-size: $-calendar-panel-title-fs;
|
||||||
|
color: $-calendar-panel-title-color;
|
||||||
|
box-shadow: 0px 4px 8px 0 rgba(0, 0, 0, 0.02);
|
||||||
|
}
|
||||||
|
}
|
||||||
38
node_modules/wot-design-uni/components/wd-calendar-view/yearPanel/types.ts
generated
vendored
Normal file
38
node_modules/wot-design-uni/components/wd-calendar-view/yearPanel/types.ts
generated
vendored
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import type { ComponentPublicInstance, ExtractPropTypes, PropType } from 'vue'
|
||||||
|
import { makeBooleanProp, makeRequiredProp } from '../../common/props'
|
||||||
|
import type { CalendarFormatter, CalendarType } from '../types'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 月份信息
|
||||||
|
*/
|
||||||
|
export interface YearInfo {
|
||||||
|
date: number
|
||||||
|
height: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export const yearPanelProps = {
|
||||||
|
type: makeRequiredProp(String as PropType<CalendarType>),
|
||||||
|
value: makeRequiredProp([Number, Array] as PropType<number | (number | null)[] | null>),
|
||||||
|
minDate: makeRequiredProp(Number),
|
||||||
|
maxDate: makeRequiredProp(Number),
|
||||||
|
formatter: Function as PropType<CalendarFormatter>,
|
||||||
|
maxRange: Number,
|
||||||
|
rangePrompt: String,
|
||||||
|
allowSameDay: makeBooleanProp(false),
|
||||||
|
showPanelTitle: makeBooleanProp(false),
|
||||||
|
defaultTime: {
|
||||||
|
type: [Array] as PropType<Array<number[]>>
|
||||||
|
},
|
||||||
|
panelHeight: makeRequiredProp(Number)
|
||||||
|
}
|
||||||
|
|
||||||
|
export type YearPanelProps = ExtractPropTypes<typeof yearPanelProps>
|
||||||
|
|
||||||
|
export type YearPanelExpose = {
|
||||||
|
/**
|
||||||
|
* 使当前日期或者选中日期滚动到可视区域
|
||||||
|
*/
|
||||||
|
scrollIntoView: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export type YearPanelInstance = ComponentPublicInstance<YearPanelProps, YearPanelExpose>
|
||||||
135
node_modules/wot-design-uni/components/wd-calendar-view/yearPanel/year-panel.vue
generated
vendored
Normal file
135
node_modules/wot-design-uni/components/wd-calendar-view/yearPanel/year-panel.vue
generated
vendored
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
<template>
|
||||||
|
<view class="wd-year-panel">
|
||||||
|
<view v-if="showPanelTitle" class="wd-year-panel__title">{{ title }}</view>
|
||||||
|
<scroll-view class="wd-year-panel__container" :style="`height: ${scrollHeight}px`" scroll-y @scroll="yearScroll" :scroll-top="scrollTop">
|
||||||
|
<view v-for="(item, index) in years" :key="index" :id="`year${index}`">
|
||||||
|
<year
|
||||||
|
:type="type"
|
||||||
|
:date="item.date"
|
||||||
|
:value="value"
|
||||||
|
:min-date="minDate"
|
||||||
|
:max-date="maxDate"
|
||||||
|
:max-range="maxRange"
|
||||||
|
:formatter="formatter"
|
||||||
|
:range-prompt="rangePrompt"
|
||||||
|
:allow-same-day="allowSameDay"
|
||||||
|
:default-time="defaultTime"
|
||||||
|
:showTitle="index !== 0"
|
||||||
|
@change="handleDateChange"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
options: {
|
||||||
|
addGlobalClass: true,
|
||||||
|
virtualHost: true,
|
||||||
|
styleIsolation: 'shared'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed, ref, onMounted } from 'vue'
|
||||||
|
import { compareYear, formatYearTitle, getYears } from '../utils'
|
||||||
|
import { isArray, isNumber, pause } from '../../common/util'
|
||||||
|
import Year from '../year/year.vue'
|
||||||
|
import { yearPanelProps, type YearInfo, type YearPanelExpose } from './types'
|
||||||
|
|
||||||
|
const props = defineProps(yearPanelProps)
|
||||||
|
const emit = defineEmits(['change'])
|
||||||
|
|
||||||
|
const scrollTop = ref<number>(0) // 滚动位置
|
||||||
|
const scrollIndex = ref<number>(0) // 当前显示的年份索引
|
||||||
|
|
||||||
|
// 滚动区域的高度
|
||||||
|
const scrollHeight = computed(() => {
|
||||||
|
const scrollHeight: number = props.panelHeight + (props.showPanelTitle ? 26 : 16)
|
||||||
|
return scrollHeight
|
||||||
|
})
|
||||||
|
|
||||||
|
// 年份信息
|
||||||
|
const years = computed<YearInfo[]>(() => {
|
||||||
|
return getYears(props.minDate, props.maxDate).map((year, index) => {
|
||||||
|
return {
|
||||||
|
date: year,
|
||||||
|
height: index === 0 ? 200 : 245
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// 标题
|
||||||
|
const title = computed(() => {
|
||||||
|
return formatYearTitle(years.value[scrollIndex.value].date)
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
scrollIntoView()
|
||||||
|
})
|
||||||
|
|
||||||
|
async function scrollIntoView() {
|
||||||
|
await pause()
|
||||||
|
let activeDate: number | null = null
|
||||||
|
if (isArray(props.value)) {
|
||||||
|
activeDate = props.value![0]
|
||||||
|
} else if (isNumber(props.value)) {
|
||||||
|
activeDate = props.value
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!activeDate) {
|
||||||
|
activeDate = Date.now()
|
||||||
|
}
|
||||||
|
|
||||||
|
let top: number = 0
|
||||||
|
for (let index = 0; index < years.value.length; index++) {
|
||||||
|
if (compareYear(years.value[index].date, activeDate) === 0) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
top += years.value[index] ? Number(years.value[index].height) : 0
|
||||||
|
}
|
||||||
|
scrollTop.value = 0
|
||||||
|
if (top > 0) {
|
||||||
|
await pause()
|
||||||
|
scrollTop.value = top + 45
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const yearScroll = (event: { detail: { scrollTop: number } }) => {
|
||||||
|
if (years.value.length <= 1) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const scrollTop = Math.max(0, event.detail.scrollTop)
|
||||||
|
doSetSubtitle(scrollTop)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置小标题
|
||||||
|
* scrollTop 滚动条位置
|
||||||
|
*/
|
||||||
|
function doSetSubtitle(scrollTop: number) {
|
||||||
|
let height: number = 0 // 月份高度和
|
||||||
|
for (let index = 0; index < years.value.length; index++) {
|
||||||
|
height = height + years.value[index].height
|
||||||
|
if (scrollTop < height) {
|
||||||
|
scrollIndex.value = index
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDateChange({ value }: { value: number[] }) {
|
||||||
|
emit('change', {
|
||||||
|
value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose<YearPanelExpose>({
|
||||||
|
scrollIntoView
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import './index.scss';
|
||||||
|
</style>
|
||||||
164
node_modules/wot-design-uni/components/wd-calendar/index.scss
generated
vendored
Normal file
164
node_modules/wot-design-uni/components/wd-calendar/index.scss
generated
vendored
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
@import '../common/abstracts/variable';
|
||||||
|
@import '../common/abstracts/mixin';
|
||||||
|
|
||||||
|
.wot-theme-dark {
|
||||||
|
@include b(calendar) {
|
||||||
|
|
||||||
|
@include e(title) {
|
||||||
|
color: $-dark-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.wd-calendar__arrow),
|
||||||
|
:deep(.wd-calendar__close),
|
||||||
|
:deep(.wd-calendar__clear) {
|
||||||
|
color: $-dark-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(range-label-item) {
|
||||||
|
color: $-dark-color;
|
||||||
|
|
||||||
|
@include when(placeholder) {
|
||||||
|
color: $-dark-color-gray;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(range-sperator) {
|
||||||
|
color: $-dark-color-gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.wd-calendar__cell--placeholder) {
|
||||||
|
.wd-cell__value {
|
||||||
|
color: $-dark-color-gray;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include b(calendar) {
|
||||||
|
|
||||||
|
@include e(header) {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(title) {
|
||||||
|
color: $-action-sheet-color;
|
||||||
|
height: $-action-sheet-title-height;
|
||||||
|
line-height: $-action-sheet-title-height;
|
||||||
|
text-align: center;
|
||||||
|
font-size: $-action-sheet-title-fs;
|
||||||
|
font-weight: $-action-sheet-weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include edeep(close) {
|
||||||
|
position: absolute;
|
||||||
|
top: $-action-sheet-close-top;
|
||||||
|
right: $-action-sheet-close-right;
|
||||||
|
color: $-action-sheet-close-color;
|
||||||
|
font-size: $-action-sheet-close-fs;
|
||||||
|
transform: rotate(-45deg);
|
||||||
|
line-height: 1.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(tabs) {
|
||||||
|
width: 222px;
|
||||||
|
margin: 10px auto 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(shortcuts) {
|
||||||
|
padding: 20px 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include edeep(tag) {
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(view) {
|
||||||
|
@include when(show-confirm) {
|
||||||
|
height: 394px;
|
||||||
|
|
||||||
|
@include when(range) {
|
||||||
|
height: 384px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(range-label) {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 14px;
|
||||||
|
|
||||||
|
@include when(monthrange) {
|
||||||
|
padding-bottom: 10px;
|
||||||
|
box-shadow: 0px 4px 8px 0 rgba(0, 0, 0, 0.02);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(range-label-item) {
|
||||||
|
flex: 1;
|
||||||
|
color: rgba(0, 0, 0, 0.85);
|
||||||
|
|
||||||
|
@include when(placeholder) {
|
||||||
|
color: rgba(0, 0, 0, 0.25);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(range-sperator) {
|
||||||
|
margin: 0 24px;
|
||||||
|
color: rgba(0, 0, 0, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(confirm) {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 12px 25px 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(confirm-btn-wrapper) {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include edeep(cell) {
|
||||||
|
@include when(disabled) {
|
||||||
|
.wd-cell__value {
|
||||||
|
color: $-input-disabled-color;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@include when(error) {
|
||||||
|
.wd-cell__value {
|
||||||
|
color: $-input-error-color;
|
||||||
|
}
|
||||||
|
:deep(.wd-calendar__arrow) {
|
||||||
|
color: $-input-error-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@include when(large) {
|
||||||
|
.wd-calendar__arrow {
|
||||||
|
font-size: $-cell-icon-size-large;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include m(placeholder) {
|
||||||
|
.wd-cell__value {
|
||||||
|
color: $-input-placeholder-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include edeep(arrow) {
|
||||||
|
display: block;
|
||||||
|
font-size: $-cell-icon-size;
|
||||||
|
color: $-cell-arrow-color;
|
||||||
|
line-height: $-cell-line-height;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include edeep(clear) {
|
||||||
|
display: block;
|
||||||
|
font-size: $-cell-icon-size;
|
||||||
|
color: $-cell-clear-color;
|
||||||
|
line-height: $-cell-line-height;
|
||||||
|
}
|
||||||
|
}
|
||||||
230
node_modules/wot-design-uni/components/wd-calendar/types.ts
generated
vendored
Normal file
230
node_modules/wot-design-uni/components/wd-calendar/types.ts
generated
vendored
Normal file
@@ -0,0 +1,230 @@
|
|||||||
|
/*
|
||||||
|
* @Author: weisheng
|
||||||
|
* @Date: 2024-03-15 20:40:34
|
||||||
|
* @LastEditTime: 2025-07-11 16:00:26
|
||||||
|
* @LastEditors: weisheng
|
||||||
|
* @Description:
|
||||||
|
* @FilePath: /wot-design-uni/src/uni_modules/wot-design-uni/components/wd-calendar/types.ts
|
||||||
|
* 记得注释
|
||||||
|
*/
|
||||||
|
import type { ComponentPublicInstance, ExtractPropTypes, PropType } from 'vue'
|
||||||
|
import { baseProps, makeArrayProp, makeBooleanProp, makeNumberProp, makeRequiredProp, makeStringProp } from '../common/props'
|
||||||
|
import type { CalendarFormatter, CalendarTimeFilter, CalendarType } from '../wd-calendar-view/types'
|
||||||
|
import type { FormItemRule } from '../wd-form/types'
|
||||||
|
|
||||||
|
const now = new Date()
|
||||||
|
const defaultMinDate = new Date(now.getFullYear(), now.getMonth() - 6, now.getDate()).getTime()
|
||||||
|
const defaultMaxDate = new Date(now.getFullYear(), now.getMonth() + 6, now.getDate(), 23, 59, 59).getTime()
|
||||||
|
|
||||||
|
export const calendarProps = {
|
||||||
|
...baseProps,
|
||||||
|
/**
|
||||||
|
* 选中值,为 13 位时间戳或时间戳数组
|
||||||
|
*/
|
||||||
|
modelValue: makeRequiredProp([Number, Array, null] as PropType<number | number[] | null>),
|
||||||
|
/**
|
||||||
|
* 日期类型,可选值:date / dates / datetime / week / month / daterange / datetimerange / weekrange / monthrange
|
||||||
|
*/
|
||||||
|
type: makeStringProp<CalendarType>('date'),
|
||||||
|
/**
|
||||||
|
* 最小日期,为 13 位时间戳
|
||||||
|
*/
|
||||||
|
minDate: makeNumberProp(defaultMinDate),
|
||||||
|
/**
|
||||||
|
* 最大日期,为 13 位时间戳
|
||||||
|
*/
|
||||||
|
maxDate: makeNumberProp(defaultMaxDate),
|
||||||
|
/**
|
||||||
|
* 周起始天
|
||||||
|
*/
|
||||||
|
firstDayOfWeek: makeNumberProp(0),
|
||||||
|
/**
|
||||||
|
* 日期格式化函数
|
||||||
|
*/
|
||||||
|
formatter: Function as PropType<CalendarFormatter>,
|
||||||
|
/**
|
||||||
|
* type 为范围选择时有效,最大日期范围
|
||||||
|
*/
|
||||||
|
maxRange: Number,
|
||||||
|
/**
|
||||||
|
* type 为范围选择时有效,选择超出最大日期范围时的错误提示文案
|
||||||
|
*/
|
||||||
|
rangePrompt: String,
|
||||||
|
/**
|
||||||
|
* type 为范围选择时有效,是否允许选择同一天
|
||||||
|
*/
|
||||||
|
allowSameDay: makeBooleanProp(false),
|
||||||
|
/**
|
||||||
|
* 选中日期所使用的当日内具体时刻
|
||||||
|
*/
|
||||||
|
defaultTime: {
|
||||||
|
type: [String, Array] as PropType<string | string[]>
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* type 为 'datetime' 或 'datetimerange' 时有效,用于过滤时间选择器的数据
|
||||||
|
*/
|
||||||
|
timeFilter: Function as PropType<CalendarTimeFilter>,
|
||||||
|
/**
|
||||||
|
* type 为 'datetime' 或 'datetimerange' 时有效,是否不展示秒修改
|
||||||
|
*/
|
||||||
|
hideSecond: makeBooleanProp(false),
|
||||||
|
/**
|
||||||
|
* 选择器左侧文案
|
||||||
|
*/
|
||||||
|
label: String,
|
||||||
|
/**
|
||||||
|
* 设置左侧标题宽度
|
||||||
|
*/
|
||||||
|
labelWidth: makeStringProp('33%'),
|
||||||
|
/**
|
||||||
|
* 禁用
|
||||||
|
*/
|
||||||
|
disabled: makeBooleanProp(false),
|
||||||
|
/**
|
||||||
|
* 只读
|
||||||
|
*/
|
||||||
|
readonly: makeBooleanProp(false),
|
||||||
|
/**
|
||||||
|
* 选择器占位符
|
||||||
|
*/
|
||||||
|
placeholder: String,
|
||||||
|
/**
|
||||||
|
* 弹出层标题
|
||||||
|
*/
|
||||||
|
title: String,
|
||||||
|
/**
|
||||||
|
* 选择器的值靠右展示
|
||||||
|
*/
|
||||||
|
alignRight: makeBooleanProp(false),
|
||||||
|
/**
|
||||||
|
* 是否为错误状态,错误状态时右侧内容为红色
|
||||||
|
*/
|
||||||
|
error: makeBooleanProp(false),
|
||||||
|
/**
|
||||||
|
* 是否必填
|
||||||
|
*/
|
||||||
|
required: makeBooleanProp(false),
|
||||||
|
/**
|
||||||
|
* 设置选择器大小,可选值:large
|
||||||
|
*/
|
||||||
|
size: String,
|
||||||
|
/**
|
||||||
|
* 是否垂直居中
|
||||||
|
*/
|
||||||
|
center: makeBooleanProp(false),
|
||||||
|
/**
|
||||||
|
* 点击遮罩是否关闭
|
||||||
|
*/
|
||||||
|
closeOnClickModal: makeBooleanProp(true),
|
||||||
|
/**
|
||||||
|
* 弹框层级
|
||||||
|
*/
|
||||||
|
zIndex: makeNumberProp(15),
|
||||||
|
/**
|
||||||
|
* 是否显示确定按钮
|
||||||
|
*/
|
||||||
|
showConfirm: makeBooleanProp(true),
|
||||||
|
/**
|
||||||
|
* 确定按钮文字
|
||||||
|
*/
|
||||||
|
confirmText: String,
|
||||||
|
/**
|
||||||
|
* 自定义展示文案的格式化函数,返回一个字符串
|
||||||
|
*/
|
||||||
|
displayFormat: Function as PropType<CalendarDisplayFormat>,
|
||||||
|
/**
|
||||||
|
* 自定义范围选择类型的面板内部回显,返回一个字符串
|
||||||
|
*/
|
||||||
|
innerDisplayFormat: Function as PropType<CalendarInnerDisplayFormat>,
|
||||||
|
/**
|
||||||
|
* 是否超出隐藏
|
||||||
|
*/
|
||||||
|
ellipsis: makeBooleanProp(false),
|
||||||
|
/**
|
||||||
|
* 是否显示类型切换功能
|
||||||
|
*/
|
||||||
|
showTypeSwitch: makeBooleanProp(false),
|
||||||
|
/**
|
||||||
|
* 快捷选项,为对象数组,其中对象的 text 必传
|
||||||
|
*/
|
||||||
|
shortcuts: makeArrayProp<Record<string, any>>(),
|
||||||
|
/**
|
||||||
|
* 快捷操作点击回调
|
||||||
|
*/
|
||||||
|
onShortcutsClick: Function as PropType<CalendarOnShortcutsClick>,
|
||||||
|
/**
|
||||||
|
* 弹出面板是否设置底部安全距离(iphone X 类型的机型)
|
||||||
|
*/
|
||||||
|
safeAreaInsetBottom: makeBooleanProp(true),
|
||||||
|
/**
|
||||||
|
* 确定前校验函数,接收 { value, resolve } 参数,通过 resolve 继续执行,resolve 接收 1 个 boolean 参数
|
||||||
|
*/
|
||||||
|
beforeConfirm: Function as PropType<CalendarBeforeConfirm>,
|
||||||
|
/**
|
||||||
|
* 表单域 model 字段名,在使用表单校验功能的情况下,该属性是必填的
|
||||||
|
*/
|
||||||
|
prop: String,
|
||||||
|
/**
|
||||||
|
* 表单验证规则,结合wd-form组件使用
|
||||||
|
*/
|
||||||
|
rules: makeArrayProp<FormItemRule>(),
|
||||||
|
customViewClass: makeStringProp(''),
|
||||||
|
/**
|
||||||
|
* label 外部自定义样式
|
||||||
|
*/
|
||||||
|
customLabelClass: makeStringProp(''),
|
||||||
|
/**
|
||||||
|
* value 外部自定义样式
|
||||||
|
*/
|
||||||
|
customValueClass: makeStringProp(''),
|
||||||
|
/**
|
||||||
|
* 是否在手指松开时立即触发picker-view的 change 事件。若不开启则会在滚动动画结束后触发 change 事件,1.2.25版本起提供,仅微信小程序和支付宝小程序支持。
|
||||||
|
*/
|
||||||
|
immediateChange: makeBooleanProp(false),
|
||||||
|
/**
|
||||||
|
* 是否使用内置单元格
|
||||||
|
* 默认为 true,使用内置单元格
|
||||||
|
*/
|
||||||
|
withCell: makeBooleanProp(true),
|
||||||
|
/**
|
||||||
|
* 是否从页面中脱离出来,用于解决各种 fixed 失效问题 (H5: teleport, APP: renderjs, 小程序: root-portal)
|
||||||
|
*/
|
||||||
|
rootPortal: makeBooleanProp(false),
|
||||||
|
/**
|
||||||
|
* 必填标记位置,可选值:before、after
|
||||||
|
*/
|
||||||
|
markerSide: makeStringProp<'before' | 'after'>('before'),
|
||||||
|
/**
|
||||||
|
* 显示清空按钮
|
||||||
|
*/
|
||||||
|
clearable: makeBooleanProp(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CalendarDisplayFormat = (value: number | number[], type: CalendarType) => string
|
||||||
|
|
||||||
|
export type CalendarInnerDisplayFormat = (value: number, rangeType: 'start' | 'end', type: CalendarType) => string
|
||||||
|
|
||||||
|
export type CalendarBeforeConfirmOption = {
|
||||||
|
value: number | number[] | null
|
||||||
|
resolve: (isPass: boolean) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CalendarBeforeConfirm = (option: CalendarBeforeConfirmOption) => void
|
||||||
|
|
||||||
|
export type CalendarOnShortcutsClickOption = {
|
||||||
|
item: Record<string, any>
|
||||||
|
index: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CalendarOnShortcutsClick = (option: CalendarOnShortcutsClickOption) => number | number[]
|
||||||
|
|
||||||
|
export type CalendarExpose = {
|
||||||
|
/** 关闭时间选择器弹窗 */
|
||||||
|
close: () => void
|
||||||
|
/** 打开时间选择器弹窗 */
|
||||||
|
open: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CalendarProps = ExtractPropTypes<typeof calendarProps>
|
||||||
|
|
||||||
|
export type CalendarInstance = ComponentPublicInstance<CalendarExpose, CalendarProps>
|
||||||
455
node_modules/wot-design-uni/components/wd-calendar/wd-calendar.vue
generated
vendored
Normal file
455
node_modules/wot-design-uni/components/wd-calendar/wd-calendar.vue
generated
vendored
Normal file
@@ -0,0 +1,455 @@
|
|||||||
|
<template>
|
||||||
|
<view :class="`wd-calendar ${customClass}`">
|
||||||
|
<template v-if="withCell">
|
||||||
|
<wd-cell
|
||||||
|
v-if="!$slots.default"
|
||||||
|
:title="label"
|
||||||
|
:value="showValue || placeholder || translate('placeholder')"
|
||||||
|
:required="required"
|
||||||
|
:size="size"
|
||||||
|
:title-width="labelWidth"
|
||||||
|
:prop="prop"
|
||||||
|
:rules="rules"
|
||||||
|
:clickable="!disabled && !readonly"
|
||||||
|
:value-align="alignRight ? 'right' : 'left'"
|
||||||
|
:center="center"
|
||||||
|
:custom-class="cellClass"
|
||||||
|
:custom-style="customStyle"
|
||||||
|
:custom-title-class="customLabelClass"
|
||||||
|
:custom-value-class="customValueClass"
|
||||||
|
:ellipsis="ellipsis"
|
||||||
|
:use-title-slot="!!$slots.label"
|
||||||
|
:marker-side="markerSide"
|
||||||
|
@click="open"
|
||||||
|
>
|
||||||
|
<template #title v-if="$slots.label">
|
||||||
|
<slot name="label"></slot>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #right-icon>
|
||||||
|
<wd-icon v-if="showArrow" custom-class="wd-calendar__arrow" name="arrow-right" />
|
||||||
|
<view v-else-if="showClear" @click.stop="handleClear">
|
||||||
|
<wd-icon custom-class="wd-calendar__clear" name="error-fill" />
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
</wd-cell>
|
||||||
|
<view v-else @click="open">
|
||||||
|
<slot></slot>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
<wd-action-sheet
|
||||||
|
v-model="pickerShow"
|
||||||
|
:duration="250"
|
||||||
|
:close-on-click-modal="closeOnClickModal"
|
||||||
|
:safe-area-inset-bottom="safeAreaInsetBottom"
|
||||||
|
:z-index="zIndex"
|
||||||
|
:root-portal="rootPortal"
|
||||||
|
@close="close"
|
||||||
|
>
|
||||||
|
<view class="wd-calendar__header">
|
||||||
|
<view v-if="!showTypeSwitch && shortcuts.length === 0" class="wd-calendar__title">{{ title || translate('title') }}</view>
|
||||||
|
<view v-if="showTypeSwitch" class="wd-calendar__tabs">
|
||||||
|
<wd-tabs ref="calendarTabs" v-model="currentTab" @change="handleTypeChange">
|
||||||
|
<wd-tab :title="translate('day')" :name="translate('day')" />
|
||||||
|
<wd-tab :title="translate('week')" :name="translate('week')" />
|
||||||
|
<wd-tab :title="translate('month')" :name="translate('month')" />
|
||||||
|
</wd-tabs>
|
||||||
|
</view>
|
||||||
|
<view v-if="shortcuts.length > 0" class="wd-calendar__shortcuts">
|
||||||
|
<wd-tag
|
||||||
|
v-for="(item, index) in shortcuts"
|
||||||
|
:key="index"
|
||||||
|
custom-class="wd-calendar__tag"
|
||||||
|
type="primary"
|
||||||
|
plain
|
||||||
|
round
|
||||||
|
@click="handleShortcutClick(index)"
|
||||||
|
>
|
||||||
|
{{ item.text }}
|
||||||
|
</wd-tag>
|
||||||
|
</view>
|
||||||
|
<wd-icon custom-class="wd-calendar__close" name="add" @click="close" />
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
v-if="inited"
|
||||||
|
:class="`wd-calendar__view ${currentType.indexOf('range') > -1 ? 'is-range' : ''} ${showConfirm ? 'is-show-confirm' : ''}`"
|
||||||
|
>
|
||||||
|
<view v-if="range(type)" :class="`wd-calendar__range-label ${type === 'monthrange' ? 'is-monthrange' : ''}`">
|
||||||
|
<view
|
||||||
|
:class="`wd-calendar__range-label-item ${!calendarValue || !isArray(calendarValue) || !calendarValue[0] ? 'is-placeholder' : ''}`"
|
||||||
|
style="text-align: right"
|
||||||
|
>
|
||||||
|
{{ rangeLabel[0] }}
|
||||||
|
</view>
|
||||||
|
<view class="wd-calendar__range-sperator">/</view>
|
||||||
|
<view :class="`wd-calendar__range-label-item ${!calendarValue || !isArray(calendarValue) || !calendarValue[1] ? 'is-placeholder' : ''}`">
|
||||||
|
{{ rangeLabel[1] }}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<wd-calendar-view
|
||||||
|
ref="calendarView"
|
||||||
|
v-model="calendarValue"
|
||||||
|
:type="currentType"
|
||||||
|
:min-date="minDate"
|
||||||
|
:max-date="maxDate"
|
||||||
|
:first-day-of-week="firstDayOfWeek"
|
||||||
|
:formatter="formatter"
|
||||||
|
:panel-height="panelHeight"
|
||||||
|
:max-range="maxRange"
|
||||||
|
:range-prompt="rangePrompt"
|
||||||
|
:allow-same-day="allowSameDay"
|
||||||
|
:default-time="defaultTime"
|
||||||
|
:time-filter="timeFilter"
|
||||||
|
:hide-second="hideSecond"
|
||||||
|
:show-panel-title="!range(type)"
|
||||||
|
:immediate-change="immediateChange"
|
||||||
|
@change="handleChange"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
<view v-if="showConfirm" class="wd-calendar__confirm">
|
||||||
|
<slot name="confirm-left"></slot>
|
||||||
|
<view class="wd-calendar__confirm-btn-wrapper">
|
||||||
|
<wd-button block :disabled="confirmBtnDisabled" @click="handleConfirm">{{ confirmText || translate('confirm') }}</wd-button>
|
||||||
|
</view>
|
||||||
|
<slot name="confirm-right"></slot>
|
||||||
|
</view>
|
||||||
|
</wd-action-sheet>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
name: 'wd-calendar',
|
||||||
|
options: {
|
||||||
|
addGlobalClass: true,
|
||||||
|
virtualHost: true,
|
||||||
|
styleIsolation: 'shared'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import wdIcon from '../wd-icon/wd-icon.vue'
|
||||||
|
import wdCalendarView from '../wd-calendar-view/wd-calendar-view.vue'
|
||||||
|
import wdActionSheet from '../wd-action-sheet/wd-action-sheet.vue'
|
||||||
|
import wdButton from '../wd-button/wd-button.vue'
|
||||||
|
import wdCell from '../wd-cell/wd-cell.vue'
|
||||||
|
import { ref, computed, watch } from 'vue'
|
||||||
|
import dayjs from '../../dayjs'
|
||||||
|
import { deepClone, isArray, isEqual, padZero, pause } from '../common/util'
|
||||||
|
import { getWeekNumber, isRange } from '../wd-calendar-view/utils'
|
||||||
|
import { FORM_KEY, type FormItemRule } from '../wd-form/types'
|
||||||
|
import { useParent } from '../composables/useParent'
|
||||||
|
import { useTranslate } from '../composables/useTranslate'
|
||||||
|
import { calendarProps, type CalendarExpose } from './types'
|
||||||
|
import type { CalendarType } from '../wd-calendar-view/types'
|
||||||
|
const { translate } = useTranslate('calendar')
|
||||||
|
|
||||||
|
const defaultDisplayFormat = (value: number | number[], type: CalendarType): string => {
|
||||||
|
switch (type) {
|
||||||
|
case 'date':
|
||||||
|
return dayjs(value as number).format('YYYY-MM-DD')
|
||||||
|
case 'dates':
|
||||||
|
return (value as number[])
|
||||||
|
.map((item) => {
|
||||||
|
return dayjs(item).format('YYYY-MM-DD')
|
||||||
|
})
|
||||||
|
.join(', ')
|
||||||
|
case 'daterange':
|
||||||
|
return `${(value as number[])[0] ? dayjs((value as number[])[0]).format('YYYY-MM-DD') : translate('startTime')} ${translate('to')} ${
|
||||||
|
(value as number[])[1] ? dayjs((value as number[])[1]).format('YYYY-MM-DD') : translate('endTime')
|
||||||
|
}`
|
||||||
|
case 'datetime':
|
||||||
|
return dayjs(value as number).format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
case 'datetimerange':
|
||||||
|
return `${(value as number[])[0] ? dayjs((value as number[])[0]).format(translate('timeFormat')) : translate('startTime')} ${translate(
|
||||||
|
'to'
|
||||||
|
)}\n${(value as number[])[1] ? dayjs((value as number[])[1]).format(translate('timeFormat')) : translate('endTime')}`
|
||||||
|
case 'week': {
|
||||||
|
const date = new Date(value as number)
|
||||||
|
const year = date.getFullYear()
|
||||||
|
const week = getWeekNumber(value as number)
|
||||||
|
const weekStart = new Date(date)
|
||||||
|
weekStart.setDate(date.getDate() - date.getDay() + 1)
|
||||||
|
const weekEnd = new Date(date)
|
||||||
|
weekEnd.setDate(date.getDate() + (7 - date.getDay()))
|
||||||
|
const adjustedYear = weekEnd.getFullYear() > year ? weekEnd.getFullYear() : year
|
||||||
|
return translate('weekFormat', adjustedYear, padZero(week))
|
||||||
|
}
|
||||||
|
case 'weekrange': {
|
||||||
|
const date1 = new Date((value as number[])[0])
|
||||||
|
const date2 = new Date((value as number[])[1])
|
||||||
|
const year1 = date1.getFullYear()
|
||||||
|
const year2 = date2.getFullYear()
|
||||||
|
const week1 = getWeekNumber((value as number[])[0])
|
||||||
|
const week2 = getWeekNumber((value as number[])[1])
|
||||||
|
const weekStart1 = new Date(date1)
|
||||||
|
weekStart1.setDate(date1.getDate() - date1.getDay() + 1)
|
||||||
|
const weekEnd1 = new Date(date1)
|
||||||
|
weekEnd1.setDate(date1.getDate() + (7 - date1.getDay()))
|
||||||
|
const weekStart2 = new Date(date2)
|
||||||
|
weekStart2.setDate(date2.getDate() - date2.getDay() + 1)
|
||||||
|
const weekEnd2 = new Date(date2)
|
||||||
|
weekEnd2.setDate(date2.getDate() + (7 - date2.getDay()))
|
||||||
|
const adjustedYear1 = weekEnd1.getFullYear() > year1 ? weekEnd1.getFullYear() : year1
|
||||||
|
const adjustedYear2 = weekEnd2.getFullYear() > year2 ? weekEnd2.getFullYear() : year2
|
||||||
|
return `${(value as number[])[0] ? translate('weekFormat', adjustedYear1, padZero(week1)) : translate('startWeek')} - ${
|
||||||
|
(value as number[])[1] ? translate('weekFormat', adjustedYear2, padZero(week2)) : translate('endWeek')
|
||||||
|
}`
|
||||||
|
}
|
||||||
|
case 'month':
|
||||||
|
return dayjs(value as number).format('YYYY / MM')
|
||||||
|
case 'monthrange':
|
||||||
|
return `${(value as number[])[0] ? dayjs((value as number[])[0]).format('YYYY / MM') : translate('startMonth')} ${translate('to')} ${
|
||||||
|
(value as number[])[1] ? dayjs((value as number[])[1]).format('YYYY / MM') : translate('endMonth')
|
||||||
|
}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatRange = (value: number, rangeType: 'start' | 'end', type: CalendarType) => {
|
||||||
|
switch (type) {
|
||||||
|
case 'daterange':
|
||||||
|
if (!value) {
|
||||||
|
return rangeType === 'end' ? translate('endTime') : translate('startTime')
|
||||||
|
}
|
||||||
|
return dayjs(value).format(translate('dateFormat'))
|
||||||
|
case 'datetimerange':
|
||||||
|
if (!value) {
|
||||||
|
return rangeType === 'end' ? translate('endTime') : translate('startTime')
|
||||||
|
}
|
||||||
|
return dayjs(value).format(translate('timeFormat'))
|
||||||
|
case 'weekrange': {
|
||||||
|
if (!value) {
|
||||||
|
return rangeType === 'end' ? translate('endWeek') : translate('startWeek')
|
||||||
|
}
|
||||||
|
const date = new Date(value)
|
||||||
|
const year = date.getFullYear()
|
||||||
|
const week = getWeekNumber(value)
|
||||||
|
return translate('weekFormat', year, padZero(week))
|
||||||
|
}
|
||||||
|
case 'monthrange':
|
||||||
|
if (!value) {
|
||||||
|
return rangeType === 'end' ? translate('endMonth') : translate('startMonth')
|
||||||
|
}
|
||||||
|
return dayjs(value).format(translate('monthFormat'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps(calendarProps)
|
||||||
|
const emit = defineEmits(['cancel', 'change', 'update:modelValue', 'confirm', 'open', 'clear'])
|
||||||
|
|
||||||
|
const pickerShow = ref<boolean>(false)
|
||||||
|
const calendarValue = ref<null | number | number[]>(null)
|
||||||
|
const lastCalendarValue = ref<null | number | number[]>(null)
|
||||||
|
const panelHeight = ref<number>(338)
|
||||||
|
const confirmBtnDisabled = ref<boolean>(true)
|
||||||
|
const currentTab = ref<number>(0)
|
||||||
|
const lastTab = ref<number>(0)
|
||||||
|
const currentType = ref<CalendarType>('date')
|
||||||
|
const lastCurrentType = ref<CalendarType>()
|
||||||
|
const inited = ref<boolean>(false)
|
||||||
|
const calendarView = ref()
|
||||||
|
const calendarTabs = ref()
|
||||||
|
|
||||||
|
const rangeLabel = computed(() => {
|
||||||
|
const [start, end] = deepClone(isArray(calendarValue.value) ? calendarValue.value : [])
|
||||||
|
return [start, end].map((item, index) => {
|
||||||
|
return (props.innerDisplayFormat || formatRange)(item, index === 0 ? 'start' : 'end', currentType.value)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const showValue = computed(() => {
|
||||||
|
if ((!isArray(props.modelValue) && props.modelValue) || (isArray(props.modelValue) && props.modelValue.length)) {
|
||||||
|
return (props.displayFormat || defaultDisplayFormat)(props.modelValue, lastCurrentType.value || currentType.value)
|
||||||
|
} else {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const cellClass = computed(() => {
|
||||||
|
const classes = ['wd-calendar__cell']
|
||||||
|
if (props.disabled) classes.push('is-disabled')
|
||||||
|
if (props.readonly) classes.push('is-readonly')
|
||||||
|
if (props.error) classes.push('is-error')
|
||||||
|
if (!showValue.value) classes.push('wd-calendar__cell--placeholder')
|
||||||
|
return classes.join(' ')
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.modelValue,
|
||||||
|
(val, oldVal) => {
|
||||||
|
if (isEqual(val, oldVal)) return
|
||||||
|
calendarValue.value = deepClone(val)
|
||||||
|
confirmBtnDisabled.value = getConfirmBtnStatus(val)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.type,
|
||||||
|
(newValue, oldValue) => {
|
||||||
|
if (props.showTypeSwitch) {
|
||||||
|
const tabs = ['date', 'week', 'month']
|
||||||
|
const rangeTabs = ['daterange', 'weekrange', 'monthrange']
|
||||||
|
|
||||||
|
const index = newValue.indexOf('range') > -1 ? rangeTabs.indexOf(newValue) || 0 : tabs.indexOf(newValue)
|
||||||
|
currentTab.value = index
|
||||||
|
}
|
||||||
|
panelHeight.value = props.showConfirm ? 338 : 400
|
||||||
|
currentType.value = deepClone(newValue)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deep: true,
|
||||||
|
immediate: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.showConfirm,
|
||||||
|
(val) => {
|
||||||
|
panelHeight.value = val ? 338 : 400
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deep: true,
|
||||||
|
immediate: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const range = computed(() => {
|
||||||
|
return (type: CalendarType) => {
|
||||||
|
return isRange(type)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 是否展示清除按钮
|
||||||
|
const showClear = computed(() => {
|
||||||
|
return props.clearable && !props.disabled && !props.readonly && showValue.value.length > 0
|
||||||
|
})
|
||||||
|
|
||||||
|
// 是否展示箭头
|
||||||
|
const showArrow = computed(() => {
|
||||||
|
return !props.disabled && !props.readonly && !showClear.value
|
||||||
|
})
|
||||||
|
|
||||||
|
function handleClear() {
|
||||||
|
emit('clear')
|
||||||
|
emit('update:modelValue', null)
|
||||||
|
}
|
||||||
|
|
||||||
|
function scrollIntoView() {
|
||||||
|
calendarView.value && calendarView.value && calendarView.value.$.exposed.scrollIntoView()
|
||||||
|
}
|
||||||
|
// 对外暴露方法
|
||||||
|
async function open() {
|
||||||
|
const { disabled, readonly } = props
|
||||||
|
|
||||||
|
if (disabled || readonly) return
|
||||||
|
|
||||||
|
inited.value = true
|
||||||
|
pickerShow.value = true
|
||||||
|
lastCalendarValue.value = deepClone(calendarValue.value)
|
||||||
|
lastTab.value = currentTab.value
|
||||||
|
lastCurrentType.value = currentType.value
|
||||||
|
// 等待渲染完毕
|
||||||
|
await pause()
|
||||||
|
scrollIntoView()
|
||||||
|
setTimeout(() => {
|
||||||
|
if (props.showTypeSwitch) {
|
||||||
|
calendarTabs.value.scrollIntoView()
|
||||||
|
calendarTabs.value.updateLineStyle(false)
|
||||||
|
}
|
||||||
|
}, 250)
|
||||||
|
emit('open')
|
||||||
|
}
|
||||||
|
// 对外暴露方法
|
||||||
|
function close() {
|
||||||
|
pickerShow.value = false
|
||||||
|
setTimeout(() => {
|
||||||
|
calendarValue.value = deepClone(lastCalendarValue.value)
|
||||||
|
currentTab.value = lastTab.value
|
||||||
|
currentType.value = lastCurrentType.value || 'date'
|
||||||
|
confirmBtnDisabled.value = getConfirmBtnStatus(lastCalendarValue.value)
|
||||||
|
}, 250)
|
||||||
|
emit('cancel')
|
||||||
|
}
|
||||||
|
function handleTypeChange({ index }: { index: number }) {
|
||||||
|
const tabs = ['date', 'week', 'month']
|
||||||
|
const rangeTabs = ['daterange', 'weekrange', 'monthrange']
|
||||||
|
const type = props.type.indexOf('range') > -1 ? rangeTabs[index] : tabs[index]
|
||||||
|
currentTab.value = index
|
||||||
|
currentType.value = type as CalendarType
|
||||||
|
}
|
||||||
|
function getConfirmBtnStatus(value: number | number[] | null) {
|
||||||
|
let confirmBtnDisabled = false
|
||||||
|
// 范围选择未选择满,或者多日期选择未选择日期,按钮置灰不可点击
|
||||||
|
if (
|
||||||
|
(props.type.indexOf('range') > -1 && (!isArray(value) || !value[0] || !value[1] || !value)) ||
|
||||||
|
(props.type === 'dates' && (!isArray(value) || value.length === 0 || !value)) ||
|
||||||
|
!value
|
||||||
|
) {
|
||||||
|
confirmBtnDisabled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return confirmBtnDisabled
|
||||||
|
}
|
||||||
|
function handleChange({ value }: { value: number | number[] | null }) {
|
||||||
|
calendarValue.value = deepClone(value)
|
||||||
|
confirmBtnDisabled.value = getConfirmBtnStatus(value)
|
||||||
|
|
||||||
|
emit('change', {
|
||||||
|
value
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!props.showConfirm && !confirmBtnDisabled.value) {
|
||||||
|
handleConfirm()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function handleConfirm() {
|
||||||
|
if (props.beforeConfirm) {
|
||||||
|
props.beforeConfirm({
|
||||||
|
value: calendarValue.value,
|
||||||
|
resolve: (isPass: boolean) => {
|
||||||
|
isPass && onConfirm()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
onConfirm()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function onConfirm() {
|
||||||
|
pickerShow.value = false
|
||||||
|
lastCurrentType.value = currentType.value
|
||||||
|
emit('update:modelValue', calendarValue.value)
|
||||||
|
emit('confirm', {
|
||||||
|
value: calendarValue.value,
|
||||||
|
type: currentType.value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleShortcutClick(index: number) {
|
||||||
|
if (props.onShortcutsClick && typeof props.onShortcutsClick === 'function') {
|
||||||
|
calendarValue.value = deepClone(
|
||||||
|
props.onShortcutsClick({
|
||||||
|
item: props.shortcuts[index],
|
||||||
|
index
|
||||||
|
})
|
||||||
|
)
|
||||||
|
confirmBtnDisabled.value = getConfirmBtnStatus(calendarValue.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!props.showConfirm) {
|
||||||
|
handleConfirm()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose<CalendarExpose>({
|
||||||
|
close,
|
||||||
|
open
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import './index.scss';
|
||||||
|
</style>
|
||||||
71
node_modules/wot-design-uni/components/wd-card/index.scss
generated
vendored
Normal file
71
node_modules/wot-design-uni/components/wd-card/index.scss
generated
vendored
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
@import "../common/abstracts/variable.scss";
|
||||||
|
@import "../common/abstracts/_mixin.scss";
|
||||||
|
.wot-theme-dark {
|
||||||
|
@include b(card) {
|
||||||
|
background-color: $-dark-background2;
|
||||||
|
|
||||||
|
@include when(rectangle) {
|
||||||
|
|
||||||
|
.wd-card__content {
|
||||||
|
@include halfPixelBorder('top', 0, $-dark-border-color);
|
||||||
|
}
|
||||||
|
.wd-card__footer {
|
||||||
|
@include halfPixelBorder('top', 0, $-dark-border-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(title-content) {
|
||||||
|
color: $-dark-color;
|
||||||
|
}
|
||||||
|
@include e(content) {
|
||||||
|
color: $-dark-color3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include b(card) {
|
||||||
|
padding: $-card-padding;
|
||||||
|
background-color: $-card-bg;
|
||||||
|
line-height: $-card-line-height;
|
||||||
|
margin: $-card-margin;
|
||||||
|
border-radius: $-card-radius;
|
||||||
|
box-shadow: $-card-shadow-color;
|
||||||
|
font-size: $-card-fs;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
|
||||||
|
@include when(rectangle) {
|
||||||
|
margin-left: 0;
|
||||||
|
margin-right: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
box-shadow: none;
|
||||||
|
|
||||||
|
.wd-card__title-content {
|
||||||
|
font-size: $-card-fs;
|
||||||
|
}
|
||||||
|
.wd-card__content {
|
||||||
|
position: relative;
|
||||||
|
padding: $-card-rectangle-content-padding;
|
||||||
|
|
||||||
|
@include halfPixelBorder('top', 0, $-card-content-border-color);
|
||||||
|
}
|
||||||
|
.wd-card__footer {
|
||||||
|
position: relative;
|
||||||
|
padding: $-card-rectangle-footer-padding;
|
||||||
|
|
||||||
|
@include halfPixelBorder('top', 0, $-card-content-border-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@include e(title-content) {
|
||||||
|
padding: 16px 0;
|
||||||
|
color: $-card-title-color;
|
||||||
|
font-size: $-card-title-fs;
|
||||||
|
}
|
||||||
|
@include e(content) {
|
||||||
|
color: $-card-content-color;
|
||||||
|
line-height: $-card-content-line-height;
|
||||||
|
}
|
||||||
|
@include e(footer) {
|
||||||
|
padding: $-card-footer-padding;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
30
node_modules/wot-design-uni/components/wd-card/types.ts
generated
vendored
Normal file
30
node_modules/wot-design-uni/components/wd-card/types.ts
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import type { ExtractPropTypes, PropType } from 'vue'
|
||||||
|
import { baseProps, makeStringProp } from '../common/props'
|
||||||
|
|
||||||
|
export type CardType = 'rectangle'
|
||||||
|
|
||||||
|
export const cardProps = {
|
||||||
|
...baseProps,
|
||||||
|
/**
|
||||||
|
* 卡片类型
|
||||||
|
*/
|
||||||
|
type: String as PropType<CardType>,
|
||||||
|
/**
|
||||||
|
* 卡片标题
|
||||||
|
*/
|
||||||
|
title: String,
|
||||||
|
/**
|
||||||
|
* 标题自定义样式
|
||||||
|
*/
|
||||||
|
customTitleClass: makeStringProp(''),
|
||||||
|
/**
|
||||||
|
* 内容自定义样式
|
||||||
|
*/
|
||||||
|
customContentClass: makeStringProp(''),
|
||||||
|
/**
|
||||||
|
* 底部自定义样式
|
||||||
|
*/
|
||||||
|
customFooterClass: makeStringProp('')
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CardProps = ExtractPropTypes<typeof cardProps>
|
||||||
37
node_modules/wot-design-uni/components/wd-card/wd-card.vue
generated
vendored
Normal file
37
node_modules/wot-design-uni/components/wd-card/wd-card.vue
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<template>
|
||||||
|
<view :class="['wd-card', type == 'rectangle' ? 'is-rectangle' : '', customClass]" :style="customStyle">
|
||||||
|
<view :class="['wd-card__title-content', customTitleClass]" v-if="title || $slots.title">
|
||||||
|
<view class="wd-card__title">
|
||||||
|
<text v-if="title">{{ title }}</text>
|
||||||
|
<slot v-else name="title"></slot>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view :class="`wd-card__content ${customContentClass}`">
|
||||||
|
<slot></slot>
|
||||||
|
</view>
|
||||||
|
<view :class="`wd-card__footer ${customFooterClass}`" v-if="$slots.footer">
|
||||||
|
<slot name="footer"></slot>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
name: 'wd-card',
|
||||||
|
options: {
|
||||||
|
addGlobalClass: true,
|
||||||
|
virtualHost: true,
|
||||||
|
styleIsolation: 'shared'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { cardProps } from './types'
|
||||||
|
|
||||||
|
defineProps(cardProps)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import './index.scss';
|
||||||
|
</style>
|
||||||
61
node_modules/wot-design-uni/components/wd-cell-group/index.scss
generated
vendored
Normal file
61
node_modules/wot-design-uni/components/wd-cell-group/index.scss
generated
vendored
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
@import '../common/abstracts/variable.scss';
|
||||||
|
@import '../common/abstracts/_mixin.scss';
|
||||||
|
|
||||||
|
.wot-theme-dark {
|
||||||
|
@include b(cell-group) {
|
||||||
|
background-color: $-dark-background2;
|
||||||
|
|
||||||
|
@include when(border) {
|
||||||
|
.wd-cell-group__title {
|
||||||
|
@include halfPixelBorder('bottom', 0, $-dark-border-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(title) {
|
||||||
|
background: $-dark-background2;
|
||||||
|
color: $-dark-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(right) {
|
||||||
|
color: $-dark-color3;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(body) {
|
||||||
|
background: $-dark-background2;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include b(cell-group) {
|
||||||
|
background-color: $-color-white;
|
||||||
|
|
||||||
|
@include when(border) {
|
||||||
|
.wd-cell-group__title {
|
||||||
|
@include halfPixelBorder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@include e(title) {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: $-cell-group-padding;
|
||||||
|
background: $-color-white;
|
||||||
|
font-size: $-cell-group-title-fs;
|
||||||
|
color: $-cell-group-title-color;
|
||||||
|
font-weight: $-fw-medium;
|
||||||
|
line-height: 1.43;
|
||||||
|
}
|
||||||
|
@include e(right) {
|
||||||
|
color: $-cell-group-value-color;
|
||||||
|
font-size: $-cell-group-value-fs;
|
||||||
|
}
|
||||||
|
@include e(body) {
|
||||||
|
background: $-color-white;
|
||||||
|
}
|
||||||
|
@include m(insert) {
|
||||||
|
border-radius: $-cell-group-insert-radius;
|
||||||
|
overflow: hidden;
|
||||||
|
margin: $-cell-group-insert-margin;
|
||||||
|
}
|
||||||
|
}
|
||||||
45
node_modules/wot-design-uni/components/wd-cell-group/types.ts
generated
vendored
Normal file
45
node_modules/wot-design-uni/components/wd-cell-group/types.ts
generated
vendored
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
* @Author: weisheng
|
||||||
|
* @Date: 2023-12-14 11:21:58
|
||||||
|
* @LastEditTime: 2024-03-18 13:57:14
|
||||||
|
* @LastEditors: weisheng
|
||||||
|
* @Description:
|
||||||
|
* @FilePath: \wot-design-uni\src\uni_modules\wot-design-uni\components\wd-cell-group\types.ts
|
||||||
|
* 记得注释
|
||||||
|
*/
|
||||||
|
import { type ExtractPropTypes, type InjectionKey } from 'vue'
|
||||||
|
import { baseProps, makeBooleanProp } from '../common/props'
|
||||||
|
|
||||||
|
export type CelllGroupProvide = {
|
||||||
|
props: {
|
||||||
|
border?: boolean
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CELL_GROUP_KEY: InjectionKey<CelllGroupProvide> = Symbol('wd-cell-group')
|
||||||
|
|
||||||
|
export const cellGroupProps = {
|
||||||
|
...baseProps,
|
||||||
|
/**
|
||||||
|
* 分组标题
|
||||||
|
*/
|
||||||
|
title: String,
|
||||||
|
/**
|
||||||
|
* 分组右侧内容
|
||||||
|
*/
|
||||||
|
value: String,
|
||||||
|
/**
|
||||||
|
* 分组启用插槽
|
||||||
|
*/
|
||||||
|
useSlot: makeBooleanProp(false),
|
||||||
|
/**
|
||||||
|
* 是否展示边框线
|
||||||
|
*/
|
||||||
|
border: makeBooleanProp(false),
|
||||||
|
/**
|
||||||
|
* 是否展示为圆角卡片风格
|
||||||
|
*/
|
||||||
|
insert: makeBooleanProp(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CellGroupProps = ExtractPropTypes<typeof cellGroupProps>
|
||||||
45
node_modules/wot-design-uni/components/wd-cell-group/wd-cell-group.vue
generated
vendored
Normal file
45
node_modules/wot-design-uni/components/wd-cell-group/wd-cell-group.vue
generated
vendored
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
<template>
|
||||||
|
<view :class="['wd-cell-group', border ? 'is-border' : '', customClass, insert ? 'wd-cell-group--insert' : '']" :style="customStyle">
|
||||||
|
<view v-if="title || value || useSlot" class="wd-cell-group__title">
|
||||||
|
<!--左侧标题-->
|
||||||
|
<view class="wd-cell-group__left">
|
||||||
|
<text v-if="!$slots.title">{{ title }}</text>
|
||||||
|
<slot v-else name="title"></slot>
|
||||||
|
</view>
|
||||||
|
<!--右侧标题-->
|
||||||
|
<view class="wd-cell-group__right">
|
||||||
|
<text v-if="!$slots.value">{{ value }}</text>
|
||||||
|
<slot v-else name="value"></slot>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="wd-cell-group__body">
|
||||||
|
<slot></slot>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
name: 'wd-cell-group',
|
||||||
|
options: {
|
||||||
|
addGlobalClass: true,
|
||||||
|
virtualHost: true,
|
||||||
|
styleIsolation: 'shared'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { useChildren } from '../composables/useChildren'
|
||||||
|
import { CELL_GROUP_KEY, cellGroupProps } from './types'
|
||||||
|
|
||||||
|
const props = defineProps(cellGroupProps)
|
||||||
|
|
||||||
|
const { linkChildren } = useChildren(CELL_GROUP_KEY)
|
||||||
|
|
||||||
|
linkChildren({ props })
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import './index.scss';
|
||||||
|
</style>
|
||||||
206
node_modules/wot-design-uni/components/wd-cell/index.scss
generated
vendored
Normal file
206
node_modules/wot-design-uni/components/wd-cell/index.scss
generated
vendored
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
@import '../common/abstracts/variable.scss';
|
||||||
|
@import '../common/abstracts/_mixin.scss';
|
||||||
|
|
||||||
|
.wot-theme-dark {
|
||||||
|
@include b(cell) {
|
||||||
|
background-color: $-dark-background2;
|
||||||
|
color: $-dark-color;
|
||||||
|
|
||||||
|
@include e(value) {
|
||||||
|
color: $-dark-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(label) {
|
||||||
|
color: $-dark-color3;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(hover) {
|
||||||
|
background-color: $-dark-background4;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(border) {
|
||||||
|
.wd-cell__wrapper {
|
||||||
|
@include halfPixelBorder('top', 0, $-dark-border-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.wd-cell__arrow-right) {
|
||||||
|
color: $-dark-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include b(cell) {
|
||||||
|
position: relative;
|
||||||
|
padding-left: $-cell-padding;
|
||||||
|
background-color: $-color-white;
|
||||||
|
text-decoration: none;
|
||||||
|
color: $-cell-title-color;
|
||||||
|
line-height: $-cell-line-height;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
@include when(border) {
|
||||||
|
.wd-cell__wrapper {
|
||||||
|
@include halfPixelBorder('top');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(wrapper) {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
padding: $-cell-wrapper-padding $-cell-padding $-cell-wrapper-padding 0;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-start;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
@include when(vertical) {
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
.wd-cell__right {
|
||||||
|
margin-top: $-cell-vertical-top;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wd-cell__value {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wd-cell__left {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(label) {
|
||||||
|
padding: $-cell-wrapper-padding-with-label $-cell-padding $-cell-wrapper-padding-with-label 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(left) {
|
||||||
|
position: relative;
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
text-align: left;
|
||||||
|
font-size: $-cell-title-fs;
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin-right: $-cell-padding;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(right) {
|
||||||
|
position: relative;
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(title) {
|
||||||
|
font-size: $-cell-title-fs;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(required) {
|
||||||
|
font-size: $-cell-required-size;
|
||||||
|
color: $-cell-required-color;
|
||||||
|
margin-left: $-cell-required-margin;
|
||||||
|
|
||||||
|
@include m(left) {
|
||||||
|
margin-left: 0;
|
||||||
|
margin-right: $-cell-required-margin;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(label) {
|
||||||
|
margin-top: 2px;
|
||||||
|
font-size: $-cell-label-fs;
|
||||||
|
color: $-cell-label-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include edeep(icon) {
|
||||||
|
display: block;
|
||||||
|
position: relative;
|
||||||
|
margin-right: $-cell-icon-right;
|
||||||
|
font-size: $-cell-icon-size;
|
||||||
|
height: $-cell-line-height;
|
||||||
|
line-height: $-cell-line-height;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(body){
|
||||||
|
display: flex;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(value) {
|
||||||
|
position: relative;
|
||||||
|
flex: 1;
|
||||||
|
font-size: $-cell-value-fs;
|
||||||
|
color: $-cell-value-color;
|
||||||
|
vertical-align: middle;
|
||||||
|
|
||||||
|
@include m(left) {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include m(right) {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include m(ellipsis) {
|
||||||
|
@include lineEllipsis;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include edeep(arrow-right) {
|
||||||
|
display: block;
|
||||||
|
margin-left: 8px;
|
||||||
|
width: $-cell-arrow-size;
|
||||||
|
font-size: $-cell-arrow-size;
|
||||||
|
color: $-cell-arrow-color;
|
||||||
|
height: $-cell-line-height;
|
||||||
|
line-height: $-cell-line-height;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(error-message){
|
||||||
|
color: $-form-item-error-message-color;
|
||||||
|
font-size: $-form-item-error-message-font-size;
|
||||||
|
line-height: $-form-item-error-message-line-height;
|
||||||
|
text-align: left;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(link) {
|
||||||
|
-webkit-tap-highlight-color: $-cell-tap-bg;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(hover) {
|
||||||
|
background-color: $-cell-tap-bg;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(large) {
|
||||||
|
.wd-cell__title {
|
||||||
|
font-size: $-cell-title-fs-large;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wd-cell__wrapper {
|
||||||
|
padding-top: $-cell-wrapper-padding-large;
|
||||||
|
padding-bottom: $-cell-wrapper-padding-large;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wd-cell__label {
|
||||||
|
font-size: $-cell-label-fs-large;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wd-cell__value {
|
||||||
|
font-size: $-cell-value-fs-large;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.wd-cell__icon) {
|
||||||
|
font-size: $-cell-icon-size-large;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(center) {
|
||||||
|
.wd-cell__wrapper {
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
117
node_modules/wot-design-uni/components/wd-cell/types.ts
generated
vendored
Normal file
117
node_modules/wot-design-uni/components/wd-cell/types.ts
generated
vendored
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
import type { ExtractPropTypes } from 'vue'
|
||||||
|
import { baseProps, makeArrayProp, makeBooleanProp, makeStringProp, makeNumericProp, numericProp } from '../common/props'
|
||||||
|
|
||||||
|
import { type FormItemRule } from '../wd-form/types'
|
||||||
|
|
||||||
|
type CellArrowDirection = 'left' | 'up' | 'down' | 'right'
|
||||||
|
|
||||||
|
export const cellProps = {
|
||||||
|
...baseProps,
|
||||||
|
/**
|
||||||
|
* 标题
|
||||||
|
*/
|
||||||
|
title: String,
|
||||||
|
/**
|
||||||
|
* 右侧内容
|
||||||
|
*/
|
||||||
|
value: makeNumericProp(''),
|
||||||
|
/**
|
||||||
|
* 图标类名
|
||||||
|
*/
|
||||||
|
icon: String,
|
||||||
|
/**
|
||||||
|
* 图标大小
|
||||||
|
*/
|
||||||
|
iconSize: numericProp,
|
||||||
|
/**
|
||||||
|
* 描述信息
|
||||||
|
*/
|
||||||
|
label: String,
|
||||||
|
/**
|
||||||
|
* 是否为跳转链接
|
||||||
|
*/
|
||||||
|
isLink: makeBooleanProp(false),
|
||||||
|
/**
|
||||||
|
* 跳转地址
|
||||||
|
*/
|
||||||
|
to: String,
|
||||||
|
/**
|
||||||
|
* 跳转时是否替换栈顶页面
|
||||||
|
*/
|
||||||
|
replace: makeBooleanProp(false),
|
||||||
|
/**
|
||||||
|
* 开启点击反馈,is-link 默认开启
|
||||||
|
*/
|
||||||
|
clickable: makeBooleanProp(false),
|
||||||
|
/**
|
||||||
|
* 设置单元格大小,可选值:large
|
||||||
|
*/
|
||||||
|
size: String,
|
||||||
|
/**
|
||||||
|
* 是否展示边框线
|
||||||
|
*/
|
||||||
|
border: makeBooleanProp(void 0),
|
||||||
|
/**
|
||||||
|
* 设置左侧标题宽度
|
||||||
|
*/
|
||||||
|
titleWidth: String,
|
||||||
|
/**
|
||||||
|
* 是否垂直居中,默认顶部居中
|
||||||
|
*/
|
||||||
|
center: makeBooleanProp(false),
|
||||||
|
/**
|
||||||
|
* 是否必填
|
||||||
|
*/
|
||||||
|
required: makeBooleanProp(false),
|
||||||
|
/**
|
||||||
|
* 表单属性,上下结构
|
||||||
|
*/
|
||||||
|
vertical: makeBooleanProp(false),
|
||||||
|
/**
|
||||||
|
* 表单域 model 字段名,在使用表单校验功能的情况下,该属性是必填的
|
||||||
|
*/
|
||||||
|
prop: String,
|
||||||
|
/**
|
||||||
|
* 表单验证规则,结合wd-form组件使用
|
||||||
|
*/
|
||||||
|
rules: makeArrayProp<FormItemRule>(),
|
||||||
|
/**
|
||||||
|
* icon 使用 slot 时的自定义样式
|
||||||
|
*/
|
||||||
|
customIconClass: makeStringProp(''),
|
||||||
|
/**
|
||||||
|
* label 使用 slot 时的自定义样式
|
||||||
|
*/
|
||||||
|
customLabelClass: makeStringProp(''),
|
||||||
|
/**
|
||||||
|
* value 使用 slot 时的自定义样式
|
||||||
|
*/
|
||||||
|
customValueClass: makeStringProp(''),
|
||||||
|
/**
|
||||||
|
* title 使用 slot 时的自定义样式
|
||||||
|
*/
|
||||||
|
customTitleClass: makeStringProp(''),
|
||||||
|
/**
|
||||||
|
* value 文字对齐方式,可选值:left、right、center
|
||||||
|
*/
|
||||||
|
valueAlign: makeStringProp<'left' | 'right'>('right'),
|
||||||
|
/**
|
||||||
|
* 是否超出隐藏,显示省略号
|
||||||
|
*/
|
||||||
|
ellipsis: makeBooleanProp(false),
|
||||||
|
/**
|
||||||
|
* 是否启用title插槽,默认启用,用来解决插槽传递时v-slot和v-if冲突问题。
|
||||||
|
* 问题见:https://github.com/dcloudio/uni-app/issues/4847
|
||||||
|
*/
|
||||||
|
useTitleSlot: makeBooleanProp(true),
|
||||||
|
/**
|
||||||
|
* 必填标记位置,可选值:before(标签前)、after(标签后)
|
||||||
|
*/
|
||||||
|
markerSide: makeStringProp<'before' | 'after'>('before'),
|
||||||
|
/**
|
||||||
|
* 箭头方向,可选值:left、up、down、right,只在 is-link 为 true 时生效
|
||||||
|
*/
|
||||||
|
arrowDirection: makeStringProp<CellArrowDirection>('right')
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CellProps = ExtractPropTypes<typeof cellProps>
|
||||||
140
node_modules/wot-design-uni/components/wd-cell/wd-cell.vue
generated
vendored
Normal file
140
node_modules/wot-design-uni/components/wd-cell/wd-cell.vue
generated
vendored
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
<template>
|
||||||
|
<view
|
||||||
|
:class="['wd-cell', isBorder ? 'is-border' : '', size ? 'is-' + size : '', center ? 'is-center' : '', customClass]"
|
||||||
|
:style="customStyle"
|
||||||
|
:hover-class="isLink || clickable ? 'is-hover' : 'none'"
|
||||||
|
:hover-stay-time="70"
|
||||||
|
@click="onClick"
|
||||||
|
>
|
||||||
|
<view :class="['wd-cell__wrapper', vertical ? 'is-vertical' : '']">
|
||||||
|
<view v-if="showLeft" class="wd-cell__left" :style="titleWidth ? 'min-width:' + titleWidth + ';max-width:' + titleWidth + ';' : ''">
|
||||||
|
<text v-if="isRequired && markerSide === 'before'" class="wd-cell__required wd-cell__required--left">*</text>
|
||||||
|
<!--左侧icon部位-->
|
||||||
|
<slot name="icon">
|
||||||
|
<wd-icon v-if="icon" :name="icon" :size="iconSize" :custom-class="`wd-cell__icon ${customIconClass}`"></wd-icon>
|
||||||
|
</slot>
|
||||||
|
|
||||||
|
<view class="wd-cell__title">
|
||||||
|
<!--title BEGIN-->
|
||||||
|
<slot v-if="useTitleSlot && $slots.title" name="title"></slot>
|
||||||
|
<text v-else-if="title" :class="customTitleClass">{{ title }}</text>
|
||||||
|
|
||||||
|
<!--title END-->
|
||||||
|
|
||||||
|
<!--label BEGIN-->
|
||||||
|
<slot name="label">
|
||||||
|
<view v-if="label" :class="`wd-cell__label ${customLabelClass}`">{{ label }}</view>
|
||||||
|
</slot>
|
||||||
|
<!--label END-->
|
||||||
|
</view>
|
||||||
|
<text v-if="isRequired && markerSide === 'after'" class="wd-cell__required">*</text>
|
||||||
|
</view>
|
||||||
|
<!--right content BEGIN-->
|
||||||
|
<view class="wd-cell__right">
|
||||||
|
<view class="wd-cell__body">
|
||||||
|
<!--文案内容-->
|
||||||
|
<view :class="`wd-cell__value ${customValueClass} wd-cell__value--${valueAlign} ${ellipsis ? 'wd-cell__value--ellipsis' : ''}`">
|
||||||
|
<slot>{{ value }}</slot>
|
||||||
|
</view>
|
||||||
|
<!--箭头-->
|
||||||
|
<wd-icon v-if="isLink" custom-class="wd-cell__arrow-right" :name="`arrow-${arrowDirection || 'right'}`" />
|
||||||
|
<slot v-else name="right-icon" />
|
||||||
|
</view>
|
||||||
|
<view v-if="errorMessage" class="wd-cell__error-message">{{ errorMessage }}</view>
|
||||||
|
</view>
|
||||||
|
<!--right content END-->
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
name: 'wd-cell',
|
||||||
|
options: {
|
||||||
|
addGlobalClass: true,
|
||||||
|
virtualHost: true,
|
||||||
|
styleIsolation: 'shared'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import wdIcon from '../wd-icon/wd-icon.vue'
|
||||||
|
import { computed, useSlots } from 'vue'
|
||||||
|
import { useCell } from '../composables/useCell'
|
||||||
|
import { useParent } from '../composables/useParent'
|
||||||
|
import { FORM_KEY } from '../wd-form/types'
|
||||||
|
import { cellProps } from './types'
|
||||||
|
import { isDef } from '../common/util'
|
||||||
|
|
||||||
|
const props = defineProps(cellProps)
|
||||||
|
const emit = defineEmits(['click'])
|
||||||
|
|
||||||
|
// 获取插槽
|
||||||
|
const slots = useSlots()
|
||||||
|
|
||||||
|
const cell = useCell()
|
||||||
|
|
||||||
|
const isBorder = computed(() => {
|
||||||
|
return Boolean(isDef(props.border) ? props.border : cell.border.value)
|
||||||
|
})
|
||||||
|
|
||||||
|
const { parent: form } = useParent(FORM_KEY)
|
||||||
|
|
||||||
|
const errorMessage = computed(() => {
|
||||||
|
if (form && props.prop && form.errorMessages && form.errorMessages[props.prop]) {
|
||||||
|
return form.errorMessages[props.prop]
|
||||||
|
} else {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 是否展示必填
|
||||||
|
const isRequired = computed(() => {
|
||||||
|
let formRequired = false
|
||||||
|
if (form && form.props.rules) {
|
||||||
|
const rules = form.props.rules
|
||||||
|
for (const key in rules) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(rules, key) && key === props.prop && Array.isArray(rules[key])) {
|
||||||
|
formRequired = rules[key].some((rule) => rule.required)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return props.required || props.rules.some((rule) => rule.required) || formRequired
|
||||||
|
})
|
||||||
|
|
||||||
|
// 是否展示左侧部分
|
||||||
|
const showLeft = computed(() => {
|
||||||
|
// 插槽优先级高于props
|
||||||
|
// 有icon插槽或icon属性
|
||||||
|
const hasIcon = slots.icon || props.icon
|
||||||
|
// 有title插槽或title属性
|
||||||
|
const hasTitle = (slots.title && props.useTitleSlot) || props.title
|
||||||
|
// 有label插槽或label属性
|
||||||
|
const hasLabel = slots.label || props.label
|
||||||
|
|
||||||
|
return hasIcon || hasTitle || hasLabel
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 点击cell的handle
|
||||||
|
*/
|
||||||
|
function onClick() {
|
||||||
|
const url = props.to
|
||||||
|
|
||||||
|
if (props.clickable || props.isLink) {
|
||||||
|
emit('click')
|
||||||
|
}
|
||||||
|
if (url && props.isLink) {
|
||||||
|
if (props.replace) {
|
||||||
|
uni.redirectTo({ url })
|
||||||
|
} else {
|
||||||
|
uni.navigateTo({ url })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import './index.scss';
|
||||||
|
</style>
|
||||||
20
node_modules/wot-design-uni/components/wd-checkbox-group/index.scss
generated
vendored
Normal file
20
node_modules/wot-design-uni/components/wd-checkbox-group/index.scss
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
@import "./../common/abstracts/_mixin.scss";
|
||||||
|
@import "./../common/abstracts/variable.scss";
|
||||||
|
|
||||||
|
.wot-theme-dark {
|
||||||
|
@include b(checkbox-group) {
|
||||||
|
background-color: $-dark-background2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@include b(checkbox-group) {
|
||||||
|
background-color: $-checkbox-bg;
|
||||||
|
|
||||||
|
// 上下20px 左右15px 内部间隔12px
|
||||||
|
@include when(button) {
|
||||||
|
width: 100%;
|
||||||
|
padding: 8px 3px 20px 15px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow: hidden;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
59
node_modules/wot-design-uni/components/wd-checkbox-group/types.ts
generated
vendored
Normal file
59
node_modules/wot-design-uni/components/wd-checkbox-group/types.ts
generated
vendored
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import { type ExtractPropTypes, type InjectionKey, type PropType } from 'vue'
|
||||||
|
import type { CheckShape } from '../wd-checkbox/types'
|
||||||
|
import { baseProps, makeBooleanProp, makeNumberProp, makeStringProp } from '../common/props'
|
||||||
|
|
||||||
|
export type RequiredModelValue = {
|
||||||
|
modelValue: Array<string | number | boolean>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type checkboxGroupProvide = {
|
||||||
|
props: Partial<Omit<CheckboxGroupProps, 'modelValue'>> & RequiredModelValue
|
||||||
|
changeSelectState: (value: string | number | boolean) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CHECKBOX_GROUP_KEY: InjectionKey<checkboxGroupProvide> = Symbol('wd-checkbox-group')
|
||||||
|
|
||||||
|
export const checkboxGroupProps = {
|
||||||
|
...baseProps,
|
||||||
|
/**
|
||||||
|
* 绑定值
|
||||||
|
*/
|
||||||
|
modelValue: {
|
||||||
|
type: Array as PropType<Array<string | number | boolean>>,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 表单模式
|
||||||
|
*/
|
||||||
|
cell: makeBooleanProp(false),
|
||||||
|
/**
|
||||||
|
* 单选框形状,可选值:circle / square / button
|
||||||
|
*/
|
||||||
|
shape: makeStringProp<CheckShape>('circle'),
|
||||||
|
/**
|
||||||
|
* 选中的颜色
|
||||||
|
*/
|
||||||
|
checkedColor: String,
|
||||||
|
/**
|
||||||
|
* 禁用
|
||||||
|
*/
|
||||||
|
disabled: makeBooleanProp(false),
|
||||||
|
/**
|
||||||
|
* 最小选中的数量
|
||||||
|
*/
|
||||||
|
min: makeNumberProp(0),
|
||||||
|
/**
|
||||||
|
* 最大选中的数量,0 为无限数量,默认为 0
|
||||||
|
*/
|
||||||
|
max: makeNumberProp(0),
|
||||||
|
/**
|
||||||
|
* 同行展示
|
||||||
|
*/
|
||||||
|
inline: makeBooleanProp(false),
|
||||||
|
/**
|
||||||
|
* 设置大小,可选值:large
|
||||||
|
*/
|
||||||
|
size: String
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CheckboxGroupProps = ExtractPropTypes<typeof checkboxGroupProps>
|
||||||
100
node_modules/wot-design-uni/components/wd-checkbox-group/wd-checkbox-group.vue
generated
vendored
Normal file
100
node_modules/wot-design-uni/components/wd-checkbox-group/wd-checkbox-group.vue
generated
vendored
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
<template>
|
||||||
|
<view :class="`wd-checkbox-group ${shape === 'button' && cell ? 'is-button' : ''} ${customClass}`" :style="customStyle">
|
||||||
|
<slot />
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
name: 'wd-checkbox-group',
|
||||||
|
options: {
|
||||||
|
addGlobalClass: true,
|
||||||
|
virtualHost: true,
|
||||||
|
styleIsolation: 'shared'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { watch } from 'vue'
|
||||||
|
import { checkNumRange, deepClone } from '../common/util'
|
||||||
|
import { useChildren } from '../composables/useChildren'
|
||||||
|
import { CHECKBOX_GROUP_KEY, checkboxGroupProps } from './types'
|
||||||
|
|
||||||
|
const props = defineProps(checkboxGroupProps)
|
||||||
|
const emit = defineEmits(['change', 'update:modelValue'])
|
||||||
|
|
||||||
|
const { linkChildren } = useChildren(CHECKBOX_GROUP_KEY)
|
||||||
|
|
||||||
|
linkChildren({ props, changeSelectState })
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.modelValue,
|
||||||
|
(newValue) => {
|
||||||
|
// 传入的value数组中包括重复的元素,这种情况非法。
|
||||||
|
if (new Set(newValue).size !== newValue.length) {
|
||||||
|
// eslint-disable-next-line quotes
|
||||||
|
console.error("checkboxGroup's bound value includes same value")
|
||||||
|
}
|
||||||
|
if (newValue.length < props.min) {
|
||||||
|
// eslint-disable-next-line quotes
|
||||||
|
console.error("checkboxGroup's bound value's length can't be less than min")
|
||||||
|
}
|
||||||
|
if (props.max !== 0 && newValue.length > props.max) {
|
||||||
|
// eslint-disable-next-line quotes
|
||||||
|
console.error("checkboxGroup's bound value's length can't be large than max")
|
||||||
|
}
|
||||||
|
// 每次value变化都会触发重新匹配选中项
|
||||||
|
},
|
||||||
|
{ deep: true, immediate: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.shape,
|
||||||
|
(newValue) => {
|
||||||
|
const type = ['circle', 'square', 'button']
|
||||||
|
if (type.indexOf(newValue) === -1) console.error(`shape must be one of ${type.toString()}`)
|
||||||
|
},
|
||||||
|
{ deep: true, immediate: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.min,
|
||||||
|
(newValue) => {
|
||||||
|
checkNumRange(newValue, 'min')
|
||||||
|
},
|
||||||
|
{ deep: true, immediate: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.max,
|
||||||
|
(newValue) => {
|
||||||
|
checkNumRange(newValue, 'max')
|
||||||
|
},
|
||||||
|
{ deep: true, immediate: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 子节点通知父节点修改子节点选中状态
|
||||||
|
* @param {any} value 子组件的标识符
|
||||||
|
*/
|
||||||
|
function changeSelectState(value: string | number | boolean) {
|
||||||
|
const temp: (string | number | boolean)[] = deepClone(props.modelValue)
|
||||||
|
const index = temp.indexOf(value)
|
||||||
|
if (index > -1) {
|
||||||
|
// 已经选中,则从 value 列表中删除子节点的标识符。
|
||||||
|
temp.splice(index, 1)
|
||||||
|
} else {
|
||||||
|
// 之前未选中,则现在把加子节点的标识符加到 value 列表中。
|
||||||
|
temp.push(value)
|
||||||
|
}
|
||||||
|
emit('update:modelValue', temp)
|
||||||
|
|
||||||
|
emit('change', {
|
||||||
|
value: temp
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import './index.scss';
|
||||||
|
</style>
|
||||||
285
node_modules/wot-design-uni/components/wd-checkbox/index.scss
generated
vendored
Normal file
285
node_modules/wot-design-uni/components/wd-checkbox/index.scss
generated
vendored
Normal file
@@ -0,0 +1,285 @@
|
|||||||
|
@import "./../common/abstracts/_mixin.scss";
|
||||||
|
@import "./../common/abstracts/variable.scss";
|
||||||
|
|
||||||
|
.wot-theme-dark {
|
||||||
|
@include b(checkbox) {
|
||||||
|
@include e(shape) {
|
||||||
|
background: transparent;
|
||||||
|
border-color: $-checkbox-border-color;
|
||||||
|
color: $-checkbox-check-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(label) {
|
||||||
|
color: $-dark-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(disabled) {
|
||||||
|
.wd-checkbox__shape {
|
||||||
|
border-color: $-dark-color-gray;
|
||||||
|
background: $-dark-background4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wd-checkbox__label {
|
||||||
|
color: $-dark-color-gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.wd-checkbox__check) {
|
||||||
|
color: $-dark-color-gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(checked) {
|
||||||
|
.wd-checkbox__shape {
|
||||||
|
color: $-dark-color-gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wd-checkbox__label {
|
||||||
|
color: $-dark-color-gray;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(button) {
|
||||||
|
|
||||||
|
.wd-checkbox__label {
|
||||||
|
border-color: #c8c9cc;
|
||||||
|
background: #3a3a3c;
|
||||||
|
color: $-dark-color-gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(checked) {
|
||||||
|
.wd-checkbox__label {
|
||||||
|
border-color: #c8c9cc;
|
||||||
|
background: #3a3a3c;
|
||||||
|
color: #c8c9cc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(button) {
|
||||||
|
.wd-checkbox__label {
|
||||||
|
background-color: $-dark-background;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(checked) {
|
||||||
|
.wd-checkbox__label {
|
||||||
|
background-color: $-dark-background2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include b(checkbox) {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: $-checkbox-margin;
|
||||||
|
font-size: 0;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
line-height: 1.2;
|
||||||
|
|
||||||
|
@include when(last-child) {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(shape) {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
width: $-checkbox-size;
|
||||||
|
height: $-checkbox-size;
|
||||||
|
border: 2px solid $-checkbox-border-color;
|
||||||
|
border-radius: 50%;
|
||||||
|
color: $-checkbox-check-color;
|
||||||
|
background: $-checkbox-bg;
|
||||||
|
vertical-align: middle;
|
||||||
|
transition: background 0.2s;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
@include when(square) {
|
||||||
|
border-radius: $-checkbox-square-radius;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(input) {
|
||||||
|
position: absolute;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
margin: 0;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include edeep(btn-check) {
|
||||||
|
display: inline-block;
|
||||||
|
font-size: $-checkbox-icon-size;
|
||||||
|
margin-right: 4px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(txt) {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
line-height: 20px;
|
||||||
|
@include lineEllipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(label) {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: $-checkbox-label-margin;
|
||||||
|
vertical-align: middle;
|
||||||
|
font-size: $-checkbox-label-fs;
|
||||||
|
color: $-checkbox-label-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include edeep(check) {
|
||||||
|
color: $-checkbox-check-color;
|
||||||
|
font-size: $-checkbox-icon-size;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(checked) {
|
||||||
|
.wd-checkbox__shape {
|
||||||
|
color: $-checkbox-checked-color;
|
||||||
|
background: currentColor;
|
||||||
|
border-color: currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.wd-checkbox__check) {
|
||||||
|
opacity: 1;
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(button) {
|
||||||
|
display: inline-block;
|
||||||
|
margin-bottom: 0;
|
||||||
|
margin-right: $-checkbox-margin;
|
||||||
|
vertical-align: top;
|
||||||
|
font-size: $-checkbox-button-font-size;
|
||||||
|
|
||||||
|
@include when(last-child) {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wd-checkbox__shape {
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
opacity: 0;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wd-checkbox__label {
|
||||||
|
display: inline-flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
min-width: $-checkbox-button-min-width;
|
||||||
|
height: $-checkbox-button-height;
|
||||||
|
font-size: $-checkbox-button-font-size;
|
||||||
|
margin-left: 0;
|
||||||
|
padding: 5px 15px;
|
||||||
|
border: 1px solid $-checkbox-button-border;
|
||||||
|
background-color: $-checkbox-button-bg;
|
||||||
|
border-radius: $-checkbox-button-radius;
|
||||||
|
transition: color 0.2s, border 0.2s;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(checked) {
|
||||||
|
.wd-checkbox__label {
|
||||||
|
color: $-checkbox-checked-color;
|
||||||
|
background-color: $-checkbox-bg;
|
||||||
|
border-color: $-checkbox-checked-color;
|
||||||
|
border-color: currentColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(inline) {
|
||||||
|
display: inline-block;
|
||||||
|
margin-bottom: 0;
|
||||||
|
margin-right: $-checkbox-margin;
|
||||||
|
|
||||||
|
@include when(last-child) {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(disabled) {
|
||||||
|
.wd-checkbox__shape {
|
||||||
|
border-color: $-checkbox-border-color;
|
||||||
|
background: $-checkbox-disabled-check-bg;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wd-checkbox__label {
|
||||||
|
color: $-checkbox-disabled-label-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(checked) {
|
||||||
|
.wd-checkbox__shape {
|
||||||
|
color: $-checkbox-disabled-check-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wd-checkbox__label {
|
||||||
|
color: $-checkbox-disabled-label-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(button) {
|
||||||
|
.wd-checkbox__label {
|
||||||
|
background: $-checkbox-disabled-color;
|
||||||
|
border-color: $-checkbox-button-border;
|
||||||
|
color: $-checkbox-disabled-label-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(checked) {
|
||||||
|
.wd-checkbox__label {
|
||||||
|
border-color: $-checkbox-button-disabled-border;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 以下内容用于解决父子组件样式隔离的问题 —— START
|
||||||
|
@include when(cell-box) {
|
||||||
|
padding: 13px 15px;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
@include when(large) {
|
||||||
|
padding: 14px 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(button-box) {
|
||||||
|
display: inline-flex;
|
||||||
|
width: 33.3333%;
|
||||||
|
padding: 12px 12px 0 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
.wd-checkbox__label {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child::after {
|
||||||
|
content: "";
|
||||||
|
display: table;
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(large) {
|
||||||
|
.wd-checkbox__shape {
|
||||||
|
width: $-checkbox-large-size;
|
||||||
|
height: $-checkbox-large-size;
|
||||||
|
font-size: $-checkbox-large-size;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wd-checkbox__label {
|
||||||
|
font-size: $-checkbox-large-label-fs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
68
node_modules/wot-design-uni/components/wd-checkbox/types.ts
generated
vendored
Normal file
68
node_modules/wot-design-uni/components/wd-checkbox/types.ts
generated
vendored
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import type { ComponentPublicInstance, ExtractPropTypes, PropType } from 'vue'
|
||||||
|
import { baseProps, makeStringProp } from '../common/props'
|
||||||
|
|
||||||
|
export type CheckShape = 'circle' | 'square' | 'button'
|
||||||
|
|
||||||
|
export const checkboxProps = {
|
||||||
|
...baseProps,
|
||||||
|
customLabelClass: makeStringProp(''),
|
||||||
|
customShapeClass: makeStringProp(''),
|
||||||
|
/**
|
||||||
|
* 单选框选中时的值
|
||||||
|
*/
|
||||||
|
modelValue: {
|
||||||
|
type: [String, Number, Boolean],
|
||||||
|
required: true,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 单选框形状,可选值:circle / square / button
|
||||||
|
*/
|
||||||
|
shape: {
|
||||||
|
type: String as PropType<CheckShape>
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 选中的颜色
|
||||||
|
*/
|
||||||
|
checkedColor: String,
|
||||||
|
/**
|
||||||
|
* 禁用
|
||||||
|
*/
|
||||||
|
disabled: {
|
||||||
|
type: [Boolean, null] as PropType<boolean | null>,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 选中值,在 checkbox-group 中使用无效,需同 false-value 一块使用
|
||||||
|
*/
|
||||||
|
trueValue: {
|
||||||
|
type: [String, Number, Boolean],
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 非选中时的值,在 checkbox-group 中使用无效,需同 true-value 一块使用
|
||||||
|
*/
|
||||||
|
falseValue: {
|
||||||
|
type: [String, Number, Boolean],
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 设置大小,可选值:large
|
||||||
|
*/
|
||||||
|
size: String,
|
||||||
|
/**
|
||||||
|
* 文字位置最大宽度
|
||||||
|
*/
|
||||||
|
maxWidth: String
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CheckboxProps = ExtractPropTypes<typeof checkboxProps>
|
||||||
|
|
||||||
|
export type CheckboxExpose = {
|
||||||
|
/**
|
||||||
|
* 切换当前选中状态
|
||||||
|
*/
|
||||||
|
toggle: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CheckboxInstance = ComponentPublicInstance<CheckboxProps, CheckboxExpose>
|
||||||
177
node_modules/wot-design-uni/components/wd-checkbox/wd-checkbox.vue
generated
vendored
Normal file
177
node_modules/wot-design-uni/components/wd-checkbox/wd-checkbox.vue
generated
vendored
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
<template>
|
||||||
|
<view
|
||||||
|
:class="`wd-checkbox ${innerCell ? 'is-cell-box' : ''} ${innerShape === 'button' ? 'is-button-box' : ''} ${isChecked ? 'is-checked' : ''} ${
|
||||||
|
isFirst ? 'is-first-child' : ''
|
||||||
|
} ${isLast ? 'is-last-child' : ''} ${innerInline ? 'is-inline' : ''} ${innerShape === 'button' ? 'is-button' : ''} ${
|
||||||
|
innerDisabled ? 'is-disabled' : ''
|
||||||
|
} ${innerSize ? 'is-' + innerSize : ''} ${customClass}`"
|
||||||
|
:style="customStyle"
|
||||||
|
@click="toggle"
|
||||||
|
>
|
||||||
|
<!--shape为button时,移除wd-checkbox__shape,只保留wd-checkbox__label-->
|
||||||
|
<view
|
||||||
|
v-if="innerShape !== 'button'"
|
||||||
|
:class="`wd-checkbox__shape ${innerShape === 'square' ? 'is-square' : ''} ${customShapeClass}`"
|
||||||
|
:style="isChecked && !innerDisabled && innerCheckedColor ? 'color :' + innerCheckedColor : ''"
|
||||||
|
>
|
||||||
|
<wd-icon custom-class="wd-checkbox__check" name="check-bold" />
|
||||||
|
</view>
|
||||||
|
<!--shape为button时只保留wd-checkbox__label-->
|
||||||
|
<view
|
||||||
|
:class="`wd-checkbox__label ${customLabelClass}`"
|
||||||
|
:style="isChecked && innerShape === 'button' && !innerDisabled && innerCheckedColor ? 'color:' + innerCheckedColor : ''"
|
||||||
|
>
|
||||||
|
<!--button选中时展示的icon-->
|
||||||
|
<wd-icon v-if="innerShape === 'button' && isChecked" custom-class="wd-checkbox__btn-check" name="check-bold" />
|
||||||
|
<!--文案-->
|
||||||
|
<view class="wd-checkbox__txt" :style="maxWidth ? 'max-width:' + maxWidth : ''">
|
||||||
|
<slot></slot>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
name: 'wd-checkbox',
|
||||||
|
options: {
|
||||||
|
addGlobalClass: true,
|
||||||
|
virtualHost: true,
|
||||||
|
styleIsolation: 'shared'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import wdIcon from '../wd-icon/wd-icon.vue'
|
||||||
|
import { computed, getCurrentInstance, onBeforeMount, watch } from 'vue'
|
||||||
|
import { useParent } from '../composables/useParent'
|
||||||
|
import { CHECKBOX_GROUP_KEY } from '../wd-checkbox-group/types'
|
||||||
|
import { getPropByPath, isDef } from '../common/util'
|
||||||
|
import { checkboxProps, type CheckboxExpose } from './types'
|
||||||
|
|
||||||
|
const props = defineProps(checkboxProps)
|
||||||
|
const emit = defineEmits(['change', 'update:modelValue'])
|
||||||
|
|
||||||
|
defineExpose<CheckboxExpose>({
|
||||||
|
toggle
|
||||||
|
})
|
||||||
|
|
||||||
|
const { parent: checkboxGroup, index } = useParent(CHECKBOX_GROUP_KEY)
|
||||||
|
|
||||||
|
const isChecked = computed(() => {
|
||||||
|
if (checkboxGroup) {
|
||||||
|
return checkboxGroup.props.modelValue.indexOf(props.modelValue) > -1
|
||||||
|
} else {
|
||||||
|
return props.modelValue === props.trueValue
|
||||||
|
}
|
||||||
|
}) // 是否被选中
|
||||||
|
|
||||||
|
const isFirst = computed(() => {
|
||||||
|
return index.value === 0
|
||||||
|
})
|
||||||
|
|
||||||
|
const isLast = computed(() => {
|
||||||
|
const children = isDef(checkboxGroup) ? checkboxGroup.children : []
|
||||||
|
return index.value === children.length - 1
|
||||||
|
})
|
||||||
|
const { proxy } = getCurrentInstance() as any
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.modelValue,
|
||||||
|
() => {
|
||||||
|
// 组合使用走这个逻辑
|
||||||
|
if (checkboxGroup) {
|
||||||
|
checkName()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.shape,
|
||||||
|
(newValue) => {
|
||||||
|
const type = ['circle', 'square', 'button']
|
||||||
|
if (isDef(newValue) && type.indexOf(newValue) === -1) console.error(`shape must be one of ${type.toString()}`)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const innerShape = computed(() => {
|
||||||
|
return props.shape || getPropByPath(checkboxGroup, 'props.shape') || 'circle'
|
||||||
|
})
|
||||||
|
|
||||||
|
const innerCheckedColor = computed(() => {
|
||||||
|
return props.checkedColor || getPropByPath(checkboxGroup, 'props.checkedColor')
|
||||||
|
})
|
||||||
|
|
||||||
|
const innerDisabled = computed(() => {
|
||||||
|
if (!checkboxGroup) {
|
||||||
|
return props.disabled
|
||||||
|
}
|
||||||
|
const { max, min, modelValue, disabled } = checkboxGroup.props
|
||||||
|
if (
|
||||||
|
(max && modelValue.length >= max && !isChecked.value) ||
|
||||||
|
(min && modelValue.length <= min && isChecked.value) ||
|
||||||
|
props.disabled === true ||
|
||||||
|
(disabled && props.disabled === null)
|
||||||
|
) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return props.disabled
|
||||||
|
})
|
||||||
|
|
||||||
|
const innerInline = computed(() => {
|
||||||
|
return getPropByPath(checkboxGroup, 'props.inline') || false
|
||||||
|
})
|
||||||
|
|
||||||
|
const innerCell = computed(() => {
|
||||||
|
return getPropByPath(checkboxGroup, 'props.cell') || false
|
||||||
|
})
|
||||||
|
|
||||||
|
const innerSize = computed(() => {
|
||||||
|
return props.size || getPropByPath(checkboxGroup, 'props.size')
|
||||||
|
})
|
||||||
|
|
||||||
|
onBeforeMount(() => {
|
||||||
|
// eslint-disable-next-line quotes
|
||||||
|
if (props.modelValue === null) console.error("checkbox's value must be set")
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 检测checkbox绑定的value是否和其它checkbox的value冲突
|
||||||
|
* @param {Object} self 自身
|
||||||
|
* @param myName 自己的标识符
|
||||||
|
*/
|
||||||
|
function checkName() {
|
||||||
|
checkboxGroup &&
|
||||||
|
checkboxGroup.children &&
|
||||||
|
checkboxGroup.children.forEach((child: any) => {
|
||||||
|
if (child.$.uid !== proxy.$.uid && child.modelValue === props.modelValue) {
|
||||||
|
console.error(`The checkbox's bound value: ${props.modelValue} has been used`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @description 点击checkbox的Event handle
|
||||||
|
*/
|
||||||
|
function toggle() {
|
||||||
|
if (innerDisabled.value) return
|
||||||
|
// 复选框单独使用时点击反选,并且在checkbox上触发change事件
|
||||||
|
if (checkboxGroup) {
|
||||||
|
emit('change', {
|
||||||
|
value: !isChecked.value
|
||||||
|
})
|
||||||
|
checkboxGroup.changeSelectState(props.modelValue)
|
||||||
|
} else {
|
||||||
|
const newVal = props.modelValue === props.trueValue ? props.falseValue : props.trueValue
|
||||||
|
emit('update:modelValue', newVal)
|
||||||
|
emit('change', {
|
||||||
|
value: newVal
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import './index.scss';
|
||||||
|
</style>
|
||||||
18
node_modules/wot-design-uni/components/wd-circle/index.scss
generated
vendored
Normal file
18
node_modules/wot-design-uni/components/wd-circle/index.scss
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
@import "../common/abstracts/variable.scss";
|
||||||
|
@import "../common/abstracts/_mixin.scss";
|
||||||
|
|
||||||
|
@include b(circle) {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
@include e(text) {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1;
|
||||||
|
top: 50%;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
color: $-circle-text-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
54
node_modules/wot-design-uni/components/wd-circle/types.ts
generated
vendored
Normal file
54
node_modules/wot-design-uni/components/wd-circle/types.ts
generated
vendored
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import type { ExtractPropTypes, PropType } from 'vue'
|
||||||
|
import { baseProps, makeBooleanProp, makeNumberProp, makeStringProp } from '../common/props'
|
||||||
|
|
||||||
|
// 进度条端点的形状,可选值为 "butt" | "round" | "square"
|
||||||
|
export type StrokeLinecapType = 'butt' | 'round' | 'square'
|
||||||
|
|
||||||
|
export const circleProps = {
|
||||||
|
...baseProps,
|
||||||
|
/**
|
||||||
|
* 当前进度
|
||||||
|
*/
|
||||||
|
modelValue: makeNumberProp(0),
|
||||||
|
/**
|
||||||
|
* 圆环直径,默认单位为 px
|
||||||
|
*/
|
||||||
|
size: makeNumberProp(100),
|
||||||
|
/**
|
||||||
|
* 进度条颜色,传入对象格式可以定义渐变色
|
||||||
|
*/
|
||||||
|
color: {
|
||||||
|
type: [String, Object] as PropType<string | Record<string, string>>,
|
||||||
|
default: '#4d80f0'
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 轨道颜色
|
||||||
|
*/
|
||||||
|
layerColor: makeStringProp('#EBEEF5'),
|
||||||
|
/**
|
||||||
|
* 填充颜色
|
||||||
|
*/
|
||||||
|
fill: String,
|
||||||
|
/**
|
||||||
|
* 动画速度(单位为 rate/s)
|
||||||
|
*/
|
||||||
|
speed: makeNumberProp(50),
|
||||||
|
/**
|
||||||
|
* 文字
|
||||||
|
*/
|
||||||
|
text: String,
|
||||||
|
/**
|
||||||
|
* 进度条宽度 单位px
|
||||||
|
*/
|
||||||
|
strokeWidth: makeNumberProp(10),
|
||||||
|
/**
|
||||||
|
* 进度条端点的形状,可选值为 "butt" | "round" | "square"
|
||||||
|
*/
|
||||||
|
strokeLinecap: makeStringProp<StrokeLinecapType>('round'),
|
||||||
|
/**
|
||||||
|
* 是否顺时针增加
|
||||||
|
*/
|
||||||
|
clockwise: makeBooleanProp(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CircleProps = ExtractPropTypes<typeof circleProps>
|
||||||
296
node_modules/wot-design-uni/components/wd-circle/wd-circle.vue
generated
vendored
Normal file
296
node_modules/wot-design-uni/components/wd-circle/wd-circle.vue
generated
vendored
Normal file
@@ -0,0 +1,296 @@
|
|||||||
|
<template>
|
||||||
|
<view :class="`wd-circle ${customClass}`" :style="customStyle">
|
||||||
|
<!-- #ifdef MP-WEIXIN -->
|
||||||
|
<canvas :style="canvasStyle" :id="canvasId" :canvas-id="canvasId" type="2d"></canvas>
|
||||||
|
<!-- #endif -->
|
||||||
|
<!-- #ifndef MP-WEIXIN -->
|
||||||
|
<canvas :width="canvasSize" :height="canvasSize" :style="canvasStyle" :id="canvasId" :canvas-id="canvasId"></canvas>
|
||||||
|
<!-- #endif -->
|
||||||
|
|
||||||
|
<view v-if="!text" class="wd-circle__text">
|
||||||
|
<!-- 自定义提示内容 -->
|
||||||
|
<slot></slot>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<text v-else class="wd-circle__text">
|
||||||
|
{{ text }}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
name: 'wd-circle',
|
||||||
|
options: {
|
||||||
|
addGlobalClass: true,
|
||||||
|
virtualHost: true,
|
||||||
|
styleIsolation: 'shared'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed, getCurrentInstance, onBeforeMount, onMounted, onUnmounted, ref, watch } from 'vue'
|
||||||
|
import { addUnit, isObj, objToStyle, uuid, getSystemInfo } from '../common/util'
|
||||||
|
import { circleProps } from './types'
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
import { canvas2dAdapter } from '../common/canvasHelper'
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// 大于等于0且小于等于100
|
||||||
|
function format(rate: number) {
|
||||||
|
return Math.min(Math.max(rate, 0), 100)
|
||||||
|
}
|
||||||
|
// 结束角度
|
||||||
|
const PERIMETER = 2 * Math.PI
|
||||||
|
// 开始角度
|
||||||
|
const BEGIN_ANGLE = -Math.PI / 2
|
||||||
|
const STEP = 1
|
||||||
|
|
||||||
|
const props = defineProps(circleProps)
|
||||||
|
const { proxy } = getCurrentInstance() as any
|
||||||
|
|
||||||
|
const progressColor = ref<string | CanvasGradient>('') // 进度条颜色
|
||||||
|
|
||||||
|
const currentValue = ref<number>(0) // 当前值
|
||||||
|
const interval = ref<any>(null) // 定时器
|
||||||
|
const pixelRatio = ref<number>(1) // 像素比
|
||||||
|
const canvasId = ref<string>(`wd-circle${uuid()}`) // canvasId
|
||||||
|
let ctx: UniApp.CanvasContext | null = null
|
||||||
|
|
||||||
|
// canvas渲染大小
|
||||||
|
const canvasSize = computed(() => {
|
||||||
|
let size = props.size
|
||||||
|
// #ifdef MP-ALIPAY
|
||||||
|
size = size * pixelRatio.value
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
return size
|
||||||
|
})
|
||||||
|
|
||||||
|
// 进度条宽度
|
||||||
|
const sWidth = computed(() => {
|
||||||
|
let sWidth = props.strokeWidth
|
||||||
|
// #ifdef MP-ALIPAY
|
||||||
|
sWidth = sWidth * pixelRatio.value
|
||||||
|
// #endif
|
||||||
|
return sWidth
|
||||||
|
})
|
||||||
|
|
||||||
|
// Circle 样式
|
||||||
|
const canvasStyle = computed(() => {
|
||||||
|
const style = {
|
||||||
|
width: addUnit(props.size),
|
||||||
|
height: addUnit(props.size)
|
||||||
|
}
|
||||||
|
return `${objToStyle(style)}`
|
||||||
|
})
|
||||||
|
|
||||||
|
// 监听目标数值变化
|
||||||
|
watch(
|
||||||
|
() => props.modelValue,
|
||||||
|
() => {
|
||||||
|
reRender()
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
// 监听Circle大小变化
|
||||||
|
watch(
|
||||||
|
() => props.size,
|
||||||
|
() => {
|
||||||
|
let timer = setTimeout(() => {
|
||||||
|
drawCircle(currentValue.value)
|
||||||
|
clearTimeout(timer)
|
||||||
|
}, 50)
|
||||||
|
},
|
||||||
|
{ immediate: false }
|
||||||
|
)
|
||||||
|
|
||||||
|
// 监听进度条颜色变化
|
||||||
|
watch(
|
||||||
|
() => props.color,
|
||||||
|
() => {
|
||||||
|
drawCircle(currentValue.value)
|
||||||
|
},
|
||||||
|
{ immediate: false, deep: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
onBeforeMount(() => {
|
||||||
|
pixelRatio.value = getSystemInfo().pixelRatio
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
currentValue.value = props.modelValue
|
||||||
|
drawCircle(currentValue.value)
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
clearTimeInterval()
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取canvas上下文
|
||||||
|
*/
|
||||||
|
function getContext() {
|
||||||
|
return new Promise<UniApp.CanvasContext>((resolve) => {
|
||||||
|
if (ctx) {
|
||||||
|
return resolve(ctx)
|
||||||
|
}
|
||||||
|
// #ifndef MP-WEIXIN
|
||||||
|
ctx = uni.createCanvasContext(canvasId.value, proxy)
|
||||||
|
resolve(ctx)
|
||||||
|
// #endif
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
uni
|
||||||
|
.createSelectorQuery()
|
||||||
|
.in(proxy)
|
||||||
|
.select(`#${canvasId.value}`)
|
||||||
|
.node((res) => {
|
||||||
|
if (res && res.node) {
|
||||||
|
const canvas = res.node
|
||||||
|
ctx = canvas2dAdapter(canvas.getContext('2d') as CanvasRenderingContext2D)
|
||||||
|
canvas.width = props.size * pixelRatio.value
|
||||||
|
canvas.height = props.size * pixelRatio.value
|
||||||
|
ctx.scale(pixelRatio.value, pixelRatio.value)
|
||||||
|
resolve(ctx)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.exec()
|
||||||
|
// #endif
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置canvas
|
||||||
|
*/
|
||||||
|
function presetCanvas(context: any, strokeStyle: string | CanvasGradient, beginAngle: number, endAngle: number, fill?: string) {
|
||||||
|
let width = sWidth.value
|
||||||
|
const position = canvasSize.value / 2
|
||||||
|
if (!fill) {
|
||||||
|
width = width / 2
|
||||||
|
}
|
||||||
|
const radius = position - width / 2
|
||||||
|
context.strokeStyle = strokeStyle
|
||||||
|
context.setStrokeStyle(strokeStyle)
|
||||||
|
context.setLineWidth(width)
|
||||||
|
context.setLineCap(props.strokeLinecap)
|
||||||
|
|
||||||
|
context.beginPath()
|
||||||
|
context.arc(position, position, radius, beginAngle, endAngle, !props.clockwise)
|
||||||
|
context.stroke()
|
||||||
|
if (fill) {
|
||||||
|
context.setLineWidth(width)
|
||||||
|
context.setFillStyle(fill)
|
||||||
|
context.fill()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 渲染管道
|
||||||
|
*/
|
||||||
|
function renderLayerCircle(context: UniApp.CanvasContext) {
|
||||||
|
presetCanvas(context, props.layerColor, 0, PERIMETER, props.fill)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 渲染进度条
|
||||||
|
*/
|
||||||
|
function renderHoverCircle(context: UniApp.CanvasContext, formatValue: number) {
|
||||||
|
// 结束角度
|
||||||
|
const progress = PERIMETER * (formatValue / 100)
|
||||||
|
const endAngle = props.clockwise ? BEGIN_ANGLE + progress : 3 * Math.PI - (BEGIN_ANGLE + progress)
|
||||||
|
|
||||||
|
// 设置进度条颜色
|
||||||
|
if (isObj(props.color)) {
|
||||||
|
const LinearColor = context.createLinearGradient(canvasSize.value, 0, 0, 0)
|
||||||
|
Object.keys(props.color)
|
||||||
|
.sort((a, b) => parseFloat(a) - parseFloat(b))
|
||||||
|
.map((key) => LinearColor.addColorStop(parseFloat(key) / 100, (props.color as Record<string, any>)[key]))
|
||||||
|
progressColor.value = LinearColor
|
||||||
|
} else {
|
||||||
|
progressColor.value = props.color
|
||||||
|
}
|
||||||
|
presetCanvas(context, progressColor.value, BEGIN_ANGLE, endAngle)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 渲染圆点
|
||||||
|
* 进度值为0时渲染一个圆点
|
||||||
|
*/
|
||||||
|
function renderDot(context: UniApp.CanvasContext) {
|
||||||
|
const strokeWidth = sWidth.value // 管道宽度=小圆点直径
|
||||||
|
const position = canvasSize.value / 2 // 坐标
|
||||||
|
// 设置进度条颜色
|
||||||
|
if (isObj(props.color)) {
|
||||||
|
const LinearColor = context.createLinearGradient(canvasSize.value, 0, 0, 0)
|
||||||
|
Object.keys(props.color)
|
||||||
|
.sort((a, b) => parseFloat(a) - parseFloat(b))
|
||||||
|
.map((key) => LinearColor.addColorStop(parseFloat(key) / 100, (props.color as Record<string, any>)[key]))
|
||||||
|
progressColor.value = LinearColor
|
||||||
|
} else {
|
||||||
|
progressColor.value = props.color
|
||||||
|
}
|
||||||
|
context.beginPath()
|
||||||
|
context.arc(position, strokeWidth / 4, strokeWidth / 4, 0, PERIMETER)
|
||||||
|
context.setFillStyle(progressColor.value)
|
||||||
|
context.fill()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 画圆
|
||||||
|
*/
|
||||||
|
function drawCircle(currentValue: number) {
|
||||||
|
getContext().then((context) => {
|
||||||
|
context.clearRect(0, 0, canvasSize.value, canvasSize.value)
|
||||||
|
renderLayerCircle(context)
|
||||||
|
|
||||||
|
const formatValue = format(currentValue)
|
||||||
|
if (formatValue !== 0) {
|
||||||
|
renderHoverCircle(context, formatValue)
|
||||||
|
} else {
|
||||||
|
renderDot(context)
|
||||||
|
}
|
||||||
|
context.draw()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Circle组件渲染
|
||||||
|
* 当前进度值变化时重新渲染Circle组件
|
||||||
|
*/
|
||||||
|
function reRender() {
|
||||||
|
// 动画通过定时器渲染
|
||||||
|
if (props.speed <= 0 || props.speed > 1000) {
|
||||||
|
drawCircle(props.modelValue)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
clearTimeInterval()
|
||||||
|
currentValue.value = currentValue.value || 0
|
||||||
|
const run = () => {
|
||||||
|
interval.value = setTimeout(() => {
|
||||||
|
if (currentValue.value !== props.modelValue) {
|
||||||
|
if (Math.abs(currentValue.value - props.modelValue) < STEP) {
|
||||||
|
currentValue.value = props.modelValue
|
||||||
|
} else if (currentValue.value < props.modelValue) {
|
||||||
|
currentValue.value += STEP
|
||||||
|
} else {
|
||||||
|
currentValue.value -= STEP
|
||||||
|
}
|
||||||
|
drawCircle(currentValue.value)
|
||||||
|
run()
|
||||||
|
} else {
|
||||||
|
clearTimeInterval()
|
||||||
|
}
|
||||||
|
}, 1000 / props.speed)
|
||||||
|
}
|
||||||
|
run()
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 清除定时器
|
||||||
|
*/
|
||||||
|
function clearTimeInterval() {
|
||||||
|
interval.value && clearTimeout(interval.value)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import './index.scss';
|
||||||
|
</style>
|
||||||
168
node_modules/wot-design-uni/components/wd-col-picker/index.scss
generated
vendored
Normal file
168
node_modules/wot-design-uni/components/wd-col-picker/index.scss
generated
vendored
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
@import '../common/abstracts/variable';
|
||||||
|
@import '../common/abstracts/mixin';
|
||||||
|
|
||||||
|
.wot-theme-dark {
|
||||||
|
@include b(col-picker) {
|
||||||
|
@include e(list-item) {
|
||||||
|
@include when(disabled) {
|
||||||
|
color: $-dark-color3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(list-item-tip) {
|
||||||
|
color: $-dark-color-gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.wd-col-picker__arrow) {
|
||||||
|
color: $-dark-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(list) {
|
||||||
|
color: $-dark-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(selected) {
|
||||||
|
color: $-dark-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.wd-col-picker__cell--placeholder) {
|
||||||
|
.wd-cell__value {
|
||||||
|
color: $-dark-color-gray;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include b(col-picker) {
|
||||||
|
@include edeep(cell) {
|
||||||
|
@include when(disabled) {
|
||||||
|
.wd-cell__value {
|
||||||
|
color: $-input-disabled-color;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@include when(error) {
|
||||||
|
.wd-cell__value {
|
||||||
|
color: $-input-error-color;
|
||||||
|
}
|
||||||
|
.wd-col-picker__arrow {
|
||||||
|
color: $-input-error-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@include when(large) {
|
||||||
|
.wd-col-picker__arrow {
|
||||||
|
font-size: $-cell-icon-size-large;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include m(placeholder) {
|
||||||
|
.wd-cell__value {
|
||||||
|
color: $-input-placeholder-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@include edeep(arrow) {
|
||||||
|
display: block;
|
||||||
|
font-size: $-cell-icon-size;
|
||||||
|
color: $-cell-arrow-color;
|
||||||
|
line-height: $-cell-line-height;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(selected) {
|
||||||
|
height: $-col-picker-selected-height;
|
||||||
|
font-size: $-col-picker-selected-fs;
|
||||||
|
color: $-col-picker-selected-color;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(selected-container){
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(selected-item) {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
height: $-col-picker-selected-height;
|
||||||
|
line-height: $-col-picker-selected-height;
|
||||||
|
padding: $-col-picker-selected-padding;
|
||||||
|
|
||||||
|
@include when(selected) {
|
||||||
|
font-weight: $-col-picker-selected-fw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(selected-line) {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 5px;
|
||||||
|
width: $-col-picker-line-width;
|
||||||
|
left: 0;
|
||||||
|
height: $-col-picker-line-height;
|
||||||
|
background: $-col-picker-line-color;
|
||||||
|
z-index: 1;
|
||||||
|
border-radius: calc($-col-picker-line-height / 2);
|
||||||
|
box-shadow: $-col-picker-line-box-shadow;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(list-container){
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(list) {
|
||||||
|
height: $-col-picker-list-height;
|
||||||
|
padding-bottom: $-col-picker-list-padding-bottom;
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow: auto;
|
||||||
|
color: $-col-picker-list-color;
|
||||||
|
font-size: $-col-picker-list-fs;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(list-item) {
|
||||||
|
display: flex;
|
||||||
|
padding: $-col-picker-list-item-padding;
|
||||||
|
align-items: flex-start;
|
||||||
|
|
||||||
|
@include when(selected) {
|
||||||
|
color: $-col-picker-list-color-checked;
|
||||||
|
|
||||||
|
:deep(.wd-col-picker__checked) {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@include when(disabled) {
|
||||||
|
color: $-col-picker-list-color-disabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(list-item-label) {
|
||||||
|
line-height: 1.285;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(list-item-tip) {
|
||||||
|
margin-top: 2px;
|
||||||
|
font-size: $-col-picker-list-fs-tip;
|
||||||
|
color: $-col-picker-list-color-tip;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include edeep(checked) {
|
||||||
|
display: block;
|
||||||
|
margin-left: 4px;
|
||||||
|
font-size: $-col-picker-list-checked-icon-size;
|
||||||
|
color: $-col-picker-list-color-checked;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(loading) {
|
||||||
|
display: flex;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
166
node_modules/wot-design-uni/components/wd-col-picker/types.ts
generated
vendored
Normal file
166
node_modules/wot-design-uni/components/wd-col-picker/types.ts
generated
vendored
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
import type { ComponentPublicInstance, ExtractPropTypes, PropType } from 'vue'
|
||||||
|
import { baseProps, makeArrayProp, makeBooleanProp, makeNumberProp, makeRequiredProp, makeStringProp, numericProp } from '../common/props'
|
||||||
|
import type { FormItemRule } from '../wd-form/types'
|
||||||
|
|
||||||
|
export const colPickerProps = {
|
||||||
|
...baseProps,
|
||||||
|
/**
|
||||||
|
* 选中项
|
||||||
|
*/
|
||||||
|
modelValue: makeRequiredProp(Array as PropType<Array<string | number>>),
|
||||||
|
/**
|
||||||
|
* 选择器数据,二维数组
|
||||||
|
*/
|
||||||
|
columns: makeArrayProp<Record<string, any>[]>(),
|
||||||
|
/**
|
||||||
|
* 选择器左侧文案
|
||||||
|
*/
|
||||||
|
label: String,
|
||||||
|
/**
|
||||||
|
* 设置左侧标题宽度
|
||||||
|
*/
|
||||||
|
labelWidth: makeStringProp('33%'),
|
||||||
|
/**
|
||||||
|
* 使用 label 插槽时设置该选项
|
||||||
|
*/
|
||||||
|
useLabelSlot: makeBooleanProp(false),
|
||||||
|
/**
|
||||||
|
* 使用默认插槽时设置该选项
|
||||||
|
*/
|
||||||
|
useDefaultSlot: makeBooleanProp(false),
|
||||||
|
/**
|
||||||
|
* 禁用
|
||||||
|
*/
|
||||||
|
disabled: makeBooleanProp(false),
|
||||||
|
/**
|
||||||
|
* 只读
|
||||||
|
*/
|
||||||
|
readonly: makeBooleanProp(false),
|
||||||
|
/**
|
||||||
|
* 选择器占位符
|
||||||
|
*/
|
||||||
|
placeholder: String,
|
||||||
|
/**
|
||||||
|
* 弹出层标题
|
||||||
|
*/
|
||||||
|
title: String,
|
||||||
|
/**
|
||||||
|
* 接收当前列的选中项 item、当前列下标、当前列选中项下标下一列数据处理函数 resolve、结束选择 finish
|
||||||
|
*/
|
||||||
|
columnChange: Function as PropType<ColPickerColumnChange>,
|
||||||
|
/**
|
||||||
|
* 自定义展示文案的格式化函数,返回一个字符串
|
||||||
|
*/
|
||||||
|
displayFormat: Function as PropType<ColPickerDisplayFormat>,
|
||||||
|
/**
|
||||||
|
* 确定前校验函数,接收 (value, resolve) 参数,通过 resolve 继续执行 picker,resolve 接收 1 个 boolean 参数
|
||||||
|
*/
|
||||||
|
beforeConfirm: Function as PropType<ColPickerBeforeConfirm>,
|
||||||
|
/**
|
||||||
|
* 选择器的值靠右展示
|
||||||
|
*/
|
||||||
|
alignRight: makeBooleanProp(false),
|
||||||
|
/**
|
||||||
|
* 是否为错误状态,错误状态时右侧内容为红色
|
||||||
|
*/
|
||||||
|
error: makeBooleanProp(false),
|
||||||
|
/**
|
||||||
|
* 是否必填
|
||||||
|
*/
|
||||||
|
required: makeBooleanProp(false),
|
||||||
|
/**
|
||||||
|
* 设置选择器大小,可选值:large
|
||||||
|
*/
|
||||||
|
size: String,
|
||||||
|
/**
|
||||||
|
* 选项对象中,value 对应的 key
|
||||||
|
*/
|
||||||
|
valueKey: makeStringProp('value'),
|
||||||
|
/**
|
||||||
|
* 选项对象中,展示的文本对应的 key
|
||||||
|
*/
|
||||||
|
labelKey: makeStringProp('label'),
|
||||||
|
/**
|
||||||
|
* 选项对象中,提示文案对应的 key
|
||||||
|
*/
|
||||||
|
tipKey: makeStringProp('tip'),
|
||||||
|
/**
|
||||||
|
* loading 图标的颜色
|
||||||
|
*/
|
||||||
|
loadingColor: makeStringProp('#4D80F0'),
|
||||||
|
/**
|
||||||
|
* 点击遮罩是否关闭
|
||||||
|
*/
|
||||||
|
closeOnClickModal: makeBooleanProp(true),
|
||||||
|
/**
|
||||||
|
* 自动触发 column-change 事件来补全数据,当 columns 为空数组或者 columns 数组长度小于 value 数组长度时,会自动触发 column-change
|
||||||
|
*/
|
||||||
|
autoComplete: makeBooleanProp(false),
|
||||||
|
/**
|
||||||
|
* 弹窗层级
|
||||||
|
*/
|
||||||
|
zIndex: makeNumberProp(15),
|
||||||
|
/**
|
||||||
|
* 弹出面板是否设置底部安全距离(iphone X 类型的机型)
|
||||||
|
*/
|
||||||
|
safeAreaInsetBottom: makeBooleanProp(true),
|
||||||
|
/**
|
||||||
|
* 是否超出隐藏
|
||||||
|
*/
|
||||||
|
ellipsis: makeBooleanProp(false),
|
||||||
|
/**
|
||||||
|
* 表单域 model 字段名,在使用表单校验功能的情况下,该属性是必填的
|
||||||
|
*/
|
||||||
|
prop: String,
|
||||||
|
/**
|
||||||
|
* 表单验证规则,结合wd-form组件使用
|
||||||
|
*/
|
||||||
|
rules: makeArrayProp<FormItemRule>(),
|
||||||
|
/**
|
||||||
|
* 底部条宽度,单位像素
|
||||||
|
*/
|
||||||
|
lineWidth: numericProp,
|
||||||
|
/**
|
||||||
|
* 底部条高度,单位像素
|
||||||
|
*/
|
||||||
|
lineHeight: numericProp,
|
||||||
|
/**
|
||||||
|
* label 外部自定义样式
|
||||||
|
*/
|
||||||
|
customViewClass: makeStringProp(''),
|
||||||
|
/**
|
||||||
|
* value 外部自定义样式
|
||||||
|
*/
|
||||||
|
customLabelClass: makeStringProp(''),
|
||||||
|
customValueClass: makeStringProp(''),
|
||||||
|
/**
|
||||||
|
* 是否从页面中脱离出来,用于解决各种 fixed 失效问题 (H5: teleport, APP: renderjs, 小程序: root-portal)
|
||||||
|
*/
|
||||||
|
rootPortal: makeBooleanProp(false),
|
||||||
|
/**
|
||||||
|
* 必填标记位置,可选值:before、after
|
||||||
|
*/
|
||||||
|
markerSide: makeStringProp<'before' | 'after'>('before')
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ColPickerProps = ExtractPropTypes<typeof colPickerProps>
|
||||||
|
|
||||||
|
export type ColPickerColumnChangeOption = {
|
||||||
|
selectedItem: Record<string, any>
|
||||||
|
index: number
|
||||||
|
rowIndex: number
|
||||||
|
resolve: (nextColumn: Record<string, any>[]) => void
|
||||||
|
finish: (isOk?: boolean) => void
|
||||||
|
}
|
||||||
|
export type ColPickerColumnChange = (option: ColPickerColumnChangeOption) => void
|
||||||
|
export type ColPickerDisplayFormat = (selectedItems: Record<string, any>[]) => string
|
||||||
|
export type ColPickerBeforeConfirm = (value: (string | number)[], selectedItems: Record<string, any>[], resolve: (isPass: boolean) => void) => void
|
||||||
|
|
||||||
|
export type ColPickerExpose = {
|
||||||
|
// 关闭picker弹框
|
||||||
|
close: () => void
|
||||||
|
// 打开picker弹框
|
||||||
|
open: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ColPickerInstance = ComponentPublicInstance<ColPickerExpose, ColPickerProps>
|
||||||
498
node_modules/wot-design-uni/components/wd-col-picker/wd-col-picker.vue
generated
vendored
Normal file
498
node_modules/wot-design-uni/components/wd-col-picker/wd-col-picker.vue
generated
vendored
Normal file
@@ -0,0 +1,498 @@
|
|||||||
|
<template>
|
||||||
|
<view :class="`wd-col-picker ${customClass}`" :style="customStyle">
|
||||||
|
<wd-cell
|
||||||
|
v-if="!$slots.default"
|
||||||
|
:title="label"
|
||||||
|
:value="showValue || placeholder || translate('placeholder')"
|
||||||
|
:required="required"
|
||||||
|
:size="size"
|
||||||
|
:title-width="labelWidth"
|
||||||
|
:prop="prop"
|
||||||
|
:rules="rules"
|
||||||
|
:clickable="!disabled && !readonly"
|
||||||
|
:value-align="alignRight ? 'right' : 'left'"
|
||||||
|
:custom-class="cellClass"
|
||||||
|
:custom-style="customStyle"
|
||||||
|
:custom-title-class="customLabelClass"
|
||||||
|
:custom-value-class="customValueClass"
|
||||||
|
:ellipsis="ellipsis"
|
||||||
|
:use-title-slot="!!$slots.label"
|
||||||
|
:marker-side="markerSide"
|
||||||
|
@click="showPicker"
|
||||||
|
>
|
||||||
|
<template v-if="$slots.label" #title>
|
||||||
|
<slot name="label"></slot>
|
||||||
|
</template>
|
||||||
|
<template #right-icon>
|
||||||
|
<wd-icon v-if="showArrow" custom-class="wd-col-picker__arrow" name="arrow-right" />
|
||||||
|
</template>
|
||||||
|
</wd-cell>
|
||||||
|
<view v-else @click="showPicker">
|
||||||
|
<slot></slot>
|
||||||
|
</view>
|
||||||
|
<wd-action-sheet
|
||||||
|
v-model="pickerShow"
|
||||||
|
:duration="250"
|
||||||
|
:title="title || translate('title')"
|
||||||
|
:close-on-click-modal="closeOnClickModal"
|
||||||
|
:z-index="zIndex"
|
||||||
|
:safe-area-inset-bottom="safeAreaInsetBottom"
|
||||||
|
:root-portal="rootPortal"
|
||||||
|
@open="handlePickerOpend"
|
||||||
|
@close="handlePickerClose"
|
||||||
|
@closed="handlePickerClosed"
|
||||||
|
>
|
||||||
|
<view class="wd-col-picker__selected">
|
||||||
|
<scroll-view :scroll-x="true" scroll-with-animation :scroll-left="scrollLeft">
|
||||||
|
<view class="wd-col-picker__selected-container">
|
||||||
|
<view
|
||||||
|
v-for="(_, colIndex) in selectList"
|
||||||
|
:key="colIndex"
|
||||||
|
:class="`wd-col-picker__selected-item ${colIndex === currentCol && 'is-selected'}`"
|
||||||
|
@click="handleColClick(colIndex)"
|
||||||
|
>
|
||||||
|
{{ selectShowList[colIndex] || translate('select') }}
|
||||||
|
</view>
|
||||||
|
<view class="wd-col-picker__selected-line" :style="state.lineStyle"></view>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
</view>
|
||||||
|
<view class="wd-col-picker__list-container">
|
||||||
|
<view
|
||||||
|
v-for="(col, colIndex) in selectList"
|
||||||
|
:key="colIndex"
|
||||||
|
class="wd-col-picker__list"
|
||||||
|
:style="colIndex === currentCol ? 'display: block;' : 'display: none;'"
|
||||||
|
>
|
||||||
|
<view
|
||||||
|
v-for="(item, index) in col"
|
||||||
|
:key="index"
|
||||||
|
:class="`wd-col-picker__list-item ${pickerColSelected[colIndex] && item[valueKey] === pickerColSelected[colIndex] && 'is-selected'} ${
|
||||||
|
item.disabled && 'is-disabled'
|
||||||
|
}`"
|
||||||
|
@click="chooseItem(colIndex, index)"
|
||||||
|
>
|
||||||
|
<view>
|
||||||
|
<view class="wd-col-picker__list-item-label">{{ item[labelKey] }}</view>
|
||||||
|
<view v-if="item[tipKey]" class="wd-col-picker__list-item-tip">{{ item[tipKey] }}</view>
|
||||||
|
</view>
|
||||||
|
<wd-icon custom-class="wd-col-picker__checked" name="check"></wd-icon>
|
||||||
|
</view>
|
||||||
|
<view v-if="loading" class="wd-col-picker__loading">
|
||||||
|
<wd-loading :color="loadingColor" />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</wd-action-sheet>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
name: 'wd-col-picker',
|
||||||
|
options: {
|
||||||
|
addGlobalClass: true,
|
||||||
|
virtualHost: true,
|
||||||
|
styleIsolation: 'shared'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import wdIcon from '../wd-icon/wd-icon.vue'
|
||||||
|
import wdLoading from '../wd-loading/wd-loading.vue'
|
||||||
|
import wdActionSheet from '../wd-action-sheet/wd-action-sheet.vue'
|
||||||
|
import wdCell from '../wd-cell/wd-cell.vue'
|
||||||
|
import { computed, getCurrentInstance, onMounted, ref, watch, type CSSProperties, reactive } from 'vue'
|
||||||
|
import { addUnit, debounce, getRect, isArray, isBoolean, isDef, isFunction, objToStyle } from '../common/util'
|
||||||
|
import { useTranslate } from '../composables/useTranslate'
|
||||||
|
import { colPickerProps, type ColPickerExpose } from './types'
|
||||||
|
|
||||||
|
const { translate } = useTranslate('col-picker')
|
||||||
|
|
||||||
|
const $container = '.wd-col-picker__selected-container'
|
||||||
|
const $item = '.wd-col-picker__selected-item'
|
||||||
|
|
||||||
|
const props = defineProps(colPickerProps)
|
||||||
|
const emit = defineEmits(['close', 'update:modelValue', 'confirm'])
|
||||||
|
|
||||||
|
const pickerShow = ref<boolean>(false)
|
||||||
|
const currentCol = ref<number>(0)
|
||||||
|
const selectList = ref<Record<string, any>[][]>([])
|
||||||
|
const pickerColSelected = ref<(string | number)[]>([])
|
||||||
|
const selectShowList = ref<Record<string, any>[]>([])
|
||||||
|
const loading = ref<boolean>(false)
|
||||||
|
const isChange = ref<boolean>(false)
|
||||||
|
const lastSelectList = ref<Record<string, any>[][]>([])
|
||||||
|
const lastPickerColSelected = ref<(string | number)[]>([])
|
||||||
|
const scrollLeft = ref<number>(0)
|
||||||
|
const inited = ref<boolean>(false)
|
||||||
|
const isCompleting = ref<boolean>(false)
|
||||||
|
|
||||||
|
const state = reactive({
|
||||||
|
lineStyle: 'display:none;' // 激活项边框线样式
|
||||||
|
})
|
||||||
|
|
||||||
|
const { proxy } = getCurrentInstance() as any
|
||||||
|
|
||||||
|
const updateLineAndScroll = debounce(function (animation = true) {
|
||||||
|
setLineStyle(animation)
|
||||||
|
lineScrollIntoView()
|
||||||
|
}, 50)
|
||||||
|
|
||||||
|
const showValue = computed(() => {
|
||||||
|
const selectedItems = (props.modelValue || []).map((item, colIndex) => {
|
||||||
|
return getSelectedItem(item, colIndex, selectList.value)
|
||||||
|
})
|
||||||
|
|
||||||
|
if (props.displayFormat) {
|
||||||
|
return props.displayFormat(selectedItems)
|
||||||
|
} else {
|
||||||
|
return selectedItems
|
||||||
|
.map((item) => {
|
||||||
|
return item[props.labelKey]
|
||||||
|
})
|
||||||
|
.join('')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const cellClass = computed(() => {
|
||||||
|
const classes = ['wd-col-picker__cell']
|
||||||
|
if (props.disabled) classes.push('is-disabled')
|
||||||
|
if (props.readonly) classes.push('is-readonly')
|
||||||
|
if (props.error) classes.push('is-error')
|
||||||
|
if (!showValue.value) classes.push('wd-col-picker__cell--placeholder')
|
||||||
|
return classes.join(' ')
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.modelValue,
|
||||||
|
(newValue) => {
|
||||||
|
if (newValue === pickerColSelected.value) return
|
||||||
|
pickerColSelected.value = newValue
|
||||||
|
newValue.map((item, colIndex) => {
|
||||||
|
return getSelectedItem(item, colIndex, selectList.value)[props.labelKey]
|
||||||
|
})
|
||||||
|
handleAutoComplete()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deep: true,
|
||||||
|
immediate: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.columns,
|
||||||
|
(newValue, oldValue) => {
|
||||||
|
if (newValue.length && !isArray(newValue[0])) {
|
||||||
|
console.error('[wot ui] error(wd-col-picker): the columns props of wd-col-picker should be a two-dimensional array')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (newValue.length === 0 && !oldValue) return
|
||||||
|
|
||||||
|
const newSelectedList = newValue.slice(0)
|
||||||
|
|
||||||
|
selectList.value = newSelectedList
|
||||||
|
|
||||||
|
selectShowList.value = pickerColSelected.value.map((item, colIndex) => {
|
||||||
|
return getSelectedItem(item, colIndex, newSelectedList)[props.labelKey]
|
||||||
|
})
|
||||||
|
lastSelectList.value = newSelectedList
|
||||||
|
|
||||||
|
if (newSelectedList.length > 0) {
|
||||||
|
currentCol.value = newSelectedList.length - 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deep: true,
|
||||||
|
immediate: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.columnChange,
|
||||||
|
(fn) => {
|
||||||
|
if (fn && !isFunction(fn)) {
|
||||||
|
console.error('The type of columnChange must be Function')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deep: true,
|
||||||
|
immediate: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.displayFormat,
|
||||||
|
(fn) => {
|
||||||
|
if (fn && !isFunction(fn)) {
|
||||||
|
console.error('The type of displayFormat must be Function')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deep: true,
|
||||||
|
immediate: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.beforeConfirm,
|
||||||
|
(fn) => {
|
||||||
|
if (fn && !isFunction(fn)) {
|
||||||
|
console.error('The type of beforeConfirm must be Function')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deep: true,
|
||||||
|
immediate: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// 是否展示箭头
|
||||||
|
const showArrow = computed(() => {
|
||||||
|
return !props.disabled && !props.readonly
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
inited.value = true
|
||||||
|
})
|
||||||
|
|
||||||
|
// 打开弹框
|
||||||
|
function open() {
|
||||||
|
showPicker()
|
||||||
|
}
|
||||||
|
// 关闭弹框
|
||||||
|
function close() {
|
||||||
|
handlePickerClose()
|
||||||
|
}
|
||||||
|
function handlePickerOpend() {
|
||||||
|
updateLineAndScroll(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handlePickerClose() {
|
||||||
|
pickerShow.value = false
|
||||||
|
emit('close')
|
||||||
|
}
|
||||||
|
|
||||||
|
function handlePickerClosed() {
|
||||||
|
if (isChange.value) {
|
||||||
|
setTimeout(() => {
|
||||||
|
selectList.value = lastSelectList.value.slice(0)
|
||||||
|
pickerColSelected.value = lastPickerColSelected.value.slice(0)
|
||||||
|
selectShowList.value = lastPickerColSelected.value.map((item, colIndex) => {
|
||||||
|
return getSelectedItem(item, colIndex, lastSelectList.value)[props.labelKey]
|
||||||
|
})
|
||||||
|
currentCol.value = lastSelectList.value.length - 1
|
||||||
|
isChange.value = false
|
||||||
|
}, 250)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showPicker() {
|
||||||
|
const { disabled, readonly } = props
|
||||||
|
|
||||||
|
if (disabled || readonly) return
|
||||||
|
pickerShow.value = true
|
||||||
|
lastPickerColSelected.value = pickerColSelected.value.slice(0)
|
||||||
|
lastSelectList.value = selectList.value.slice(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSelectedItem(value: string | number, colIndex: number, selectList: Record<string, any>[][]) {
|
||||||
|
const { valueKey, labelKey } = props
|
||||||
|
if (selectList[colIndex]) {
|
||||||
|
const selecteds = selectList[colIndex].filter((item) => {
|
||||||
|
return item[valueKey] === value
|
||||||
|
})
|
||||||
|
|
||||||
|
if (selecteds.length > 0) {
|
||||||
|
return selecteds[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
[valueKey]: value,
|
||||||
|
[labelKey]: ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function chooseItem(colIndex: number, index: number) {
|
||||||
|
const item = selectList.value[colIndex][index]
|
||||||
|
if (item.disabled) return
|
||||||
|
|
||||||
|
const newPickerColSelected = pickerColSelected.value.slice(0, colIndex)
|
||||||
|
newPickerColSelected.push(item[props.valueKey])
|
||||||
|
isChange.value = true
|
||||||
|
pickerColSelected.value = newPickerColSelected
|
||||||
|
selectList.value = selectList.value.slice(0, colIndex + 1)
|
||||||
|
selectShowList.value = newPickerColSelected.map((item, colIndex) => {
|
||||||
|
return getSelectedItem(item, colIndex, selectList.value)[props.labelKey]
|
||||||
|
})
|
||||||
|
|
||||||
|
if (selectShowList.value[colIndex] && colIndex === currentCol.value) {
|
||||||
|
updateLineAndScroll(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
handleColChange(colIndex, item, index)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleColChange(colIndex: number, item: Record<string, any>, index: number, callback?: () => void) {
|
||||||
|
loading.value = true
|
||||||
|
const { columnChange, beforeConfirm } = props
|
||||||
|
columnChange &&
|
||||||
|
columnChange({
|
||||||
|
selectedItem: item,
|
||||||
|
index: colIndex,
|
||||||
|
rowIndex: index,
|
||||||
|
resolve: (nextColumn: Record<string, any>[]) => {
|
||||||
|
if (!isArray(nextColumn)) {
|
||||||
|
console.error('[wot ui] error(wd-col-picker): the data of each column of wd-col-picker should be an array')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const newSelectList = selectList.value.slice(0)
|
||||||
|
newSelectList[colIndex + 1] = nextColumn
|
||||||
|
|
||||||
|
selectList.value = newSelectList
|
||||||
|
loading.value = false
|
||||||
|
currentCol.value = colIndex + 1
|
||||||
|
|
||||||
|
updateLineAndScroll(true)
|
||||||
|
if (typeof callback === 'function') {
|
||||||
|
isCompleting.value = false
|
||||||
|
selectShowList.value = pickerColSelected.value.map((item, colIndex) => {
|
||||||
|
return getSelectedItem(item, colIndex, selectList.value)[props.labelKey]
|
||||||
|
})
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
finish: (isOk?: boolean) => {
|
||||||
|
// 每设置展示数据回显
|
||||||
|
if (typeof callback === 'function') {
|
||||||
|
loading.value = false
|
||||||
|
isCompleting.value = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (isBoolean(isOk) && !isOk) {
|
||||||
|
loading.value = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (beforeConfirm) {
|
||||||
|
beforeConfirm(
|
||||||
|
pickerColSelected.value,
|
||||||
|
pickerColSelected.value.map((item, colIndex) => {
|
||||||
|
return getSelectedItem(item, colIndex, selectList.value)
|
||||||
|
}),
|
||||||
|
(isPass: boolean) => {
|
||||||
|
if (isPass) {
|
||||||
|
onConfirm()
|
||||||
|
} else {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
onConfirm()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
function onConfirm() {
|
||||||
|
isChange.value = false
|
||||||
|
loading.value = false
|
||||||
|
pickerShow.value = false
|
||||||
|
|
||||||
|
emit('update:modelValue', pickerColSelected.value)
|
||||||
|
emit('confirm', {
|
||||||
|
value: pickerColSelected.value,
|
||||||
|
selectedItems: pickerColSelected.value.map((item, colIndex) => {
|
||||||
|
return getSelectedItem(item, colIndex, selectList.value)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
function handleColClick(index: number) {
|
||||||
|
isChange.value = true
|
||||||
|
currentCol.value = index
|
||||||
|
updateLineAndScroll(true)
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @description 更新navBar underline的偏移量
|
||||||
|
* @param {Boolean} animation 是否伴随动画
|
||||||
|
*/
|
||||||
|
function setLineStyle(animation: boolean = true) {
|
||||||
|
if (!inited.value) return
|
||||||
|
const { lineWidth, lineHeight } = props
|
||||||
|
getRect($item, true, proxy)
|
||||||
|
.then((rects) => {
|
||||||
|
const lineStyle: CSSProperties = {}
|
||||||
|
if (isDef(lineWidth)) {
|
||||||
|
lineStyle.width = addUnit(lineWidth)
|
||||||
|
}
|
||||||
|
if (isDef(lineHeight)) {
|
||||||
|
lineStyle.height = addUnit(lineHeight)
|
||||||
|
lineStyle.borderRadius = `calc(${addUnit(lineHeight)} / 2)`
|
||||||
|
}
|
||||||
|
const rect = rects[currentCol.value]
|
||||||
|
let left = rects.slice(0, currentCol.value).reduce((prev, curr) => prev + Number(curr.width), 0) + Number(rect.width) / 2
|
||||||
|
lineStyle.transform = `translateX(${left}px) translateX(-50%)`
|
||||||
|
|
||||||
|
if (animation) {
|
||||||
|
lineStyle.transition = 'width 300ms ease, transform 300ms ease'
|
||||||
|
}
|
||||||
|
|
||||||
|
state.lineStyle = objToStyle(lineStyle)
|
||||||
|
})
|
||||||
|
.catch(() => {})
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @description scroll-view滑动到active的tab_nav
|
||||||
|
*/
|
||||||
|
function lineScrollIntoView() {
|
||||||
|
if (!inited.value) return
|
||||||
|
Promise.all([getRect($item, true, proxy), getRect($container, false, proxy)])
|
||||||
|
.then(([navItemsRects, navRect]) => {
|
||||||
|
if (!isArray(navItemsRects) || navItemsRects.length === 0) return
|
||||||
|
// 选中元素
|
||||||
|
const selectItem = navItemsRects[currentCol.value]
|
||||||
|
// 选中元素之前的节点的宽度总和
|
||||||
|
const offsetLeft = navItemsRects.slice(0, currentCol.value).reduce((prev, curr) => prev + Number(curr.width), 0)
|
||||||
|
// scroll-view滑动到selectItem的偏移量
|
||||||
|
scrollLeft.value = offsetLeft - ((navRect as any).width - Number(selectItem.width)) / 2
|
||||||
|
})
|
||||||
|
.catch(() => {})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 递归列数据补齐
|
||||||
|
function diffColumns(colIndex: number) {
|
||||||
|
// colIndex 为 -1 时,item 为空对象,>=0 时则具有 value 属性
|
||||||
|
const item = colIndex === -1 ? {} : { [props.valueKey]: props.modelValue[colIndex] }
|
||||||
|
handleColChange(colIndex, item, -1, () => {
|
||||||
|
// 如果 columns 长度还小于 value 长度,colIndex + 1,继续递归补齐
|
||||||
|
if (selectList.value.length < props.modelValue.length) {
|
||||||
|
diffColumns(colIndex + 1)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
function handleAutoComplete() {
|
||||||
|
if (props.autoComplete) {
|
||||||
|
// 如果 columns 数组长度为空,或者长度小于 value 的长度,自动触发 columnChange 来补齐数据
|
||||||
|
if (selectList.value.length < props.modelValue.length || selectList.value.length === 0) {
|
||||||
|
// isCompleting 是否在自动补全,锁操作
|
||||||
|
if (!isCompleting.value) {
|
||||||
|
// 如果 columns 长度为空,则传入的 colIndex 为 -1
|
||||||
|
const colIndex = selectList.value.length === 0 ? -1 : selectList.value.length - 1
|
||||||
|
diffColumns(colIndex)
|
||||||
|
}
|
||||||
|
isCompleting.value = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose<ColPickerExpose>({
|
||||||
|
close,
|
||||||
|
open
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import './index.scss';
|
||||||
|
</style>
|
||||||
19
node_modules/wot-design-uni/components/wd-col/index.scss
generated
vendored
Normal file
19
node_modules/wot-design-uni/components/wd-col/index.scss
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
@import '../common/abstracts/variable';
|
||||||
|
@import '../common/abstracts/mixin';
|
||||||
|
|
||||||
|
$i: 1;
|
||||||
|
|
||||||
|
@include b(col) {
|
||||||
|
float: left;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
@while $i <= 24 {
|
||||||
|
.wd-col__#{$i} {
|
||||||
|
width: calc(100% / 24 * $i);
|
||||||
|
}
|
||||||
|
.wd-col__offset-#{$i} {
|
||||||
|
margin-left: calc(100% / 24 * $i);
|
||||||
|
}
|
||||||
|
$i: $i + 1;
|
||||||
|
}
|
||||||
15
node_modules/wot-design-uni/components/wd-col/types.ts
generated
vendored
Normal file
15
node_modules/wot-design-uni/components/wd-col/types.ts
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import type { ExtractPropTypes } from 'vue'
|
||||||
|
import { baseProps, makeNumberProp } from '../common/props'
|
||||||
|
|
||||||
|
export const colProps = {
|
||||||
|
...baseProps,
|
||||||
|
/**
|
||||||
|
* 列元素宽度
|
||||||
|
*/
|
||||||
|
span: makeNumberProp(24),
|
||||||
|
/**
|
||||||
|
* 列元素偏移距离
|
||||||
|
*/
|
||||||
|
offset: makeNumberProp(0)
|
||||||
|
}
|
||||||
|
export type ColProps = ExtractPropTypes<typeof colProps>
|
||||||
49
node_modules/wot-design-uni/components/wd-col/wd-col.vue
generated
vendored
Normal file
49
node_modules/wot-design-uni/components/wd-col/wd-col.vue
generated
vendored
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<template>
|
||||||
|
<view :class="['wd-col', span && 'wd-col__' + span, offset && 'wd-col__offset-' + offset, customClass]" :style="rootStyle">
|
||||||
|
<!-- 每一列 -->
|
||||||
|
<slot />
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
name: 'wd-col',
|
||||||
|
options: {
|
||||||
|
addGlobalClass: true,
|
||||||
|
virtualHost: true,
|
||||||
|
styleIsolation: 'shared'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed, watch } from 'vue'
|
||||||
|
import { useParent } from '../composables/useParent'
|
||||||
|
import { ROW_KEY } from '../wd-row/types'
|
||||||
|
import { colProps } from './types'
|
||||||
|
import { isDef } from '../common/util'
|
||||||
|
|
||||||
|
const props = defineProps(colProps)
|
||||||
|
const { parent: row } = useParent(ROW_KEY)
|
||||||
|
|
||||||
|
const rootStyle = computed(() => {
|
||||||
|
const gutter = isDef(row) ? row.props.gutter || 0 : 0
|
||||||
|
const padding = `${gutter / 2}px`
|
||||||
|
const style = gutter > 0 ? `padding-left: ${padding}; padding-right: ${padding};background-clip: content-box;` : ''
|
||||||
|
return `${style}${props.customStyle}`
|
||||||
|
})
|
||||||
|
|
||||||
|
watch([() => props.span, () => props.offset], () => {
|
||||||
|
check()
|
||||||
|
})
|
||||||
|
|
||||||
|
function check() {
|
||||||
|
const { span, offset } = props
|
||||||
|
if (span < 0 || offset < 0) {
|
||||||
|
console.error('[wot-design] warning(wd-col): attribute span/offset must be greater than or equal to 0')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import './index.scss';
|
||||||
|
</style>
|
||||||
90
node_modules/wot-design-uni/components/wd-collapse-item/index.scss
generated
vendored
Normal file
90
node_modules/wot-design-uni/components/wd-collapse-item/index.scss
generated
vendored
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
@import '../common/abstracts/variable';
|
||||||
|
@import '../common/abstracts/mixin';
|
||||||
|
|
||||||
|
.wot-theme-dark {
|
||||||
|
@include b(collapse-item) {
|
||||||
|
@include halfPixelBorder('top', 0, $-dark-border-color);
|
||||||
|
|
||||||
|
|
||||||
|
@include e(title) {
|
||||||
|
color: $-dark-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(body) {
|
||||||
|
color: $-dark-color3;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(disabled) {
|
||||||
|
.wd-collapse-item__title {
|
||||||
|
color: $-dark-color-gray;
|
||||||
|
}
|
||||||
|
.wd-collapse-item__arrow {
|
||||||
|
color: $-dark-color-gray;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@include b(collapse-item) {
|
||||||
|
position: relative;
|
||||||
|
@include halfPixelBorder('top');
|
||||||
|
|
||||||
|
|
||||||
|
@include e(header) {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: $-collapse-header-padding;
|
||||||
|
overflow: hidden;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
@include when(expanded) {
|
||||||
|
@include halfPixelBorder('bottom');
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(custom) {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(title) {
|
||||||
|
color: $-collapse-title-color;
|
||||||
|
font-weight: $-fw-medium;
|
||||||
|
font-size: $-collapse-title-fs;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include edeep(arrow) {
|
||||||
|
display: block;
|
||||||
|
font-size: $-collapse-arrow-size;
|
||||||
|
color: $-collapse-arrow-color;
|
||||||
|
transition: transform 0.3s;
|
||||||
|
|
||||||
|
@include when(retract) {
|
||||||
|
transform: rotate(-180deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(wrapper) {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
will-change: height;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(body) {
|
||||||
|
color: $-collapse-body-color;
|
||||||
|
font-size: $-collapse-body-fs;
|
||||||
|
padding: $-collapse-body-padding;
|
||||||
|
line-height: 1.43;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(disabled) {
|
||||||
|
.wd-collapse-item__title {
|
||||||
|
color: $-collapse-disabled-color;
|
||||||
|
}
|
||||||
|
.wd-collapse-item__arrow {
|
||||||
|
color: $-collapse-disabled-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
48
node_modules/wot-design-uni/components/wd-collapse-item/types.ts
generated
vendored
Normal file
48
node_modules/wot-design-uni/components/wd-collapse-item/types.ts
generated
vendored
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import type { ComponentPublicInstance, ExtractPropTypes, PropType } from 'vue'
|
||||||
|
import { baseProps, makeBooleanProp, makeRequiredProp, makeStringProp } from '../common/props'
|
||||||
|
|
||||||
|
export type CollapseItemBeforeExpand = (name: string) => boolean | Promise<unknown>
|
||||||
|
|
||||||
|
export const collapseItemProps = {
|
||||||
|
...baseProps,
|
||||||
|
/**
|
||||||
|
* 自定义折叠栏内容容器样式类名
|
||||||
|
*/
|
||||||
|
customBodyClass: makeStringProp(''),
|
||||||
|
/**
|
||||||
|
* 自定义折叠栏内容容器样式
|
||||||
|
*/
|
||||||
|
customBodyStyle: makeStringProp(''),
|
||||||
|
/**
|
||||||
|
* 折叠栏的标题, 可通过 slot 传递自定义内容
|
||||||
|
*/
|
||||||
|
title: makeStringProp(''),
|
||||||
|
/**
|
||||||
|
* 禁用折叠栏
|
||||||
|
*/
|
||||||
|
disabled: makeBooleanProp(false),
|
||||||
|
/**
|
||||||
|
* 折叠栏的标识符
|
||||||
|
*/
|
||||||
|
name: makeRequiredProp(String),
|
||||||
|
/**
|
||||||
|
* 打开前的回调函数,返回 false 可以阻止打开,支持返回 Promise
|
||||||
|
*/
|
||||||
|
beforeExpend: Function as PropType<CollapseItemBeforeExpand>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CollapseItemProps = ExtractPropTypes<typeof collapseItemProps>
|
||||||
|
|
||||||
|
export type CollapseItemExpose = {
|
||||||
|
/**
|
||||||
|
* 获取展开状态
|
||||||
|
* @returns boolean
|
||||||
|
*/
|
||||||
|
getExpanded: () => boolean
|
||||||
|
/**
|
||||||
|
* 更新展开状态
|
||||||
|
*/
|
||||||
|
updateExpand: () => Promise<void>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CollapseItemInstance = ComponentPublicInstance<CollapseItemProps, CollapseItemExpose>
|
||||||
171
node_modules/wot-design-uni/components/wd-collapse-item/wd-collapse-item.vue
generated
vendored
Normal file
171
node_modules/wot-design-uni/components/wd-collapse-item/wd-collapse-item.vue
generated
vendored
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
<template>
|
||||||
|
<view :class="`wd-collapse-item ${disabled ? 'is-disabled' : ''} is-border ${customClass}`" :style="customStyle">
|
||||||
|
<view
|
||||||
|
:class="`wd-collapse-item__header ${expanded ? 'is-expanded' : ''} ${isFirst ? 'wd-collapse-item__header-first' : ''} ${
|
||||||
|
$slots.title ? 'is-custom' : ''
|
||||||
|
}`"
|
||||||
|
@click="handleClick"
|
||||||
|
>
|
||||||
|
<slot name="title" :expanded="expanded" :disabled="disabled" :isFirst="isFirst">
|
||||||
|
<text class="wd-collapse-item__title">{{ title }}</text>
|
||||||
|
<wd-icon name="arrow-down" :custom-class="`wd-collapse-item__arrow ${expanded ? 'is-retract' : ''}`" />
|
||||||
|
</slot>
|
||||||
|
</view>
|
||||||
|
<view class="wd-collapse-item__wrapper" :style="contentStyle" @transitionend="handleTransitionEnd">
|
||||||
|
<view class="wd-collapse-item__body" :class="customBodyClass" :style="customBodyStyle" :id="collapseId">
|
||||||
|
<slot />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
name: 'wd-collapse-item',
|
||||||
|
options: {
|
||||||
|
addGlobalClass: true,
|
||||||
|
virtualHost: true,
|
||||||
|
styleIsolation: 'shared'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import wdIcon from '../wd-icon/wd-icon.vue'
|
||||||
|
import { computed, getCurrentInstance, onMounted, ref, watch, type CSSProperties } from 'vue'
|
||||||
|
import { addUnit, getRect, isArray, isDef, isPromise, isString, objToStyle, pause, uuid } from '../common/util'
|
||||||
|
import { useParent } from '../composables/useParent'
|
||||||
|
import { COLLAPSE_KEY } from '../wd-collapse/types'
|
||||||
|
import { collapseItemProps, type CollapseItemExpose } from './types'
|
||||||
|
|
||||||
|
const collapseId = ref<string>(`collapseId${uuid()}`)
|
||||||
|
|
||||||
|
const props = defineProps(collapseItemProps)
|
||||||
|
|
||||||
|
const { parent: collapse, index } = useParent(COLLAPSE_KEY)
|
||||||
|
|
||||||
|
const height = ref<string | number>('')
|
||||||
|
const inited = ref<boolean>(false)
|
||||||
|
const expanded = ref<boolean>(false)
|
||||||
|
const { proxy } = getCurrentInstance() as any
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 容器样式,(动画)
|
||||||
|
*/
|
||||||
|
const isFirst = computed(() => {
|
||||||
|
return index.value === 0
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 容器样式,(动画)
|
||||||
|
*/
|
||||||
|
const contentStyle = computed(() => {
|
||||||
|
const style: CSSProperties = {}
|
||||||
|
if (inited.value) {
|
||||||
|
style.transition = 'height 0.3s ease-in-out'
|
||||||
|
}
|
||||||
|
if (!expanded.value) {
|
||||||
|
style.height = '0px'
|
||||||
|
} else if (height.value) {
|
||||||
|
style.height = addUnit(height.value)
|
||||||
|
}
|
||||||
|
return objToStyle(style)
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否选中
|
||||||
|
*/
|
||||||
|
const isSelected = computed(() => {
|
||||||
|
const modelValue = collapse ? collapse?.props.modelValue || [] : []
|
||||||
|
const { name } = props
|
||||||
|
return (isString(modelValue) && modelValue === name) || (isArray(modelValue) && modelValue.indexOf(name as string) >= 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => isSelected.value,
|
||||||
|
(newVal) => {
|
||||||
|
updateExpand(newVal)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
updateExpand(isSelected.value)
|
||||||
|
})
|
||||||
|
|
||||||
|
async function updateExpand(useBeforeExpand: boolean = true) {
|
||||||
|
try {
|
||||||
|
if (useBeforeExpand) {
|
||||||
|
await handleBeforeExpand()
|
||||||
|
}
|
||||||
|
initRect()
|
||||||
|
} catch (error) {
|
||||||
|
/* empty */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function initRect() {
|
||||||
|
getRect(`#${collapseId.value}`, false, proxy).then(async (rect) => {
|
||||||
|
const { height: rectHeight } = rect
|
||||||
|
height.value = isDef(rectHeight) ? Number(rectHeight) : ''
|
||||||
|
await pause()
|
||||||
|
if (isSelected.value) {
|
||||||
|
expanded.value = true
|
||||||
|
} else {
|
||||||
|
expanded.value = false
|
||||||
|
}
|
||||||
|
if (!inited.value) {
|
||||||
|
inited.value = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleTransitionEnd() {
|
||||||
|
if (expanded.value) {
|
||||||
|
height.value = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 点击子项
|
||||||
|
async function handleClick() {
|
||||||
|
if (props.disabled) return
|
||||||
|
try {
|
||||||
|
await updateExpand()
|
||||||
|
const { name } = props
|
||||||
|
collapse && collapse.toggle(name, !expanded.value)
|
||||||
|
} catch (error) {
|
||||||
|
/* empty */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 展开前钩子
|
||||||
|
*/
|
||||||
|
function handleBeforeExpand() {
|
||||||
|
return new Promise<void>((resolve, reject) => {
|
||||||
|
const { name } = props
|
||||||
|
const nextexpanded = !expanded.value
|
||||||
|
if (nextexpanded && props.beforeExpend) {
|
||||||
|
const response = props.beforeExpend(name)
|
||||||
|
if (!response) {
|
||||||
|
reject()
|
||||||
|
}
|
||||||
|
if (isPromise(response)) {
|
||||||
|
response.then(() => resolve()).catch(reject)
|
||||||
|
} else {
|
||||||
|
resolve()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
resolve()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function getExpanded() {
|
||||||
|
return expanded.value
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose<CollapseItemExpose>({ getExpanded, updateExpand })
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import './index.scss';
|
||||||
|
</style>
|
||||||
55
node_modules/wot-design-uni/components/wd-collapse/index.scss
generated
vendored
Normal file
55
node_modules/wot-design-uni/components/wd-collapse/index.scss
generated
vendored
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
@import "../common/abstracts/variable";
|
||||||
|
@import "../common/abstracts/mixin";
|
||||||
|
|
||||||
|
.wot-theme-dark {
|
||||||
|
@include b(collapse) {
|
||||||
|
background: $-dark-background2;
|
||||||
|
|
||||||
|
@include e(content) {
|
||||||
|
color: $-dark-color3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include b(collapse) {
|
||||||
|
background: $-color-white;
|
||||||
|
|
||||||
|
@include when(viewmore) {
|
||||||
|
padding: $-collapse-side-padding;
|
||||||
|
}
|
||||||
|
@include e(content) {
|
||||||
|
font-size: $-collapse-body-fs;
|
||||||
|
color: $-collapse-body-color;
|
||||||
|
|
||||||
|
@include when(retract) {
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
font-size: $-collapse-retract-fs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@include e(more) {
|
||||||
|
display: inline-block;
|
||||||
|
font-size: $-collapse-retract-fs;
|
||||||
|
margin-top: 8px;
|
||||||
|
color: $-collapse-more-color;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
@include e(more-txt) {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
@include e(arrow) {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
transition: transform 0.1s;
|
||||||
|
font-size: $-collapse-arrow-size;
|
||||||
|
height: $-collapse-arrow-size;
|
||||||
|
line-height: $-collapse-arrow-size;
|
||||||
|
|
||||||
|
@include when(retract) {
|
||||||
|
transform: rotate(-180deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
58
node_modules/wot-design-uni/components/wd-collapse/types.ts
generated
vendored
Normal file
58
node_modules/wot-design-uni/components/wd-collapse/types.ts
generated
vendored
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import { type ComponentPublicInstance, type ExtractPropTypes, type InjectionKey, type PropType } from 'vue'
|
||||||
|
import { baseProps, makeBooleanProp, makeNumberProp, makeStringProp } from '../common/props'
|
||||||
|
|
||||||
|
export type CollapseToggleAllOptions =
|
||||||
|
| boolean
|
||||||
|
| {
|
||||||
|
expanded?: boolean
|
||||||
|
skipDisabled?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CollapseProvide = {
|
||||||
|
props: Partial<CollapseProps>
|
||||||
|
toggle: (name: string, expanded: boolean) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const COLLAPSE_KEY: InjectionKey<CollapseProvide> = Symbol('wd-collapse')
|
||||||
|
|
||||||
|
export const collapseProps = {
|
||||||
|
...baseProps,
|
||||||
|
/**
|
||||||
|
* 查看更多模式下的插槽外部自定义样式
|
||||||
|
*/
|
||||||
|
customMoreSlotClass: makeStringProp(''),
|
||||||
|
/**
|
||||||
|
* 绑定值
|
||||||
|
*/
|
||||||
|
modelValue: {
|
||||||
|
type: [String, Array, Boolean] as PropType<string | Array<string> | boolean>
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 手风琴模式
|
||||||
|
*/
|
||||||
|
accordion: makeBooleanProp(false),
|
||||||
|
/**
|
||||||
|
* 查看更多的折叠面板
|
||||||
|
*/
|
||||||
|
viewmore: makeBooleanProp(false),
|
||||||
|
/**
|
||||||
|
* 查看更多的自定义插槽使用标志
|
||||||
|
*/
|
||||||
|
useMoreSlot: makeBooleanProp(false),
|
||||||
|
/**
|
||||||
|
* 查看更多的折叠面板,收起时的显示行数
|
||||||
|
*/
|
||||||
|
lineNum: makeNumberProp(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CollapseProps = ExtractPropTypes<typeof collapseProps>
|
||||||
|
|
||||||
|
export type CollapseExpose = {
|
||||||
|
/**
|
||||||
|
* 切换所有面板展开状态,传 true 为全部展开,false 为全部收起,不传参为全部切换
|
||||||
|
* @param options 面板状态
|
||||||
|
*/
|
||||||
|
toggleAll: (options?: CollapseToggleAllOptions) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CollapseInstance = ComponentPublicInstance<CollapseProps, CollapseExpose>
|
||||||
151
node_modules/wot-design-uni/components/wd-collapse/wd-collapse.vue
generated
vendored
Normal file
151
node_modules/wot-design-uni/components/wd-collapse/wd-collapse.vue
generated
vendored
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
<template>
|
||||||
|
<view :class="`wd-collapse ${viewmore ? 'is-viewmore' : ''} ${customClass}`" :style="customStyle">
|
||||||
|
<!-- 普通或手风琴 -->
|
||||||
|
<block v-if="!viewmore">
|
||||||
|
<slot></slot>
|
||||||
|
</block>
|
||||||
|
<!-- 查看更多模式 -->
|
||||||
|
<view v-else>
|
||||||
|
<view
|
||||||
|
:class="`wd-collapse__content ${!modelValue ? 'is-retract' : ''} `"
|
||||||
|
:style="`-webkit-line-clamp: ${contentLineNum}; -webkit-box-orient: vertical`"
|
||||||
|
>
|
||||||
|
<slot></slot>
|
||||||
|
</view>
|
||||||
|
<view class="wd-collapse__more" @click="handleMore">
|
||||||
|
<!-- 自定义展开按钮 -->
|
||||||
|
<view v-if="useMoreSlot" :class="customMoreSlotClass">
|
||||||
|
<slot name="more"></slot>
|
||||||
|
</view>
|
||||||
|
<!-- 显示展开或折叠按钮 -->
|
||||||
|
<block v-else>
|
||||||
|
<span class="wd-collapse__more-txt">{{ !modelValue ? translate('expand') : translate('retract') }}</span>
|
||||||
|
<view :class="`wd-collapse__arrow ${modelValue ? 'is-retract' : ''}`">
|
||||||
|
<wd-icon name="arrow-down"></wd-icon>
|
||||||
|
</view>
|
||||||
|
</block>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
name: 'wd-collapse',
|
||||||
|
options: {
|
||||||
|
addGlobalClass: true,
|
||||||
|
virtualHost: true,
|
||||||
|
styleIsolation: 'shared'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import wdIcon from '../wd-icon/wd-icon.vue'
|
||||||
|
import { onBeforeMount, ref, watch } from 'vue'
|
||||||
|
import { COLLAPSE_KEY, collapseProps, type CollapseExpose, type CollapseToggleAllOptions } from './types'
|
||||||
|
import { useChildren } from '../composables/useChildren'
|
||||||
|
import { isArray, isBoolean, isDef } from '../common/util'
|
||||||
|
import { useTranslate } from '../composables/useTranslate'
|
||||||
|
|
||||||
|
const props = defineProps(collapseProps)
|
||||||
|
const emit = defineEmits(['change', 'update:modelValue'])
|
||||||
|
|
||||||
|
const { translate } = useTranslate('collapse')
|
||||||
|
const contentLineNum = ref<number>(0) // 查看更多的折叠面板,收起时的显示行数
|
||||||
|
|
||||||
|
const { linkChildren, children } = useChildren(COLLAPSE_KEY)
|
||||||
|
|
||||||
|
linkChildren({ props, toggle })
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.modelValue,
|
||||||
|
(newVal) => {
|
||||||
|
const { viewmore, accordion } = props
|
||||||
|
// 手风琴状态下 value 类型只能为 string
|
||||||
|
if (accordion && typeof newVal !== 'string') {
|
||||||
|
console.error('accordion value must be string')
|
||||||
|
} else if (!accordion && !viewmore && !isArray(newVal)) {
|
||||||
|
console.error('value must be Array')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.lineNum,
|
||||||
|
(newVal) => {
|
||||||
|
if (newVal <= 0) {
|
||||||
|
console.error('lineNum must greater than 0')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ deep: true, immediate: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
onBeforeMount(() => {
|
||||||
|
const { lineNum, viewmore, modelValue } = props
|
||||||
|
contentLineNum.value = viewmore && !modelValue ? lineNum : 0
|
||||||
|
})
|
||||||
|
|
||||||
|
function updateChange(activeNames: string | string[] | boolean) {
|
||||||
|
emit('update:modelValue', activeNames)
|
||||||
|
emit('change', {
|
||||||
|
value: activeNames
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggle(name: string, expanded: boolean) {
|
||||||
|
const { accordion, modelValue } = props
|
||||||
|
if (accordion) {
|
||||||
|
updateChange(name === modelValue ? '' : name)
|
||||||
|
} else if (expanded) {
|
||||||
|
updateChange((modelValue as string[]).concat(name))
|
||||||
|
} else {
|
||||||
|
updateChange((modelValue as string[]).filter((activeName) => activeName !== name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 切换所有面板展开状态,传 true 为全部展开,false 为全部收起,不传参为全部切换
|
||||||
|
* @param options 面板状态
|
||||||
|
*/
|
||||||
|
const toggleAll = (options: CollapseToggleAllOptions = {}) => {
|
||||||
|
if (props.accordion) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (isBoolean(options)) {
|
||||||
|
options = { expanded: options }
|
||||||
|
}
|
||||||
|
|
||||||
|
const { expanded, skipDisabled } = options
|
||||||
|
const names: string[] = []
|
||||||
|
children.forEach((item, index) => {
|
||||||
|
if (item.disabled && skipDisabled) {
|
||||||
|
if (item.$.exposed!.getExpanded()) {
|
||||||
|
names.push(item.name || index)
|
||||||
|
}
|
||||||
|
} else if (isDef(expanded) ? expanded : !item.$.exposed!.getExpanded()) {
|
||||||
|
names.push(item.name || index)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
updateChange(names)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查看更多点击
|
||||||
|
*/
|
||||||
|
function handleMore() {
|
||||||
|
emit('update:modelValue', !props.modelValue)
|
||||||
|
emit('change', {
|
||||||
|
value: !props.modelValue
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose<CollapseExpose>({
|
||||||
|
toggleAll
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import './index.scss';
|
||||||
|
</style>
|
||||||
1124
node_modules/wot-design-uni/components/wd-config-provider/types.ts
generated
vendored
Normal file
1124
node_modules/wot-design-uni/components/wd-config-provider/types.ts
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user