diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 00000000..1d060c23 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,2 @@ +babel.config.js +src/uni_modules/mp-html/* \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 00000000..18907349 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,42 @@ +/* + * @Author: weisheng + * @Date: 2023-03-14 16:06:21 + * @LastEditTime: 2023-03-28 16:52:32 + * @LastEditors: weisheng + * @Description: + * @FilePath: \fant-mini-plus\.eslintrc.js + * 记得注释 + */ +module.exports = { + env: { + browser: true, + es2021: true + }, + extends: ['eslint:recommended', 'plugin:vue/vue3-essential', 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended'], + overrides: [], + parser: 'vue-eslint-parser', + parserOptions: { + parser: '@typescript-eslint/parser', + ecmaVersion: 2020 + }, + plugins: ['vue', '@typescript-eslint'], + rules: { + 'linebreak-style': ['error', 'unix'], + quotes: ['error', 'single'], + semi: ['error', 'never'], + 'no-console': 'off', + 'no-debugger': 'off', + 'no-undef': 'off', + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-use-before-define': 'off', + '@typescript-eslint/no-inferrable-types': 'off', + '@typescript-eslint/no-unused-vars': 'off', + '@typescript-eslint/no-non-null-assertion': 'off', + '@typescript-eslint/no-var-requires': 'off', + '@typescript-eslint/no-namespace': 'off', + 'no-inner-declarations': 'off', + '@typescript-eslint/no-this-alias': 'off', + '@typescript-eslint/no-empty-function': 'off', + 'vue/multi-word-component-names': 'off' + } +} diff --git a/.git-cz.json b/.git-cz.json new file mode 100644 index 00000000..66c81de0 --- /dev/null +++ b/.git-cz.json @@ -0,0 +1,70 @@ +{ + "disableEmoji": false, + "list": ["test", "feat", "fix", "chore", "docs", "refactor", "style", "ci", "perf", "release", "revert", "build"], + "maxMessageLength": 64, + "minMessageLength": 3, + "questions": ["type", "scope", "subject", "body", "breaking", "issues", "lerna"], + "scopes": [], + "types": { + "chore": { + "description": "Chore | 构建/工程依赖/工具", + "emoji": "🚀", + "value": "chore" + }, + "ci": { + "description": "Continuous Integration | CI 配置", + "emoji": "👷", + "value": "ci" + }, + "docs": { + "description": "Documentation | 文档", + "emoji": "✏️ ", + "value": "docs" + }, + "feat": { + "description": "Features | 新功能", + "emoji": "✨", + "value": "feat" + }, + "fix": { + "description": "Bug Fixes | Bug 修复", + "emoji": "🐛", + "value": "fix" + }, + "perf": { + "description": "Performance Improvements | 性能优化", + "emoji": "⚡", + "value": "perf" + }, + "refactor": { + "description": "Code Refactoring | 代码重构", + "emoji": "♻️ ", + "value": "refactor" + }, + "release": { + "description": "Create a release commit | 发版提交", + "emoji": "🏹", + "value": "release" + }, + "style": { + "description": "Styles | 风格", + "emoji": "💄", + "value": "style" + }, + "revert": { + "description": "Revert | 回退", + "emoji": "⏪", + "value": "revert" + }, + "build": { + "description": "Build System | 打包构建", + "emoji": "📦", + "value": "build" + }, + "test": { + "description": "Tests | 测试", + "emoji": "✅", + "value": "test" + } + } +} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 5d947ca8..9d10334a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,18 +1,30 @@ -# Build and Release Folders -bin-debug/ -bin-release/ -[Oo]bj/ -[Bb]in/ - -# Other files and folders -.settings/ - -# Executables -*.swf -*.air -*.ipa -*.apk - -# Project files, i.e. `.project`, `.actionScriptProperties` and `.flexProperties` -# should NOT be excluded as they contain compiler settings and other important -# information for Eclipse / Flash Builder. +.DS_Store +node_modules/ +unpackage/ +dist/ +lib/ +website/ +/docs +.temp +.cache +# src/uni_modules/fant-mini-plus/components/hd-*/API.md +docs/components/hd-* +docs/.vuepress/public/*.zip + +# local env files +.env.local +.env.*.local + +# Log files +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Editor directories and files +.project +.idea +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw* diff --git a/.husky/commit-msg b/.husky/commit-msg new file mode 100644 index 00000000..5426a932 --- /dev/null +++ b/.husky/commit-msg @@ -0,0 +1,4 @@ +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" + +npx commitlint --edit $1 diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 00000000..9a8404ed --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,4 @@ +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" + +npx lint-staged --allow-empty $1 diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000..8c956eea --- /dev/null +++ b/.prettierrc @@ -0,0 +1,11 @@ +{ + "printWidth": 150, + "semi": false, + "singleQuote": true, + "trailingComma": "none", + "bracketSpacing": true, + "requirePragma": false, + "proseWrap": "preserve", + "arrowParens": "always", + "htmlWhitespaceSensitivity": "ignore" +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..80cc6a3f --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,39 @@ +{ + "workbench.settings.useSplitJSON": true, + // vscode默认启用了根据文件类型自动设置tabsize的选项 + "editor.detectIndentation": false, + // 重新设定tabsize + "editor.tabSize": 2, + // #每次保存的时候自动格式化 + // "editor.formatOnSave": true, + // #每次保存的时候将代码按eslint格式进行修复 + "editor.codeActionsOnSave": { + "source.fixAll.eslint": true + }, + // 添加 vue 支持 + "eslint.validate": [ + "javascript", + "javascriptreact", + "typescript", + "vue" + ], + // #去掉代码结尾的分号 + "prettier.semi": true, + // #使用单引号替代双引号 + "prettier.singleQuote": true, + // #让函数(名)和后面的括号之间加个空格 + "javascript.format.insertSpaceBeforeFunctionParenthesis": true, + // #这个按用户自身习惯选择 + "vetur.format.defaultFormatter.html": "js-beautify-html", + // #让vue中的js按编辑器自带的ts格式进行格式化 + "vetur.format.defaultFormatter.js": "vscode-typescript", + "vetur.format.options.tabSize": 2, + "vetur.format.defaultFormatterOptions": { + "js-beautify-html": { + "wrap_line_length": 150, + "wrap_attributes": true, + "end_with_newline": true + // #vue组件中html代码格式化样式 + } + } +} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..b69cd13b --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,112 @@ +# Changelog + +All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. + +### [0.0.12](https://gitlab.hd123.com/vue/fant-mini-plus/compare/v0.0.11...v0.0.12) (2023-05-16) + + +### Features + +* ✨ Transition组件在APP、微信、H5、支付宝、QQ平台使用wxs优化动画表现 ([14806e0](https://gitlab.hd123.com/vue/fant-mini-plus/commit/14806e0d77ef5e0bf0e93d723861ef306c8c8b3e)) + + +### Bug Fixes + +* 🐛 修复Popup组件从中心弹出时宽度异常的问题 ([4fd41d4](https://gitlab.hd123.com/vue/fant-mini-plus/commit/4fd41d4b42c37cce1c939c57e6001d5b3afeafaf)) + +### [0.0.11](https://gitlab.hd123.com/vue/fant-mini-plus/compare/v0.0.10...v0.0.11) (2023-05-15) + + +### Features + +* ✨ Button组件自定义节点设置为虚拟节点去掉微信小程序自定义组件多出的最外层标签 ([a877049](https://gitlab.hd123.com/vue/fant-mini-plus/commit/a87704996224269a9077ee380be72268c936cf82)) + +### [0.0.10](https://gitlab.hd123.com/vue/fant-mini-plus/compare/v0.0.9...v0.0.10) (2023-05-15) + + +### Features + +* ✨ 新增 Image 和 WaterMark 组件 ([90a097e](https://gitlab.hd123.com/vue/fant-mini-plus/commit/90a097e83a7f612f9b76425e07de5f602ff39d3c)) +* ✨ 新增WaterMark水印组件 ([37a3979](https://gitlab.hd123.com/vue/fant-mini-plus/commit/37a39798f555d55394d33830256398452d5e7d35)) + +### [0.0.9](https://gitlab.hd123.com/vue/fant-mini-plus/compare/v0.0.8...v0.0.9) (2023-04-25) + + +### Features + +* ✨ Table组件优化支持data-source响应式更新 ([4f4b451](https://gitlab.hd123.com/vue/fant-mini-plus/commit/4f4b4510f395f02f554e45958c40a10cde07ff93)) + + +### Bug Fixes + +* 🐛 修复Calendar组件在某些版本uni-app的H5端第一次打开无数据渲染的问题 ([2d2dd33](https://gitlab.hd123.com/vue/fant-mini-plus/commit/2d2dd337c2e29b774c8dedfa01917758b1283917)) + +### [0.0.8](https://gitlab.hd123.com/vue/fant-mini-plus/compare/v0.0.7...v0.0.8) (2023-04-23) + + +### Features + +* ✨ 新增WaterMark水印组件 ([3d9ba3f](https://gitlab.hd123.com/vue/fant-mini-plus/commit/3d9ba3f658c2ce8de8eaec2a5c8bf64963e4f871)) +* ✨ Table表格组件支持自定义列模板并增加row-height行高属性 ([23866e2](https://gitlab.hd123.com/vue/fant-mini-plus/commit/23866e2f6ef8ef9a080824f3ebe720dd9755dca6)) + +### [0.0.7](https://gitlab.hd123.com/vue/fant-mini-plus/compare/v0.0.6...v0.0.7) (2023-04-10) + + +### Bug Fixes + +* 🐛 修复README中缺少演示小程序二维码的问题 ([298a21d](https://gitlab.hd123.com/vue/fant-mini-plus/commit/298a21d647bc82d705d4458aa4694ecff35f3f56)) + +### [0.0.6](https://gitlab.hd123.com/vue/fant-mini-plus/compare/v0.0.5...v0.0.6) (2023-04-07) + + +### Bug Fixes + +* 🐛 修复DatePicker组件动画效果生硬的问题 ([1b821e0](https://gitlab.hd123.com/vue/fant-mini-plus/commit/1b821e04e56025e9cee82859e8278dfabfc90d23)) + +### [0.0.5](https://gitlab.hd123.com/vue/fant-mini-plus/compare/v0.0.4...v0.0.5) (2023-04-05) + + +### Bug Fixes + +* 🐛 修复支付宝小程序Calendar组件不显示二级标题月份的问题 ([b7c541d](https://gitlab.hd123.com/vue/fant-mini-plus/commit/b7c541d4b6fbbe10c746f7a56dbc36c5eb1e0831)) +* 🐛 修复Area组件有默认值时打开未滚动到默认选项的问题 ([b3df205](https://gitlab.hd123.com/vue/fant-mini-plus/commit/b3df20514b7ef22ef7cb46907229dc9d981a374f)) +* 🐛 修复Transition组件动画抖动的问题 ([e5fa79b](https://gitlab.hd123.com/vue/fant-mini-plus/commit/e5fa79b8ee599cc14392abb89cbf988e07104e92)) + +### [0.0.4](https://gitlab.hd123.com/vue/fant-mini-plus/compare/v0.0.3...v0.0.4) (2023-04-04) + + +### Features + +* ✨ Popup和Transition组件新增destory属性用于控制是否销毁插槽中的内容 ([bb26d31](https://gitlab.hd123.com/vue/fant-mini-plus/commit/bb26d318af7af1cffe6d3d9eca9018c1c1ce8f40)) + + +### Bug Fixes + +* 🐛 修复Modal组件弹出异常的问题 ([38b88b6](https://gitlab.hd123.com/vue/fant-mini-plus/commit/38b88b6f5fc8e596411ab43df59a1f41db430528)) +* 🐛 修复Popup连续多次弹出有概率无法再弹出的问题 ([23e51d2](https://gitlab.hd123.com/vue/fant-mini-plus/commit/23e51d2e04bf6931bd8c3cd5b4dcf7dce15f3850)) + +### [0.0.3](https://gitlab.hd123.com/vue/fant-mini-plus/compare/v0.0.2...v0.0.3) (2023-04-03) + + +### Bug Fixes + +* 🐛 修复DatePicker组件在支付宝平台显示异常的问题 ([604277c](https://gitlab.hd123.com/vue/fant-mini-plus/commit/604277c16a284b05829353a93a6a213f42269dc4)) + +### [0.0.2](https://gitlab.hd123.com/vue/fant-mini-plus/compare/v0.0.1...v0.0.2) (2023-04-03) + + +### Bug Fixes + +* 🐛 改善transition动画延迟的问题 ([38b4d74](https://gitlab.hd123.com/vue/fant-mini-plus/commit/38b4d74bf27166d81f799661b058762caf6c145c)) + +### 0.0.1 (2023-03-29) + + +### Features + +* ✨ v0.0.1版本完成,移植所有fant-mini组件到vue3 ([e0a7e75](https://gitlab.hd123.com/vue/fant-mini-plus/commit/e0a7e75ffbcc89928be1868d1130cf0a74727882)) + + +### Bug Fixes + +* 🐛 修复动画组件在微信小程序上卡顿的问题 ([4c66ced](https://gitlab.hd123.com/vue/fant-mini-plus/commit/4c66cedcb67515a1c1301bba65a9c7e653885df7)) diff --git a/build/changelog.js b/build/changelog.js new file mode 100644 index 00000000..3f2a5d50 --- /dev/null +++ b/build/changelog.js @@ -0,0 +1,22 @@ +/* + * @Author: weisheng + * @Date: 2022-02-24 15:37:04 + * @LastEditTime: 2023-03-21 20:57:36 + * @LastEditors: weisheng + * @Description: 讲生成的changelog移动到文档和组件中 + * @FilePath: \fant-mini-plus-plus\build\changelog.js + * 记得注释 + */ +const fs = require('fs') +const path = require('path') +const fromPath = path.resolve(__dirname, '../CHANGELOG.md') +const toPath = path.resolve(__dirname, '../src/uni_modules/fant-mini-plus') +const docPath = path.resolve(__dirname, '../fant-doc/docs/components') + +try { + const file = fs.readFileSync(fromPath, 'utf-8') + fs.writeFileSync(`${toPath}/changelog.md`, file) + fs.writeFileSync(`${docPath}/changelog.md`, file) +} catch (error) { + console.log('CHANGELOG 获取失败') +} diff --git a/build/ci_keys/private.wxa3fab3dcde4667f0.key b/build/ci_keys/private.wxa3fab3dcde4667f0.key new file mode 100644 index 00000000..293094a3 --- /dev/null +++ b/build/ci_keys/private.wxa3fab3dcde4667f0.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEAqtvdTeEPKGwStj1fVg6UWlMN5uxMv7TQDVs/6vlHSf307ObC +6acjvH+Id2ci5OepN6NgKoGh3P3xwLAXQsOQsCvfnlwiRFaTZ/kD6BWTPSxLHmpf +oS4LYEqUxTrRKnypOarGvqOIvkLNyzpw5En8Vnf1/K67eoFY0UFF87fn4763zlUW +wOuzvVYu56i7FLdU2v0m3UdguJ6pZ3VGpTVfOpkA/nnTjRvPVjFK0BgQBLnHzXUx +1kcA3ZRuCOEFGnyxgaIGwxmD2k5rcwFmvgXkOTaZeERdlRj3As8N7scGsSaIm2RJ +y43lQg2l4gEY2F9HZniGC3H5Qiqa8YttVFGX2wIDAQABAoIBAFUk2eTceeRH7w84 +CFFnVJCqgOwJ57lFDsUJKxIahWcfEjYYTRuI+isOVuBB2ka+FzqtxNeJ4DKzrgy6 +8+yGbo0MYBSXj1AE4NJYapT2Y3iBoTGYCu3Ud0DWCcs7o06L7vzY2M/ZyOQfgFR9 +XBK3t/MTNtdj7/N9j9g/se9hP0LjUEqBBzsE27CGNIu2YzbXxQ5Se72WkM665v5K +Ivkb7Pgh+duN0Bt4hR2BsMUWK/SGOoGoXpM1TgDwzNyRQEgjPOpX/Ipt0AO3jVhL +xS3mFQD6NQckm6l8QTma8su7lFod+K3nvC24QTwbtAfxWdrioLfDyxcGZzUk82Gb +XYXnkIECgYEA1tGQFk1H4bU+ARDrwolPQJI/WPdLs9sscheEeiu2nM6kdbAmwnzm +sY2NtOynksrLMXeEyGCnXtKm7qEH+tUBVB/lsgEXw9Te1gHGZSBk/aPNcA5jXTOA +8684zZBJ6JZVsGmAVbAjti6yUsMhSZk8MjGJnhNKlPqWoURl1q3TmcECgYEAy5zu +wIkd7pmoWHczv9aoMRUUu8xtTNUTq1pfbCb6sc2v4Nocw3+Wtbf3xwe7gwd7QhBn +clwMF/reNslFxyuQQRB1UIOP3igJ6r4sTCPAl8Qsj8xdmN9DMa1WVSLPjCEdj+6Z +0tAnOiYPYCqxG+Hsvbmm5vGk3Kj+nW97lZ8TgJsCgYB95rC2AXEhneHLKimjCGrE +g3JRKA7cSJZR/+qK19fdK1dECouM7Tsf0MC+yvyjketpAI14Cv3NG1TvAr30iqaO +sWsj2nQdOEOp1bx7RHMsHLao+CXQWAE50PZPtEM85+8sx4iJsAQeIFwvGWIHCqI3 +IMVxOgk6K2vg9H9jRNmBgQKBgGTpzYbNLnGP5FicE6DToZ5Z5WHCSrWWsV3ut3Zh +x0QSPkYBs9nMxYQgvoP9OBkTvyoZ+Ts7lZ7Y7gNXM+cnlyI4JvyVh9tCGtAmGsxN +t+lACBno3saieKoJT814KEc6Lm2kgsZx9c8jB+HQpuC701qgxbCWOPBILZEXrLeW +FNNbAoGAfxFWLGPXl6oB8qAXX/41v8RhhsziBAuOfoRy7CzkGm1L2BWjTXF/gR/0 +xVrNTm/sRIX82zLt/AwWZZja9TutEa3PIGo+E6VaTDIMMyT/vPFol1SWabcRaQgk +KivcysDoP2DLtwYjVznD3WLmxNVN0boNjnZWto3hYzxAqlkVcg4= +-----END RSA PRIVATE KEY----- diff --git a/build/compiler.js b/build/compiler.js new file mode 100644 index 00000000..228e1c10 --- /dev/null +++ b/build/compiler.js @@ -0,0 +1,46 @@ +/* + * @Author: weisheng + * @Date: 2023-03-21 20:58:19 + * @LastEditTime: 2023-03-21 20:58:31 + * @LastEditors: weisheng + * @Description: + * @FilePath: \fant-mini-plus-plus\build\compiler.js + * 记得注释 + */ +const fs = require('fs') +const path = require('path') + +const src = path.resolve(__dirname, '../src/uni_modules/fant-mini-plus') +const libDir = path.resolve(__dirname, '../lib') + +const copyFile = function (srcPath, tarPath, filter = []) { + fs.mkdir(tarPath, (err) => {}) + fs.readdir(srcPath, function (err, files) { + if (err === null) { + files.forEach(function (filename) { + const filedir = path.join(srcPath, filename) + const filterFlag = filter.some((item) => { + return path.extname(filename).toLowerCase() === item && filename !== 'changelog.md' + }) + if (!filterFlag) { + fs.stat(filedir, function (errs, stats) { + const isFile = stats.isFile() + if (isFile) { + // 复制文件 + const destPath = path.join(tarPath, filename) + fs.copyFile(filedir, destPath, (err) => {}) + } else { + // 创建文件夹 + const tarFiledir = path.join(tarPath, filename) + copyFile(filedir, tarFiledir, filter) // 递归 + } + }) + } + }) + } else { + if (err) console.error(err) + } + }) +} + +copyFile(src, libDir, ['.md']) diff --git a/build/deploy.js b/build/deploy.js new file mode 100644 index 00000000..faabfaf7 --- /dev/null +++ b/build/deploy.js @@ -0,0 +1,72 @@ +/* + * @Author: weisheng + * @Date: 2022-01-28 14:23:02 + * @LastEditTime: 2023-03-21 20:59:16 + * @LastEditors: weisheng + * @Description: + * @FilePath: \fant-mini-plus\build\deploy.js + * 记得注释 + */ + +const OSS = require('ali-oss') +const fs = require('fs') + +const client = new OSS({ + region: 'oss-cn-hongkong', + accessKeyId: 'LTAI5tJ6okg3xgdy4VfCjmzs', + accessKeySecret: '8Hk0Af1CQufErdjrnTI2o5BQmbhY41', + bucket: 'historysoa' +}) + +async function putOss(ossPath, filePath) { + try { + const result = await client.multipartUpload(ossPath, filePath) + console.log(`上传远程oss文件:${filePath}成功!`) + } catch (e) { + console.log(`上传异常:${e}`) + } +} + +/** + * 上传前删除所有的文件 + */ +async function deleteAll(object) { + const result = await client.list({ + prefix: `${object}` + }) + result.objects.forEach((item) => { + client.delete(item.name) + console.log(`删除远程oss文件:${item.name}成功!`) + }) +} + +/** + * 获取指定文件夹下的文件 + * @param {string} local 文件夹路径 + */ +async function addFile(local, objectName, srcName) { + const localFiles = fs.readdirSync(local) + localFiles.forEach(async (localFile) => { + // 拼接文件夹子项的路径 + const filePath = `${local}/${localFile}` + // 获取子项文件信息 + const stat = fs.statSync(filePath) + if (stat.isFile()) { + console.log(srcName, 'srcName') + const ossPath = filePath.split(`${srcName ? srcName : local}`).join(`${objectName}`) + // 上传到oss + await putOss(ossPath, filePath) + } else { + addFile(filePath, objectName, srcName ? srcName : local) + } + }) +} + +async function upload() { + await deleteAll('fant-mini-plus') + await addFile('fant-doc/dist', 'fant-mini-plus') + await deleteAll('fant-demo') + await addFile('dist/build/h5', 'fant-demo') +} + +upload() diff --git a/build/docs.js b/build/docs.js new file mode 100644 index 00000000..38da5ff8 --- /dev/null +++ b/build/docs.js @@ -0,0 +1,54 @@ +/* + * @Author: weisheng + * @Date: 2022-02-11 15:19:37 + * @LastEditTime: 2023-03-28 16:40:46 + * @LastEditors: weisheng + * @Description: + * @FilePath: \fant-mini-plus\build\docs.js + * 记得注释 + */ +const fs = require('fs') +const path = require('path') + +/** + * 获取指定文件夹下的文件 + * @param {string} local 文件夹路径 + */ +function getFile(local) { + const localFiles = fs.readdirSync(local).filter((file) => { + return path.extname(file).toLowerCase() === '' || path.extname(file).toLowerCase() === '.md' + }) + const docfile = { + introduction: '', // 简介 + instructions: '', // 使用说明 + api: '' + } // 文档文件内容 + localFiles.forEach((localFile) => { + // 拼接文件夹子项的路径 + const filePath = `${local}/${localFile}` + // 获取子项文件信息 + const stat = fs.statSync(filePath) + if (stat.isFile()) { + if (localFile === 'API.md') { + // 读取markdown + const file = fs.readFileSync(filePath, 'utf-8') + docfile.api = file.replace(new RegExp(file.split('\n\n', 2)[0], 'g'), '').replace(new RegExp(file.split('\n\n', 2)[1], 'g'), '') + // fs.writeFileSync(`docs/components/${filePath.split('/').reverse()[1]}.md`, file) + } else if (localFile === 'README.md') { + // 读取markdown为数组 + docfile.introduction = fs.readFileSync(filePath, 'utf-8') + } else if (localFile === 'INDEX.md') { + // 读取markdown为数组 + docfile.instructions = fs.readFileSync(filePath, 'utf-8') + } + } else { + getFile(filePath) + } + }) + if (docfile.api || docfile.instructions || docfile.introduction) { + fs.writeFileSync(`fant-doc/docs/components/${local.split('/').reverse()[0]}.md`, docfile.introduction + docfile.instructions + docfile.api) + } +} + +// 合并文档 +getFile('src/uni_modules/fant-mini-plus/components') diff --git a/build/generate.js b/build/generate.js new file mode 100644 index 00000000..02a19248 --- /dev/null +++ b/build/generate.js @@ -0,0 +1,283 @@ +/* + * @Author: 庞昭昭 + * @Date: 2022-02-21 10:23:46 + * @LastEditTime: 2023-03-21 20:59:58 + * @LastEditors: weisheng + * @Description: 创建文件夹并初始化 + * @FilePath: \fant-mini-plus\build\generate.js + * 记得注释 + */ +const fs = require('fs') +const path = require('path') +const inquirer = require('inquirer') +const { execSync } = require('child_process') + +inquirer + .prompt([ + { + type: 'list', + name: 'operation', + message: '请选择操作类型(默认值:✨ create)', + choices: ['✨ create 创建', '🐛 modify 编辑', '🚀 remove 移除'], + default: '✨ create 创建' + }, + { + type: 'list', + name: 'type', + message: '请选择组件类型(默认值:✨ basic 基础组件)', + choices: ['✨ basic 基础组件', '🐛 form 表单组件', '🚀 action 反馈组件', '🔬 display 展示组件', '🧭 navigation 导航组件'], + default: '✨ basic 基础组件' + }, + { + type: 'input', + name: 'oldname', + message: '请输入原组件名', + default: '', + when: function (answers) { + // 当操作不是创建 + return answers['operation'] !== '✨ create 创建' + }, + validate: function (val) { + if (!val || !val.trim()) { + return '请输入原组件名' + } else { + return true + } + } + }, + { + type: 'input', + name: 'name', + message: '请输入组件名', + default: '', + validate: function (val) { + if (!val || !val.trim()) { + return '请输入组件名' + } else { + return true + } + } + }, + { + type: 'list', + name: 'confirm', + message: '确认操作吗?', + choices: ['Y', 'N'], + default: 'Y' + } + ]) + .then((answers) => { + if (!answers['confirm'] || answers['confirm'].toLowerCase() != 'y') { + console.log('🚨 操作取消') + return + } + + let name = '' + if (answers['name']) { + name = answers['name'] + } + + let oldname = '' + if (answers['oldname']) { + oldname = answers['oldname'] + } + + let type = '' + if (answers['type']) { + type = answers['type'].split(' ')[1] + } + + // 文件夹父目录 + const parentPath = 'src/uni_modules/fant-mini-plus/components' + // 文件夹目录 + const folderPath = `${parentPath}/${name}` + + // 操作 + switch (answers['operation']) { + case '✨ create 创建': + create(folderPath, type, name) // 新建 + break + case '🐛 modify 编辑': + modify(folderPath, type, name, oldname) // 编辑名称 + break + case '🚀 remove 移除': + remove(folderPath, type, name, oldname) // 删除 + break + default: + break + } + }) + .catch((error) => { + if (error.isTtyError) { + // Prompt couldn't be rendered in the current environment + } else { + // Something else went wrong + } + }) + +// 创建 +function create(url, type, name) { + // 检查创建路径是否存在 + if (!fs.existsSync(url)) { + // 不存在,创建文件夹 + fs.mkdirSync(url) + // vue模板代码 + const vueTemplate = ` + + + + ` + // 创建vue组件 + fs.writeFile(`${url}/${name}.vue`, vueTemplate, (err) => { + if (err) throw err + }) + // 创建代码演示文档 + fs.writeFile(`${url}/INDEX.md`, '## 代码演示', (err) => { + if (err) throw err + }) + // 创建组件说明文档 + fs.writeFile(`${url}/README.md`, '', (err) => { + if (err) throw err + }) + // 更新doc文档 + updateDoc('create', type, name) + } else { + console.error('warning:文件夹已存在', url) + } +} + +// 编辑 +function modify(url, type, name, oldname) { + const oldName = oldname + const newName = name + // 判断给定的路径是否存在 + if (fs.existsSync(url)) { + if (!newName) { + console.log('error:请传入新名称') + return + } + /** + * 返回文件和子目录的数组 + */ + files = fs.readdirSync(url) + files.forEach((file, index) => { + // 规范化生成文件路径。 + const curPath = path.join(url, file) + /** + * fs.statSync同步读取文件夹文件,如果是文件夹,在重复触发函数 + */ + if (fs.statSync(curPath).isDirectory()) { + // recurse + modify(curPath.replace(/\\/g, '/')) + } else { + // 获取文件内容 + const cur = fs.readFileSync(curPath, 'utf-8') + // 替换文件名称 + fs.writeFileSync(curPath, cur.replace(new RegExp(oldName, 'g'), newName), (err) => { + if (err) throw err + }) + if (file.includes(oldName)) { + // 修改文件名称 + fs.renameSync(`${url}/${file}`, `${url}/${file.replace(new RegExp(oldName, 'g'), newName)}`) + } + } + }) + /** + * 修改文件夹名称 + */ + const newUrlArr = url.split('/') + newUrlArr.splice(newUrlArr.length - 1, 1, newName) + const newUrl = newUrlArr.join('/') + fs.renameSync(url, newUrl, (err) => { + if (err) throw err + }) + // 更新doc文档配置 + updateDoc(type, name) + } else { + console.error('error:给定的路径不存在,请给出正确的路径', folderPath) + } +} + +// 删除 +function remove(url, type, name, oldname) { + // 判断给定的路径是否存在 + if (fs.existsSync(url)) { + /** + * 返回文件和子目录的数组 + */ + files = fs.readdirSync(url) + files.forEach((file, index) => { + // 规范化生成文件路径。 + const curPath = path.join(url, file) + /** + * fs.statSync同步读取文件夹文件,如果是文件夹,在重复触发函数 + */ + if (fs.statSync(curPath).isDirectory()) { + // recurse + remove(curPath) + } else { + // 函数删除文件 + fs.unlinkSync(curPath) + } + }) + /** + * 清除文件夹 + */ + fs.rmdirSync(url) + // 更新doc文档配置 + updateDoc(type, name, oldname) + } else { + console.error('error:给定的路径不存在,请给出正确的路径', folderPath) + } +} + +// 更新doc文档新config +function updateDoc(operation, type, name, oldname) { + // 更新config配置 + // 获取组件list集合 + const cmpList = require(`../fant-doc/docs/.vuepress/cmp/${type}.js`) + + // 操作 + if (operation == 'create') { + // 将新建文档插入到数组末尾 + if (!cmpList.children.includes(`/components/${name}`)) { + // 检查是否已存在文档路径配置,避免重复加入 + cmpList.children.push(`/components/${name}`) + // 重写cmpList文件 + fs.writeFileSync(`fant-doc/docs/.vuepress/cmp/${type}.js`, `module.exports = ${JSON.stringify(cmpList)}`, (err) => { + if (err) throw err + }) + } + } else if (operation == 'modify') { + const oldName = oldname + const newName = name + // 替换文档路径名称 + cmpList.children.splice(cmpList.children.indexOf(`/components/${oldName}`), 1, `/components/${newName}`) + // 重写cmpList文件 + fs.writeFileSync(`fant-doc/docs/.vuepress/cmp/${type}.js`, `module.exports = ${JSON.stringify(cmpList)}`, (err) => { + if (err) throw err + }) + } else if (operation == 'remove') { + // 获取删除文档路径配置下标 + const index = cmpList.children.indexOf(`/components/${name}`) + if (index > -1) { + // 检查是否已存在文档路径配置,存在时删除 + cmpList.children.splice(index, 1) + // 重写cmpList文件 + fs.writeFileSync(`fant-doc/docs/.vuepress/cmp/${type}.js`, `module.exports = ${JSON.stringify(cmpList)}`, (err) => { + if (err) throw err + }) + } + } else { + console.log('无操作,未更新文档') + } +} diff --git a/build/release.js b/build/release.js new file mode 100644 index 00000000..900fb5f3 --- /dev/null +++ b/build/release.js @@ -0,0 +1,87 @@ +/* + * @Author: weisheng + * @Date: 2022-11-01 17:12:57 + * @LastEditTime: 2023-03-28 16:40:56 + * @LastEditors: weisheng + * @Description: 组件发版问答 + * @FilePath: \fant-mini-plus\build\release.js + * 记得注释 + */ +const inquirer = require('inquirer') +// Node 核心模块 +const { execSync } = require('child_process') +const { writeFileSync, readFileSync } = require('fs') +const path = require('path') +const src = path.resolve(__dirname, '../src/uni_modules/fant-mini-plus') +const oldVersion = require('../package.json').version +inquirer + .prompt([ + { + type: 'list', + name: 'version', + message: '请选择发版类型(默认值:✨ minor)', + choices: ['🐛 patch 小版本', '✨ minor 中版本', '🚀 major 大版本'], + default: '✨ minor 中版本' + }, + { + type: 'list', + name: 'release', + message: '确认发布?', + choices: ['Y', 'N'], + default: 'Y' + } + ]) + .then((answers) => { + if (!answers['release'] || answers['release'].toLowerCase() != 'y') { + console.log('🚨 操作取消') + return + } + // 项目版本更新 + switch (answers['version']) { + case '🐛 patch 小版本': + execSync('yarn release-patch') + break + case '✨ minor 中版本': + execSync('yarn release-minor') + break + case '🚀 major 大版本': + execSync('yarn release-major') + break + default: + execSync('yarn release-minor') + break + } + // 生成日志 + execSync('yarn changelog') + // 更新版本 + const file = readFileSync(path.resolve(__dirname, '../package.json')) + const packageJson = JSON.parse(file.toString()) + const version = packageJson.version + console.log(`√ bumping version in package.json from ${oldVersion} to ${version}`) + const package = require('../src/uni_modules/fant-mini-plus/package.json') + package.version = version + writeFileSync(path.resolve(src, 'package.json'), JSON.stringify(package)) + // 生成声明文件 + execSync('yarn build:types') + console.log('√ build:types complete') + // 生成制品 + execSync('yarn compiler') + console.log('√ compiler complete') + execSync(`node build/updateDownloadVersion.js ${oldVersion} ${version}`) + execSync('yarn lint') + execSync('git add -A ') + execSync(`git commit -am "build: compile ${version}"`) + execSync(`git tag -a v${version} -am "chore(release): ${version}"`) + console.log('√ committing changes') + const branch = execSync('git branch --show-current').toString().replace(/\*/g, '').replace(/ /g, '') + console.log('🎉 版本发布成功') + const tip = 'Run `git push --follow-tags origin ' + branch + '` ' + 'to publish' + console.log(tip.replace(/\n/g, '')) + }) + .catch((error) => { + if (error.isTtyError) { + // Prompt couldn't be rendered in the current environment + } else { + // Something else went wrong + } + }) diff --git a/build/test.js b/build/test.js new file mode 100644 index 00000000..02ba0631 --- /dev/null +++ b/build/test.js @@ -0,0 +1,44 @@ +/* + * @Author: weisheng + * @Date: 2023-06-10 23:33:04 + * @LastEditTime: 2023-06-10 23:42:45 + * @LastEditors: weisheng + * @Description: + * @FilePath: \wot-design-uni\build\test.js + * 记得注释 + */ +const fs = require('fs') +const path = require('path') +// 文件夹父目录 + +const src = path.resolve(__dirname, '../src/uni_modules/wot-design-uni/components') + +const make = (local) => { + fs.readdir(local, function (err, files) { + if (err === null) { + files.forEach(function (filename) { + const url = path.resolve(local, filename + '/' + filename + '.vue') + // 检查创建路径是否存在 + if (!fs.existsSync(url)) { + // vue模板代码 + const vueTemplate = ` + + ` + // 创建vue组件 + fs.writeFile(`${url}`, vueTemplate, (err) => { + if (err) throw err + }) + } else { + console.error('warning:文件夹已存在', url) + } + }) + } else { + if (err) console.error(err) + } + }) +} + +make(src) diff --git a/build/updateDownloadVersion.js b/build/updateDownloadVersion.js new file mode 100644 index 00000000..d1d49781 --- /dev/null +++ b/build/updateDownloadVersion.js @@ -0,0 +1,27 @@ +/* + * @Author: weisheng + * @Date: 2023-03-14 17:35:30 + * @LastEditTime: 2023-03-28 16:41:20 + * @LastEditors: weisheng + * @Description: + * @FilePath: \fant-mini-plus\build\updateDownloadVersion.js + * 记得注释 + */ +const fs = require('fs') +const path = require('path') +const docPath = path.resolve(__dirname, '../fant-doc/docs/components') +// 传入参数 +const args = process.argv.splice(2) +const oldVersion = args[0] +const newVersion = args[1] +console.log(oldVersion, 'oldVersion') +console.log(newVersion, 'newVersion') + +if (oldVersion && newVersion) { + let installation = fs.readFileSync(`${docPath}/installation.md`, 'utf-8') + installation = installation.replace(new RegExp(`fant-mini-plus@${oldVersion}`, 'g'), `fant-mini-plus@${newVersion}`) + installation = installation.replace(new RegExp('fant-mini-plus', 'g'), `fant-mini-plus@${newVersion}`) + fs.writeFileSync(`${docPath}/installation.md`, installation) +} else { + console.log('组件库压缩包本本更新失败...') +} diff --git a/commitlint.config.js b/commitlint.config.js new file mode 100644 index 00000000..0367d83f --- /dev/null +++ b/commitlint.config.js @@ -0,0 +1,12 @@ +/* + * @Author: weisheng + * @Date: 2021-11-24 13:10:52 + * @LastEditTime: 2023-03-25 18:12:52 + * @LastEditors: weisheng + * @Description: + * @FilePath: \fant-mini-plus\commitlint.config.js + * 记得注释 + */ +module.exports = { + extends: ['@commitlint/config-conventional'] +} diff --git a/index.html b/index.html new file mode 100644 index 00000000..35ba70d7 --- /dev/null +++ b/index.html @@ -0,0 +1,30 @@ + + + + + + + + + + + + +
+ + + diff --git a/package.json b/package.json new file mode 100644 index 00000000..8df96ffd --- /dev/null +++ b/package.json @@ -0,0 +1,145 @@ +{ + "name": "wot-design-uni", + "version": "0.0.1", + "scripts": { + "dev:app": "uni -p app", + "dev:app-android": "uni -p app-android", + "dev:app-ios": "uni -p app-ios", + "dev:custom": "uni -p", + "dev:h5": "uni", + "dev:h5:ssr": "uni --ssr", + "dev:mp-alipay": "uni -p mp-alipay", + "dev:mp-baidu": "uni -p mp-baidu", + "dev:mp-kuaishou": "uni -p mp-kuaishou", + "dev:mp-lark": "uni -p mp-lark", + "dev:mp-qq": "uni -p mp-qq", + "dev:mp-toutiao": "uni -p mp-toutiao", + "dev:mp-weixin": "uni -p mp-weixin", + "dev:quickapp-webview": "uni -p quickapp-webview", + "dev:quickapp-webview-huawei": "uni -p quickapp-webview-huawei", + "dev:quickapp-webview-union": "uni -p quickapp-webview-union", + "build:app": "uni build -p app", + "build:app-android": "uni build -p app-android", + "build:app-ios": "uni build -p app-ios", + "build:custom": "uni build -p", + "build:h5": "uni build", + "build:h5:ssr": "uni build --ssr", + "build:mp-alipay": "uni build -p mp-alipay", + "build:mp-baidu": "uni build -p mp-baidu", + "build:mp-kuaishou": "uni build -p mp-kuaishou", + "build:mp-lark": "uni build -p mp-lark", + "build:mp-qq": "uni build -p mp-qq", + "build:mp-toutiao": "uni build -p mp-toutiao", + "build:mp-weixin": "uni build -p mp-weixin", + "build:quickapp-webview": "uni build -p quickapp-webview", + "build:quickapp-webview-huawei": "uni build -p quickapp-webview-huawei", + "build:quickapp-webview-union": "uni build -p quickapp-webview-union", + "type-check": "vue-tsc --noEmit", + "prepare": "husky install", + "lint": "eslint --fix --ext .js,.vue,.ts src", + "commit": "git-cz", + "release-major": "standard-version --release-as major", + "release-minor": "standard-version --release-as minor", + "release-patch": "standard-version --release-as patch", + "clean:lib": "rimraf lib", + "release-lib": "node build/release.js", + "build:types": "tsc -b ./tsconfig.types.json && node ./typesCopy.js", + "compiler": "npm run clean:lib && node build/compiler.js", + "publish-lib": "cd lib && npm publish", + "install:fant-doc": "cd fant-doc && yarn", + "docs:generate": "yarn compiler && node build/docs.js", + "docs:dev": "yarn docs:generate && cd fant-doc && yarn serve", + "docs:build": "yarn docs:generate && cd fant-doc && yarn build", + "deploy": "yarn docs:build && node build/deploy.js", + "component:generate": "node build/generate.js", + "changelog": "node build/changelog.js" + }, + "dependencies": { + "@dcloudio/uni-app": "3.0.0-alpha-3080220230511001", + "@dcloudio/uni-app-plus": "3.0.0-alpha-3080220230511001", + "@dcloudio/uni-components": "3.0.0-alpha-3080220230511001", + "@dcloudio/uni-h5": "3.0.0-alpha-3080220230511001", + "@dcloudio/uni-mp-alipay": "3.0.0-alpha-3080220230511001", + "@dcloudio/uni-mp-baidu": "3.0.0-alpha-3080220230511001", + "@dcloudio/uni-mp-jd": "3.0.0-alpha-3080220230511001", + "@dcloudio/uni-mp-kuaishou": "3.0.0-alpha-3080220230511001", + "@dcloudio/uni-mp-lark": "3.0.0-alpha-3080220230511001", + "@dcloudio/uni-mp-qq": "3.0.0-alpha-3080220230511001", + "@dcloudio/uni-mp-toutiao": "3.0.0-alpha-3080220230511001", + "@dcloudio/uni-mp-weixin": "3.0.0-alpha-3080220230511001", + "@dcloudio/uni-quickapp-webview": "3.0.0-alpha-3080220230511001", + "vue": "^3.2.45", + "vue-i18n": "^9.1.9" + }, + "devDependencies": { + "@commitlint/cli": "^17.4.4", + "@commitlint/config-conventional": "^17.4.4", + "@dcloudio/types": "^3.3.2", + "@dcloudio/uni-automator": "3.0.0-alpha-3080220230511001", + "@dcloudio/uni-cli-shared": "3.0.0-alpha-3080220230511001", + "@dcloudio/uni-stacktracey": "3.0.0-alpha-3080220230511001", + "@dcloudio/vite-plugin-uni": "3.0.0-alpha-3080220230511001", + "@types/node": "^18.14.6", + "@typescript-eslint/eslint-plugin": "^5.55.0", + "@typescript-eslint/parser": "^5.55.0", + "@vant/area-data": "^1.4.1", + "@vue/tsconfig": "^0.1.3", + "eslint": "^8.36.0", + "eslint-config-prettier": "^8.7.0", + "eslint-plugin-prettier": "^4.2.1", + "eslint-plugin-vue": "^9.9.0", + "git-cz": "^4.9.0", + "husky": "^8.0.3", + "inquirer": "8.0.0", + "lint-staged": "^13.2.0", + "mini-types": "^0.1.7", + "miniprogram-api-typings": "^3.9.0", + "npm-run-all": "^4.1.5", + "prettier": "^2.8.4", + "query-string": "^8.1.0", + "rimraf": "^4.4.0", + "rollup-plugin-visualizer": "^5.9.0", + "sass": "^1.59.3", + "standard-version": "^9.5.0", + "typescript": "^4.9.4", + "uni-mini-router": "^0.0.12", + "uni-read-pages-vite": "^0.0.6", + "vite": "4.0.3", + "vitest": "^0.30.1", + "vue-eslint-parser": "^9.1.0", + "vue-tsc": "^1.0.24" + }, + "config": { + "commitizen": { + "path": "git-cz" + } + }, + "standard-version": { + "skip": { + "tag": true + } + }, + "browserslist": [ + "Android >= 4.4", + "ios >= 9" + ], + "lint-staged": { + "*.{js,ts,vue}": "eslint --fix --ext .js,.vue,.ts src" + }, + "uni-app": { + "scripts": { + "mp-dingtalk": { + "title": "钉钉小程序", + "env": { + "UNI_PLATFORM": "mp-alipay" + }, + "define": { + "MP-DINGTALK": true + } + } + } + }, + "files": [ + "lib" + ] +} diff --git a/src/App.vue b/src/App.vue new file mode 100644 index 00000000..7fe7b309 --- /dev/null +++ b/src/App.vue @@ -0,0 +1,22 @@ + + + diff --git a/src/components/demo-block/demo-block.vue b/src/components/demo-block/demo-block.vue new file mode 100644 index 00000000..b29a734f --- /dev/null +++ b/src/components/demo-block/demo-block.vue @@ -0,0 +1,52 @@ + + + diff --git a/src/env.d.ts b/src/env.d.ts new file mode 100644 index 00000000..da88959b --- /dev/null +++ b/src/env.d.ts @@ -0,0 +1,17 @@ +/* + * @Author: weisheng + * @Date: 2023-03-21 21:06:55 + * @LastEditTime: 2023-03-21 21:07:02 + * @LastEditors: weisheng + * @Description: + * @FilePath: \fant-mini-plus\src\env.d.ts + * 记得注释 + */ +/// + +declare module '*.vue' { + import { DefineComponent } from 'vue' + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types + const component: DefineComponent<{}, {}, any> + export default component +} diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 00000000..76425a3e --- /dev/null +++ b/src/main.ts @@ -0,0 +1,20 @@ +/* + * @Author: weisheng + * @Date: 2023-03-09 19:23:03 + * @LastEditTime: 2023-06-10 23:01:56 + * @LastEditors: weisheng + * @Description: + * @FilePath: \wot-design-uni\src\main.ts + * 记得注释 + */ +import { createSSRApp } from 'vue' +import App from './App.vue' +import router from './router' +export function createApp() { + const app = createSSRApp(App) + app.config.warnHandler = () => null + app.use(router) + return { + app + } +} diff --git a/src/manifest.json b/src/manifest.json new file mode 100644 index 00000000..80df84c7 --- /dev/null +++ b/src/manifest.json @@ -0,0 +1,72 @@ +{ + "name" : "", + "appid" : "", + "description" : "", + "versionName" : "1.0.0", + "versionCode" : "100", + "transformPx" : false, + /* 5+App特有相关 */ + "app-plus" : { + "usingComponents" : true, + "nvueStyleCompiler" : "uni-app", + "compilerVersion" : 3, + "splashscreen" : { + "alwaysShowBeforeRender" : true, + "waiting" : true, + "autoclose" : true, + "delay" : 0 + }, + /* 模块配置 */ + "modules" : {}, + /* 应用发布信息 */ + "distribute" : { + /* android打包配置 */ + "android" : { + "permissions" : [ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ] + }, + /* ios打包配置 */ + "ios" : {}, + /* SDK配置 */ + "sdkConfigs" : {} + } + }, + /* 快应用特有相关 */ + "quickapp" : {}, + /* 小程序特有相关 */ + "mp-weixin" : { + "appid" : "wxa3fab3dcde4667f0", + "setting" : { + "urlCheck" : false + }, + "usingComponents" : true + }, + "mp-alipay" : { + "usingComponents" : true + }, + "mp-baidu" : { + "usingComponents" : true + }, + "mp-toutiao" : { + "usingComponents" : true + }, + "uniStatistics": { + "enable": false + }, + "vueVersion" : "3" +} diff --git a/src/model/KV.ts b/src/model/KV.ts new file mode 100644 index 00000000..48f79882 --- /dev/null +++ b/src/model/KV.ts @@ -0,0 +1,13 @@ +/* + * @Author: weisheng + * @Date: 2021-12-28 15:57:08 + * @LastEditTime: 2021-12-28 15:58:09 + * @LastEditors: weisheng + * @Description: + * @FilePath: \fant-mini\src\model\KV.ts + * 记得注释 + */ +export default class KV { + label: string = '' // 操作标题 + value!: T // 操作项数组 +} diff --git a/src/model/OperationOption.ts b/src/model/OperationOption.ts new file mode 100644 index 00000000..3fefe374 --- /dev/null +++ b/src/model/OperationOption.ts @@ -0,0 +1,16 @@ +import KV from './KV' + +/* + * @Author: weisheng + * @Date: 2021-12-28 15:13:43 + * @LastEditTime: 2023-03-21 22:34:12 + * @LastEditors: weisheng + * @Description: 模板操作选项 + * @FilePath: \fant-mini-plus\src\model\OperationOption.ts + * 记得注释 + */ +export default class OperationOption { + label: string = '' // 操作的标题(用于展示) + name: string | string[] = '' // 操作真实值用于代码传参 + value: KV[] = [] // 操作项数组 +} diff --git a/src/pages.json b/src/pages.json new file mode 100644 index 00000000..db22cebc --- /dev/null +++ b/src/pages.json @@ -0,0 +1,47 @@ +{ + "pages": [ + { + "path": "pages/button/Button", + "name": "button", + "style": { + "mp-alipay": { + "allowsBounceVertical": "NO" + }, + + "navigationBarTitleText": "Button 按钮" + } + }, + { + "path": "pages/icon/Icon", + "name": "icon", + "style": { + "mp-alipay": { + "allowsBounceVertical": "NO" + }, + + "navigationBarTitleText": "Icon 图标" + } + }, + { + "path": "pages/badge/Badge", + "name": "badge", + "style": { + "mp-alipay": { + "allowsBounceVertical": "NO" + }, + "navigationBarTitleText": "Badge 徽标" + } + } + ], + "tabBar": { + "color": "#7a7e83", + "selectedColor": "#1C64FD", + "backgroundColor": "#ffffff", + "list": [ ] + }, + "globalStyle": { + "navigationBarTextStyle": "black", + "navigationBarBackgroundColor": "#FFF", + "backgroundColor": "#F8F8F8" + } + } \ No newline at end of file diff --git a/src/pages/badge/Badge.vue b/src/pages/badge/Badge.vue new file mode 100644 index 00000000..d1d22a79 --- /dev/null +++ b/src/pages/badge/Badge.vue @@ -0,0 +1,54 @@ + + + diff --git a/src/pages/badge/index.js b/src/pages/badge/index.js new file mode 100644 index 00000000..9555c825 --- /dev/null +++ b/src/pages/badge/index.js @@ -0,0 +1 @@ +Page({}) \ No newline at end of file diff --git a/src/pages/badge/index.json b/src/pages/badge/index.json new file mode 100644 index 00000000..49185977 --- /dev/null +++ b/src/pages/badge/index.json @@ -0,0 +1,8 @@ +{ + "navigationBarTitleText": "Badge 角标", + "usingComponents": { + "demo-block": "../../components/demo-block/index", + "wd-badge": "../../wot-design/badge/index", + "wd-button": "../../wot-design/button/index" + } +} diff --git a/src/pages/badge/index.jxml b/src/pages/badge/index.jxml new file mode 100644 index 00000000..05e2a37e --- /dev/null +++ b/src/pages/badge/index.jxml @@ -0,0 +1,132 @@ + + + 评论 + + + 回复 + + + 评论 + + + 回复 + + + 评论 + + + 回复 + + + + + + 评论 + + + 回复 + + + + + + 评论 + + + 回复 + + + + + 数据查询 + + 回复 + + diff --git a/src/pages/badge/index.jxss b/src/pages/badge/index.jxss new file mode 100644 index 00000000..42b325df --- /dev/null +++ b/src/pages/badge/index.jxss @@ -0,0 +1,4 @@ +.badge { + margin: 0 30px 20px 0; + display: inline-block; +} diff --git a/src/pages/button/Button.vue b/src/pages/button/Button.vue new file mode 100644 index 00000000..65000cd5 --- /dev/null +++ b/src/pages/button/Button.vue @@ -0,0 +1,118 @@ + + + diff --git a/src/pages/icon/Icon.vue b/src/pages/icon/Icon.vue new file mode 100644 index 00000000..4d75a222 --- /dev/null +++ b/src/pages/icon/Icon.vue @@ -0,0 +1,93 @@ + + + diff --git a/src/router/index.ts b/src/router/index.ts new file mode 100644 index 00000000..89d8ab58 --- /dev/null +++ b/src/router/index.ts @@ -0,0 +1,27 @@ +/* + * @Author: weisheng + * @Date: 2021-10-13 11:15:00 + * @LastEditTime: 2023-04-06 20:10:46 + * @LastEditors: weisheng + * @Description: + * @FilePath: \fant-mini-plus\src\router\index.ts + * 记得注释 + */ +import { createRouter } from 'uni-mini-router' + +const router = createRouter({ + routes: [...ROUTES] +}) +router.beforeEach((to, from, next) => { + console.log(to, 'to') + console.log(from, 'from') + console.log('进入路由之前调用') + next() +}) +router.afterEach((to, from) => { + console.log(to, 'to') + console.log(from, 'from') + console.log('进入路由之后调用') +}) + +export default router diff --git a/src/shime-uni.d.ts b/src/shime-uni.d.ts new file mode 100644 index 00000000..8f2d51bf --- /dev/null +++ b/src/shime-uni.d.ts @@ -0,0 +1,15 @@ +/* eslint-disable @typescript-eslint/no-empty-interface */ +/* + * @Author: weisheng + * @Date: 2023-03-09 19:23:03 + * @LastEditTime: 2023-03-21 21:30:30 + * @LastEditors: weisheng + * @Description: + * @FilePath: \fant-mini-plus\src\shime-uni.d.ts + * 记得注释 + */ +export {} +declare module 'vue' { + type Hooks = App.AppInstance & Page.PageInstance + interface ComponentCustomOptions extends Hooks {} +} diff --git a/src/types.d.ts b/src/types.d.ts new file mode 100644 index 00000000..934d2395 --- /dev/null +++ b/src/types.d.ts @@ -0,0 +1,11 @@ +/* + * @Author: weisheng + * @Date: 2023-03-09 21:36:22 + * @LastEditTime: 2023-03-21 21:32:12 + * @LastEditors: weisheng + * @Description: + * @FilePath: \fant-mini-plus\src\types.d.ts + * 记得注释 + */ +//type.d.ts +declare const ROUTES: [] diff --git a/src/uni.scss b/src/uni.scss new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/src/uni.scss @@ -0,0 +1 @@ + diff --git a/src/uni_modules/wot-design-uni/changelog.md b/src/uni_modules/wot-design-uni/changelog.md new file mode 100644 index 00000000..e69de29b diff --git a/src/uni_modules/wot-design-uni/components/common/abstracts/_config.scss b/src/uni_modules/wot-design-uni/components/common/abstracts/_config.scss new file mode 100644 index 00000000..fe65cb44 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/common/abstracts/_config.scss @@ -0,0 +1,7 @@ +/** + * SCSS 配置项:命名空间以及BEM + */ +$namespace: 'wd'; +$elementSeparator: '__'; +$modifierSeparator: '--'; +$state-prefix: 'is-'; \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/common/abstracts/_function.scss b/src/uni_modules/wot-design-uni/components/common/abstracts/_function.scss new file mode 100644 index 00000000..c1fd39e6 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/common/abstracts/_function.scss @@ -0,0 +1,80 @@ +/** + * 辅助函数 + */ +@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($open-linear, $deg, $theme-color, $set, $color-list, $per-list) { + // 开启渐变 + @if $open-linear { + $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)); + } @else { + // 不开启渐变 直接使用色值 + @return $theme-color; + } +} diff --git a/src/uni_modules/wot-design-uni/components/common/abstracts/_mixin.scss b/src/uni_modules/wot-design-uni/components/common/abstracts/_mixin.scss new file mode 100644 index 00000000..26f82707 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/common/abstracts/_mixin.scss @@ -0,0 +1,265 @@ +/** + * 混合宏 + */ +@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; + } + } + } +} +/* 定义状态(m) */ +@mixin m($modifier...) { + $selectors: ""; + @each $item in $modifier { + $selectors: #{$selectors + & + $modifierSeparator + $item + ","}; + } + + @at-root { + #{$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($state) { + @at-root { + &.#{$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) { + &::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; + } +} +@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: -$size; + } + @include e(arrow-up) { + border-left: $size solid transparent; + border-right: $size solid transparent; + border-bottom: $size solid $bg; + transform: translateX(-50%); + top: -$size; + } + @include e(arrow-left) { + border-top: $size solid transparent; + border-bottom: $size solid transparent; + border-right: $size solid $bg; + transform: translateY(-50%); + left: -$size; + } + @include e(arrow-right) { + border-top: $size solid transparent; + border-bottom: $size solid transparent; + border-left: $size solid $bg; + transform: translateY(-50%); + right: -$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; + bottom: -$size/2; + transform: rotateZ(45deg); + box-shadow: $box-shadow; + } + } + @include e(arrow-up) { + transform: translateX(-50%); + &:after { + content: ""; + width: $size; + height: $size; + background-color: $bg; + position: absolute; + top: -$size/2; + transform: rotateZ(45deg); + box-shadow: $box-shadow; + } + } + @include e(arrow-left) { + transform: translateY(-50%); + &:after { + content: ""; + width: $size; + height: $size; + background-color: $bg; + position: absolute; + left: -$size/2; + transform: rotateZ(45deg); + box-shadow: $box-shadow; + } + } + @include e(arrow-right) { + transform: translateY(-50%); + &:after { + content: ""; + width: $size; + height: $size; + background-color: $bg; + position: absolute; + right: -$size/2; + transform: rotateZ(45deg); + box-shadow: $box-shadow; + } + } +} diff --git a/src/uni_modules/wot-design-uni/components/common/abstracts/variable.scss b/src/uni_modules/wot-design-uni/components/common/abstracts/variable.scss new file mode 100644 index 00000000..8a7cadee --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/common/abstracts/variable.scss @@ -0,0 +1,829 @@ +@import './function'; +$open-linear: true !default; + +/** + * UI规范基础变量 + */ +/*----------------------------------------- Theme color. start ----------------------------------------*/ +/* 主题颜色 */ +$-color-theme: $default-theme !default; // 品牌色 +$-color-white: #ffffff !default; // 用于mix的白色 +$-color-black: #000000 !default; // 用于mix的黑色 + +/* 辅助色 */ +$-color-success: #34d19d !default; // 成功色 +$-color-warning: #f0883a !default; // 警告色 +$-color-danger: #fa4350 !default; // 危险出错色 +$-color-purple: #8268de !default; // 紫色 +$-color-yellow: #f0cd1d !default; // 黄色 +$-color-blue: #2bb3ed !default; // 蓝色 + +// TODO 全部替换后如果不用此处就删除 +$-color-info: #909399 !default; +$-color-theme-light: mix($-color-white, $-color-theme, 42%) !default; // 弱化色 +$-color-theme-emph: mix($-color-black, $-color-theme, 18%) !default; // 强调色 + +/* 文字颜色(默认浅色背景下 */ +$-color-title: $-color-black !default; // 模块标题/重要正文 000 +$-color-content: mix($-color-black, $-color-white, 85%) !default; // 普通正文 262626 +$-color-secondary: mix($-color-black, $-color-white, 65%) !default; // 次要信息,注释/补充/正文 595959 +$-color-aid: mix($-color-black, $-color-white, 45%) !default; // 辅助文字字号,弱化信息,引导性/不可点文字 8c8c8c +$-color-tip: mix($-color-black, $-color-white, 25%) !default; // 失效、默认提示文字 bfbfbf +$-color-border: mix($-color-black, $-color-white, 15%) !default; // 控件边框线 d9d9d9 +$-color-border-light: mix($-color-black, $-color-white, 9%) !default; // 分割线颜色 e8e8e8 +$-color-bg: mix($-color-black, $-color-white, 4%) !default; // 背景色、禁用填充色 f5f5f5 +$-color-table-bg: mix($-color-black, $-color-white, 2%) !default; // 表头填充 fafafa + +/* 文字颜色-深背景 */ +$-color-title-indark: $-color-white !default; // 模块标题/重要正文 fff +$-color-text-indark: mix($-color-white, $-color-black, 85%) !default; // 普通正文 d9d9d9 +$-color-secondary-indark: mix($-color-white, $-color-black, 65%) !default; // 次要信息,注释/补充/正文 a6a6a6 +$-color-aid-indark: mix($-color-white, $-color-black, 45%) !default; // 次级按钮边框线 737373 +$-color-tip-indark: mix($-color-white, $-color-black, 25%) !default; // 失效、默认提示文字 404040 +$-color-border-indark: mix($-color-white, $-color-black, 15%) !default; // 控件边框线 262626 +$-color-border-light-indark: mix($-color-white, $-color-black, 9%) !default; // 分割线颜色 171717 +$-color-bg-indark: mix($-color-white, $-color-black, 4%) !default; // 背景色、禁用填充色 0a0a0a +$-color-table-bg-indark: mix($-color-white, $-color-black, 2%) !default; // 表头填充 fafafa + +// TODO 待替换 +/* 透明度 */ +$-color-inlight-title-mask: mix($-color-black, $-color-white, 100%) !default; // 模块标题/重要正文 000 +/* 图形颜色 */ +$-color-icon: mix($-color-black, $-color-white, 15%) !default; // icon颜色 +$-color-icon-active: #eee !default; // icon颜色hover +$-color-icon-disabled: #a7a7a7 !default; // icon颜色disabled +/* modal */ +$-modal-bg: rgba($-color-black, 0.65) !default; + +/*----------------------------------------- Theme color. end -------------------------------------------*/ + +/*-------------------------------- Theme color application size. start --------------------------------*/ + +/* 文字字号 */ +$-fs-big: 24px !default; // 大型标题 +$-fs-important: 19px !default; // 重要数据 +$-fs-title: 16px !default; // 标题字号/重要正文字号 +$-fs-content: 14px !default; // 普通正文 +$-fs-secondary: 12px !default; // 次要信息,注释/补充/正文 +$-fs-aid: 10px !default; // 辅助文字字号,弱化信息,引导性/不可点文字 + +/* 文字字重 */ +$-fw-medium: 500 !default; // PingFangSC-Medium +$-fw-semibold: 600 !default; // PingFangSC-Semibold + +/* 尺寸 */ +$-size-side-padding: 15px !default; // 屏幕两边留白 + +/*-------------------------------- Theme color application size. end --------------------------------*/ + +/* action-sheet */ +$-action-sheet-weight: 500 !default; // 面板字重 +$-action-sheet-radius: 16px !default; // 面板圆角大小 +$-action-sheet-action-height: 48px !default; // 单条菜单高度 +$-action-sheet-color: rgba($-color-black, 0.85) !default; // 选项名称颜色 +$-action-sheet-fs: $-fs-title !default; // 选项名称字号 +$-action-sheet-active-color: $-color-bg !default; // 点击高亮颜色 +$-action-sheet-subname-fs: $-fs-secondary !default; // 描述信息字号 +$-action-sheet-subname-color: rgba($-color-black, 0.45) !default; // 描述信息颜色 +$-action-sheet-disabled-color: rgba($-color-black, 0.25) !default; // 禁用颜色 +$-action-sheet-bg: $-color-white !default; // 菜单容器颜色(取消按钮上方的颜色) +$-action-sheet-title-height: 64px !default; // 标题高度 +$-action-sheet-title-fs: $-fs-title !default; // 标题字号 +$-action-sheet-close-fs: $-fs-title !default; // 关闭按钮大小 +$-action-sheet-close-color: rgba($-color-black, 0.65) !default; // 关闭按钮颜色 +$-action-sheet-close-top: 25px !default; // 关闭按钮距离标题顶部距离 +$-action-sheet-close-right: 15px !default; // 关闭按钮距离标题右侧距离 +$-action-sheet-cancel-color: #131415 !default; // 取消按钮颜色 +$-action-sheet-cancel-height: 44px !default; // 取消按钮高度 +$-action-sheet-cancel-bg: rgba(240,240,240,1) !default; // 取消按钮背景色 +$-action-sheet-cancel-radius: 22px !default; // 取消按钮圆角大小 +$-action-sheet-panel-padding: 12px 0 11px !default; // 自定义面板内边距大小 +$-action-sheet-panel-img-fs: 40px !default; // 自定义面板图片大小 +$-action-sheet-panel-img-radius: 4px !default; // 自定义面板图片圆角大小 + +/* badge */ +$-badge-bg: $-color-danger !default; // 背景填充颜色 +$-badge-color: #fff !default; // 文字颜色 +$-badge-fs: 12px !default; // 文字字号 +$-badge-padding: 0 5px !default; // padding +$-badge-height: 16px !default; // 高度 +$-badge-primary: $-color-theme !default; +$-badge-success: $-color-success !default; +$-badge-warning: $-color-warning !default; +$-badge-danger: $-color-danger !default; +$-badge-info: $-color-info !default; +$-badge-dot-size: 6px !default; // dot 类型大小 +$-badge-border: 2px solid $-badge-color; // 边框样式 + +/* button */ +$-button-small-height: 28px !default; // 小型按钮高度 +$-button-small-padding: 0 11px !default; // 小型按钮padding +$-button-small-fs: $-fs-secondary !default; // 小型按钮字号 +$-button-small-radius: 2px !default; // 小型按钮圆角大小 +$-button-small-loading: 14px !default; // 小型按钮loading图标大小 + +$-button-medium-height: 36px !default; // 中型按钮高度 +$-button-medium-padding: 0 15px !default; // 中型按钮padding +$-button-medium-fs: $-fs-content !default; // 中型按钮字号 +$-button-medium-radius: 4px !default; // 中型按钮圆角大小 +$-button-medium-loading: 18px !default; // 中型按钮loading图标大小 +$-button-medium-box-shadow-size: 0px 2px 4px 0px !default; // 中尺寸阴影尺寸 + +$-button-large-height: 44px !default; // 大型按钮高度 +$-button-large-padding: 0 36px !default; // 大型按钮padding +$-button-large-fs: $-fs-title !default; // 大型按钮字号 +$-button-large-radius: 8px !default; // 大型按钮圆角大小 +$-button-large-loading: 24px !default; // 大小按钮loading图标大小 +$-button-large-box-shadow-size: 0px 4px 8px 0px !default; // 大尺寸阴影尺寸 + +$-button-icon-fs: 18px !default; // 带图标的按钮的图标大小 +$-button-icon-size: 40px !default; // icon 类型按钮尺寸 +$-button-icon-color: rgba($-color-black, 0.65) !default; // icon 类型按钮颜色 +$-button-icon-active-color: $-color-icon-active !default; // icon 类型按钮点击态颜色 +$-button-icon-disabled-color: $-color-icon-disabled !default; // icon 类型按钮禁用颜色 + +$-button-normal-bg: $-color-white !default; // 默认按钮禁用背景色 +$-button-normal-color: $-color-title !default; // 文字颜色 +$-button-normal-active-color: $-color-black !default; // 文字点击态颜色 +$-button-normal-active-bg: $-color-bg !default; // 默认按钮点击态背景色 +$-button-normal-border-active-color: rgba($-color-black, 0.45) !default; // 默认按钮点击态边框色 +$-button-normal-disabled-color: rgba($-color-black, 0.25) !default; // 默认按钮禁用文字色 +$-button-normal-disabled-bg: $-color-white !default; // 默认按钮禁用背景色 +$-button-normal-border-disabled-color: $-color-bg !default; // 默认按钮禁用边框色 +$-button-border-color: mix($-color-black, $-color-white, 65%) !default; // 默认按钮边框色 + +$-button-primary-color: $-color-theme !default; // 主要按钮颜色 +$-button-primary-bg-color: resultColor( + $open-linear, + 315deg, + $-color-theme, + "dark" "light", + #4f7cf8 #668df8, + 0% 100% +) !default; // 主要按钮背景颜色 +$-button-primary-active-color: resultColor( + $open-linear, + 315deg, + darken($-color-theme, 7%), + "dark" "light", + #416bdf #4e79ee, + 0% 100% +) !default; // 主要按钮点击态颜色 +$-button-primary-plain-active-bg-color: #eff4fe !default; // 主要按钮点击态颜色 +$-button-primary-disabled-color: resultColor( + $open-linear, + 315deg, + rgba($-color-theme, 0.6), + "dark" "light", + #8FADFF #9FB8FE, + 0% 100% +) !default; // 主要按钮禁用颜色 +$-button-primary-plain-disabled-color: themeColor($-color-theme, "light", #9DB9F6) !default; // 主要按钮点击态文字颜色 +$-button-primary-box-shadow-color: rgba($-color-theme, 0.25) !default; // 主要按钮阴影颜色 + +$-button-success-color: $-color-success !default; // 成功按钮颜色 +$-button-success-active-color: mix($-color-black, $-button-success-color, 18%) !default; // 成功按钮点击态颜色 +$-button-success-disabled-color: mix($-color-white, $-button-success-color, 42%) !default; // 成功按钮禁用颜色 +$-button-success-box-shadow-color: rgba($-color-success, 0.25) !default; // 主要按钮阴影颜色 + +$-button-info-bg-color: #F0F0F0 !default; // 信息按钮背景颜色 +$-button-info-color: $-color-title !default; // 信息按钮颜色 +$-button-info-active-bg-color: #E1E1E1 !default; // 信息按钮背景颜色 +$-button-info-active-color: rgba($-color-black, 0.85) !default; // 信息按钮点击态颜色 +$-button-info-disabled-bg-color: #F0F0F0 !default; // 信息按钮禁用颜色 +$-button-info-disabled-color: rgba($-color-black, 0.09) !default; // 信息按钮禁用颜色 + +$-button-info-plain-border-color: rgba($-color-black, 0.45) !default; // 信息按钮禁用颜色 +$-button-info-plain-bg-color: $-color-white !default; // 信息按钮禁用颜色 +$-button-info-plain-disabled-bg-color: #F0F0F0 !default; // 信息按钮禁用颜色 +$-button-info-plain-active-color: rgba($-color-black, 0.45) !default; // 信息按钮禁用颜色 +$-button-info-plain-active-bg-color: #F0F0F0 !default; // 信息按钮禁用颜色 +$-button-info-plain-normal-color: rgba($-color-black, 0.85) !default; // 信息幽灵按钮默认颜色 + +$-button-warning-color: $-color-warning !default; // 警告按钮颜色 +$-button-warning-active-color: mix($-color-black, $-button-warning-color, 18%) !default; // 警告按钮点击颜色 +$-button-warning-disabled-color: mix($-color-white, $-button-warning-color, 42%) !default; // 警告按钮禁用颜色 +$-button-warning-box-shadow-color: rgba($-color-warning, 0.25) !default; // 主要按钮阴影颜色 + +$-button-error-color: $-color-danger !default; // 错误按钮颜色 +$-button-error-active-color: mix($-color-black, $-button-error-color, 18%) !default; // 错误按钮点击颜色 +$-button-error-disabled-color: mix($-color-white, $-button-error-color, 42%) !default; // 错误按钮禁用颜色 +$-button-error-box-shadow-color: rgba($-color-danger, 0.25) !default; // 主要按钮阴影颜色 + +$-button-suck-height: 50px !default; // suck 类型按钮高度 +$-button-suck-active-color: $-button-primary-plain-active-bg-color !default; // 错误按钮禁用颜色 + +/* cell */ +$-cell-padding: $-size-side-padding !default; // cell 左右padding距离 +$-cell-ling-height: 1.43 !default; // 行高 + +$-cell-group-title-fs: $-fs-title !default; // 组标题字号 +$-cell-group-padding: 13px $-cell-padding !default; // 组padding +$-cell-group-title-color: rgba($-color-black, 0.85) !default; // 组标题文字颜色 +$-cell-group-value-fs: $-fs-content !default; // 组值字号 +$-cell-group-value-color: $-color-content !default; // 组值文字颜色 + +$-cell-wrapper-padding: 13px !default; // cell 容器padding +$-cell-wrapper-padding-with-label: 16px !default; // cell 容器上下padding(有label情况下) +$-cell-icon-right: $-cell-padding !default; // 图标距离右边缘 +$-cell-icon-size: 16px !default; // 图标大小 +$-cell-title-fs: 14px !default; // 标题字号 +$-cell-title-color: rgba($-color-black, 0.85) !default; // 标题文字颜色 +$-cell-label-fs: 12px !default; // 描述信息字号 +$-cell-label-color: rgba($-color-black, 0.45) !default; // 描述信息文字颜色 +$-cell-value-fs: 14px !default; // 右侧内容字号 +$-cell-value-color: rgba($-color-black, 0.85) !default; // 右侧内容文字颜色 +$-cell-value-line-height: 1.58 !default; // 右侧内容行高 +$-cell-arrow-size: 18px !default; // 右箭头大小 +$-cell-arrow-color: rgba($-color-black, 0.25) !default; // 右箭头颜色 +$-cell-tap-bg: rgba($-color-black, 0.06) !default; // 点击态背景色 + +$-cell-title-fs-large: 16px !default; // 大尺寸标题字号 +$-cell-label-fs-large: 14px !default; // 描述信息字号 +$-cell-icon-size-large: 18px !default; // 图标大小 + +$-cell-required-color: $-color-danger !default; // 要求必填*颜色 +$-cell-required-size: 18px !default; // 必填*字号 +$-cell-vertical-top: 16px !default; // 表单类型-上下结构的间距 + +/* calendar */ +$-calendar-fs: 16px !default; +$-calendar-panel-padding: 0 12px !default; +$-calendar-panel-title-fs: 14px !default; +$-calendar-panel-title-color: rgba(0, 0, 0, 0.85) !default; +$-calendar-week-color: rgba(0, 0, 0, 0.85) !default; +$-calendar-week-height: 36px !default; +$-calendar-week-fs: 12px !default; +$-calendar-day-fs: 16px !default; +$-calendar-day-color: rgba(0, 0, 0, 0.85) !default; +$-calendar-day-fw: 500 !default; +$-calendar-day-height: 64px !default; +$-calendar-month-width: 50px !default; +$-calendar-active-color: $-color-theme !default; +$-calendar-disabled-color: rgba(0, 0, 0, 0.25) !default; +$-calendar-range-color: rgba($-color-theme, 0.09) !default; +$-calendar-active-border: 8px !default; +$-calendar-info-fs: 10px !default; + +/* checkbox */ +$-checkbox-margin: 10px !default; // 多个复选框距离 +$-checkbox-bg: $-color-white !default; // 多个复选框距离 +$-checkbox-label-margin: 9px !default; // 右侧文字与左侧图标距离 +$-checkbox-size: 16px !default; // 左侧图标尺寸 +$-checkbox-icon-size: 14px !default; // 左侧图标尺寸 +$-checkbox-border-color: #dcdcdc !default; // 左侧图标边框颜色 +$-checkbox-check-color: $-color-white !default; // 左侧图标边框颜色 +$-checkbox-label-fs: 14px !default; // 右侧文字字号 +$-checkbox-label-color: rgba($-color-black, 0.85); // 右侧文字颜色 +$-checkbox-checked-color: $-color-theme !default; // 选中颜色 + +$-checkbox-disabled-color: rgba($-color-black, 0.04) !default; // 禁用背景颜色 +$-checkbox-disabled-label-color: rgba($-color-black, 0.25) !default; // 禁用文字颜色 +$-checkbox-disabled-check-color: rgba($-color-black, 0.15) !default; // 禁用图标颜色 +$-checkbox-disabled-check-bg: rgba($-color-black, 0.15) !default; // 禁用边框背景颜色 +$-checkbox-square-radius: 4px !default; // 方型圆角大小 + +$-checkbox-large-size: 18px !default; // 左侧图标尺寸 +$-checkbox-large-label-fs: 16px !default; // 右侧文字字号 + +$-checkbox-button-height: 32px !default; // 按钮模式复选框高 +$-checkbox-button-min-width: 78px !default; // 按钮模式最小宽 +$-checkbox-button-radius: 16px !default; // 按钮圆角大小 +$-checkbox-button-bg: rgba($-color-black, 0.04) !default; // 按钮模式背景颜色 +$-checkbox-button-font-size: 14px !default; // 按钮模式字号 +$-checkbox-button-border: #f5f5f5 !default; // 按钮边框颜色 +$-checkbox-button-disabled-border: rgba($-color-black, 0.15) !default; // 按钮禁用边框颜色 + +/* collapse */ +$-collapse-side-padding: $-size-side-padding !default; // 左右间距 +$-collapse-body-padding: 14px 25px !default; // body padding +$-collapse-header-padding: 13px $-size-side-padding !default; // 头部padding +$-collapse-title-color: rgba($-color-black, 0.85) !default; // 标题颜色 +$-collapse-title-fs: 16px !default; // 标题字号 +$-collapse-arrow-size: 18px !default; // 箭头大小 +$-collapse-arrow-color: #d8d8d8 !default; // 箭头颜色 +$-collapse-body-fs: 14px !default; // 内容字号 +$-collapse-body-color: rgba($-color-black, 0.65) !default; // 内容颜色 +$-collapse-disabled-color: rgba($-color-black, 0.15) !default; // 禁用颜色 +$-collapse-retract-fs: 14px !default; // 更多 字号 +$-collapse-more-color: $-color-theme !default; // 更多 颜色 + +/* divider */ +$-divider-padding: 0 $-size-side-padding !default; // 两边间距 +$-divider-color: rgba($-color-black, 0.45) !default; // 字体颜色 +$-divider-line-color: rgba($-color-black, 0.15) !default; // 线条颜色 +$-divider-fs: 14px !default; // 字体大小 + +/* drop-menu */ +$-drop-menu-height: 48px; // 展示选中项的高度 +$-drop-menu-color: $-color-content !default; // 展示选中项的颜色 +$-drop-menu-fs: $-fs-content !default; // 展示选中项的字号 +$-drop-menu-side-padding: $-size-side-padding !default; // 两边留白间距 +$-drop-menu-disabled-color: rgba($-color-black, 0.25) !default; // 禁用颜色 +$-drop-menu-item-height: 48px; // 选项高度 +$-drop-menu-item-color: $-color-content !default; // 选项颜色 +$-drop-menu-item-fs: $-fs-content !default; // 选项字号 +$-drop-menu-item-color-active: $-color-theme !default; // 选中颜色 +$-drop-menu-item-color-tip: rgba($-color-black, 0.45) !default; // 提示文字颜色 +$-drop-menu-item-fs-tip: $-fs-secondary !default; // 提示文字字号 +$-drop-menu-option-check-size: 20px !default; // check 图标大小 +$-drop-menu-line-color: resultColor( + $open-linear, + 315deg, + $-color-theme, + "dark" "light", + rgba(81, 124, 240, 1) rgba(118, 158, 245, 1), + 0% 100% +) !default; // 下划线颜色 +$-drop-menu-line-height: 3px !default; // 下划线高度 + +/* input-number */ +$-input-number-color: #262626 !default; // 文字颜色 +$-input-number-border-color: #e8e8e8 !default; // 边框颜色 +$-input-number-disabled-color: rgba($-color-black, 0.25) !default; // 禁用颜色 +$-input-number-height: 24px !default; // 加减号按钮高度 +$-input-number-btn-width: 26px !default; // 加减号按钮宽度 +$-input-number-input-width: 36px !default; // 输入框宽度 +$-input-number-radius: 4px !default; // 加减号按钮圆角大小 +$-input-number-fs: 12px !default; // 输入框字号 +$-input-number-icon-size: 14px !default; // 加减号图标大小 +$-input-number-icon-color: rgba($-color-black, 0.65) !default; // icon颜色 + +/* input */ +$-input-border-color: #dadada !default; // 无label边框颜色 +$-input-not-empty-border-color: #262626 !default; // 无label边框颜色 +$-input-fs: $-cell-title-fs !default; // 字号 +$-input-fs-large: $-cell-title-fs-large !default; // 大尺寸字号 +$-input-icon-margin: 8px !default; // 图标距离 +$-input-color: #262626 !default; // 文字颜色 +$-input-placeholder-color: #bfbfbf !default; // 占位符颜色 +$-input-disabled-color: #d9d9d9 !default; // 输入框禁用颜色 +$-input-error-color: $-color-danger !default; // 输入框错误颜色 +$-input-icon-color: #bfbfbf !default; // 图标颜色 +$-input-clear-color: #585858 !default; // 关闭按钮颜色 +$-input-count-color: #bfbfbf !default; // 计数文字颜色 +$-input-count-current-color: #262626 !default; // 当前长度颜色 +$-input-label-padding: 11px 10px 11px 0 !default; // label padding +$-input-bg: $-color-white !default; // 默认背景颜色 + +$-input-cell-bg: $-color-white !default; // cell 类型背景色 +$-input-cell-border-color: $-color-border-light !default; // cell 类型边框颜色 +$-input-cell-padding: $-size-side-padding !default; // cell 类型左右间距 +$-input-cell-height: 46px !default; // cell 高度 +$-input-cell-height-large: 48px !default; // cell 大尺寸高度 +$-input-cell-label-width: 33% !default; // cell 下 label 的宽度 +$-input-inner-height: 34px !default; // 非cell和textarea下的高度 +$-input-inner-height-no-border: 20px !default; // 无边框下的高度 +$-input-inner-padding: 6px 0 !default; // 非cell和textarea下的padding +$-input-count-fs: 14px !default; // 计数字号 +$-input-count-fs-large: 14px !default; // 大尺寸计数字号 +$-input-icon-size: 16px !default; // 图标大小 +$-input-icon-size-large: 18px !default; // 大尺寸图标大小 +$-input-textarea-padding: 15px !default; // textarea下的padding + +/* loadmore */ +$-loadmore-height: 48px !default; // 高度 +$-loadmore-color: rgba($-color-black, 0.45) !default; // 颜色 +$-loadmore-fs: 14px !default; // 字号 +$-loadmore-error-color: $-color-theme !default; // 点击重试颜色 + +/* message-box */ +$-message-box-width: 300px !default; // 宽度 +$-message-box-bg: $-color-white !default; // 默认背景颜色 +$-message-box-radius: 16px !default; // 圆角大小 +$-message-box-padding: 25px 24px 0 !default; // 主体内容padding +$-message-box-title-fs: 16px !default; // 标题字号 +$-message-box-title-color: rgba($-color-black, 0.85) !default; // 标题颜色 +$-message-box-content-fs: 14px !default; // 内容字号 +$-message-box-content-color: #666666 !default; // 内容颜色 +$-message-box-content-max-height: 264px !default; // 内容最大高度 +$-message-box-content-scrollbar-width: 4px !default; // 内容滚动条宽度 +$-message-box-content-scrollbar-color: rgba($-color-black, 0.1) !default; // 内容滚动条颜色 +$-message-box-input-error-color: $-input-error-color !default; // 输入框错误颜色 + +/* notice-bar */ +$-notice-bar-fs: 12px !default; // 字号 +$-notice-bar-line-height: 18px !default; // 行高 +$-notice-bar-border-radius: 8px !default; // 圆角 +$-notice-bar-padding: 9px 20px 9px 15px !default; // 非换行下的padding +$-notice-bar-warning-bg: #fff6c8 !default; // 背景色 +$-notice-bar-info-bg: #f4f9ff !default; // 背景色 +$-notice-bar-danger-bg: #feeced !default; // 背景色 +$-notice-bar-warning-color: $-color-warning !default; // 文字和图标颜色 +$-notice-bar-info-color: $-color-theme !default; // 文字和图标颜色 +$-notice-bar-danger-color: $-color-danger !default; // 文字和图标颜色 +$-notice-bar-prefix-size: 18px !default; // 图标大小 +$-notice-bar-close-bg: rgba($-color-black, 0.15) !default; // 右侧关闭按钮背景颜色 +$-notice-bar-close-size: 18px !default; // 右侧关闭按钮背景颜色 +$-notice-bar-close-color: $-color-white !default; // 右侧关闭按钮颜色 +$-notice-bar-close-size: 10px !default; // 关闭按钮大小 +$-notice-bar-wrap-padding: 14px $-size-side-padding !default; // 换行下的padding + +/* pagination */ +$-pagination-content-padding: 10px 15px !default; +$-pagination-message-padding: 1px 0 16px 0 !default; +$-pagination-message-fs: 12px !default; +$-pagination-message-color: rgba($-color-black, 0.69) !default; +$-pagination-nav-border: 1px solid rgba($-color-black, 0.45) !default; +$-pagination-nav-border-radius: 16px !default; +$-pagination-nav-fs: 12px !default; +$-pagination-nav-width: 60px !default; +$-pagination-nav-color: rgba($-color-black, 0.85) !default; +$-pagination-nav-content-fs: 12px !default; +$-pagination-nav-sepatator-padding: 0 4px !default; +$-pagination-nav-current-color: $-color-theme !default; + +/* picker */ +$-picker-toolbar-height: 54px !default; // toolbar 操作条的高度 +$-picker-action-height: 16px !default; // toolbar 操作条的高度 +$-picker-toolbar-finish-color: $-color-theme !default; // toolbar 操作条完成按钮的颜色 +$-picker-toolbar-cancel-color: #666666 !default; // toolbar 操作条的边框颜色 +$-picker-toolbar-fs: $-fs-title !default; // toolbar 操作条的字号 +$-picker-toolbar-title-color: rgba($-color-black, 0.85) !default; // toolbar 操作台的标题颜色 +$-picker-column-fs: 16px !default; // 选择器选项的字号 +$-picker-bg: $-color-white !default; // 选择器选项的字号 +$-picker-column-active-fs: 18px !default; // 选择器选项被选中的字号 +$-picker-column-color: rgba($-color-black, 0.85) !default; // 选择器选项的颜色 +$-picker-column-height: 210px !default; // 列高 滚筒外部的高度 +$-picker-column-item-height: 35px !default; // 列高 滚筒外部的高度 +$-picker-column-select-bg: #f5f5f5 !default; +$-picker-loading-button-color: rgba($-color-black, 0.25) !default; // loading 背景颜色 +$-picker-column-padding: 0 $-size-side-padding !default; // 选项内间距 + +$-picker-column-disabled-color: rgba($-color-black, 0.25) !default; // 选择器选项禁用的颜色 +$-picker-mask: linear-gradient(180deg, hsla(0, 0%, 100%, 0.9), hsla(0, 0%, 100%, 0.25)), + linear-gradient(0deg, hsla(0, 0%, 100%, 0.9), hsla(0, 0%, 100%, 0.25)) !default; // 上下阴影 +$-picker-loading-bg: rgba($-color-white, 0.8) !default; // loading 背景颜色 +$-picker-region-separator-color: rgba($-color-black, 0.65) !default; // 区域选择文字颜色 +$-picker-cell-arrow-size-large: $-cell-icon-size !default; // cell 类型的大尺寸 右侧icon尺寸 + +$-picker-region-color: rgba($-color-black, 0.45) !default; // 区域选择文字颜色 +$-picker-region-bg-active-color: resultColor( + $open-linear, + 315deg, + $-color-theme, + "dark" "light" "light", + rgba(79,124,248,1) rgba(102,141,248,1) rgba(102,141,248,1), + 0% 100% 100% +) !default; // 区域选择激活选中背景颜色 + +$-picker-region-fs: 14px !default; // 区域选择文字字号 + +/* col-picker */ +$-col-picker-selected-height: 44px !default; // 弹框顶部值高度 +$-col-picker-selected-padding: 0 16px !default; // 弹框顶部值左右间距 +$-col-picker-selected-fs: 14px !default; // 弹框顶部值字号 +$-col-picker-selected-color: rgba($-color-black, 0.85) !default; // 弹框顶部值文字颜色 +$-col-picker-selected-fw: 700 !default; // 弹框顶部值高亮字重 +$-col-picker-line-width: 16px !default; // 弹框顶部值高亮线条宽度 +$-col-picker-line-height: 3px !default; // 弹框顶部值高亮线条高度 +$-col-picker-line-color: linear-gradient(315deg, rgba(81,124,240,1), rgba(118,158,245,1)) !default; // 弹框顶部值高亮线条颜色 +$-col-picker-line-box-shadow: 0px 1px 2px 0px rgba(1, 87, 255, 0.2) !default; // 弹框顶部值高亮线条阴影 +$-col-picker-list-height: 53vh !default; // 弹框列表高度 +$-col-picker-list-padding-bottom: 30px !default; // 弹框列表底部间距 +$-col-picker-list-color: rgba($-color-black, 0.85) !default; // 弹框列表文字颜色 +$-col-picker-list-color-disabled: rgba($-color-black, 0.15) !default; // 弹框列表文字禁用颜色 +$-col-picker-list-color-tip: rgba($-color-black, 0.45) !default; // 弹框列表提示文字颜色 +$-col-picker-list-fs: 14px !default; // 弹框列表文字字号 +$-col-picker-list-fs-tip: 12px !default; // 弹框列表提示文字字号 +$-col-picker-list-item-padding: 12px 15px !default; // 弹框列表选项间距 +$-col-picker-list-checked-icon-size: 18px !default; // 弹框列表选中箭头大小 +$-col-picker-list-color-checked: $-color-theme !default; // 弹框列表选中选项颜色 + +/* popup */ +$-popup-close-size: 24px !default; // 关闭按钮尺寸 +$-popup-close-color: #666 !default; // 关闭按钮颜色 + +/* progress */ +$-progress-padding: 9px 0 8px !default; // 进度条内边距 +$-progress-bg: rgba(229,229,229,1) !default; // 进度条底色 +$-progress-danger-color: $-color-danger !default; // 进度条danger颜色 +$-progress-success-color: $-color-success !default; // 进度条success进度条颜色 +$-progress-color: resultColor( + $open-linear, + 315deg, + $-color-theme, + "dark" "light", + #517CF0 #769EF5 , + 0% 100% +) !default; // 进度条渐变色 +$-progress-linear-success-color: resultColor( + $open-linear, + 315deg, + $-color-theme, + "dark" "light", + #20B080 #2BD69D , + 0% 100% +) !default; // success进度条渐变色 +$-progress-linear-danger-color: resultColor( + $open-linear, + 315deg, + $-color-theme, + "dark" "light", + #E04350 #FF5964 , + 0% 100% +) !default; // danger进度条渐变色 +$-progress-height: 3px !default; // 进度条高度 +$-progress-label-color: #333 !default; // 文字颜色 +$-progress-label-fs: 14px !default; // 文字字号 +$-progress-icon-fs: 18px !default; // 图标字号 + +/* radio */ +$-radio-margin: $-checkbox-margin !default; // 多个单选框距离 +$-radio-label-margin: $-checkbox-label-margin !default; // 右侧文字与左侧图标距离 +$-radio-size: 16px !default; // 左侧图标尺寸 +$-radio-bg: $-checkbox-bg !default; // 左侧图标尺寸 +$-radio-label-fs: $-checkbox-label-fs !default; // 右侧文字字号 +$-radio-label-color: $-checkbox-label-color !default; // 右侧文字颜色 +$-radio-checked-color: $-checkbox-checked-color !default; // 选中颜色 +$-radio-disabled-color: $-checkbox-disabled-color !default; // 禁用颜色 +$-radio-disabled-label-color: $-checkbox-disabled-label-color !default; // 禁用文字颜色 + +$-radio-large-size: $-checkbox-large-size !default; // 左侧图标尺寸 +$-radio-large-label-fs: $-checkbox-large-label-fs !default; // 右侧文字字号 + +$-radio-button-height: $-checkbox-button-height !default; // 按钮模式复选框高 +$-radio-button-min-width: 60px !default; // 按钮模式最小宽 +$-radio-button-max-width: 144px !default; // 按钮模式最大宽 +$-radio-button-radius: $-checkbox-button-radius !default; // 按钮圆角大小 +$-radio-button-bg: $-checkbox-button-bg !default; // 按钮模式背景颜色 +$-radio-button-fs: $-checkbox-button-font-size !default; // 按钮模式字号 +$-radio-button-border: $-checkbox-button-border !default; // 按钮边框颜色 +$-radio-button-disabled-border: $-checkbox-button-disabled-border !default; // 按钮禁用边框颜色 + +$-radio-dot-size: 8px !default; // 单选dot模式圆点尺寸 +$-radio-dot-large-size: 10px !default; // 单选dot模式大尺寸圆点尺寸 +$-radio-dot-checked-bg: $-color-theme !default; // 单选dot模式选中背景色 +$-radio-dot-checked-border-color: $-color-theme !default; // 单选dot模式选中边框色 +$-radio-dot-border-color: #dcdcdc !default; // 单选dot模式边框色 +$-radio-dot-disabled-border: #d9d9d9 !default; // 单选dot模式禁用边框颜色 +$-radio-dot-disabled-bg: #d9d9d9 !default; // 单选dot模式禁用背景颜色 + +/* search */ +$-search-side-padding: $-size-side-padding !default; // 左右间距 +$-search-padding: 10px 0 10px $-search-side-padding !default; // 不包含取消按钮的间距 +$-search-input-radius: 15px !default; // 输入框圆角大小 +$-search-input-bg: $-color-bg !default; // 输入框背景色 +$-search-input-height: 30px !default; // 输入框高度 +$-search-input-padding: 0 32px 0 42px !default; // 输入框间距 +$-search-input-fs: $-fs-content !default; // 输入框字号 +$-search-input-color: #262626 !default; // 输入框文字颜色 +$-search-icon-color: $-color-icon !default; // 图标颜色 +$-search-placeholder-color: #bfbfbf !default; // placeholder 颜色 +$-search-cancel-padding: 0 $-search-side-padding 0 10px !default; // 取消按钮间距 +$-search-cancel-fs: $-fs-title !default; // 取消按钮字号 +$-search-cancel-color: rgba($-color-black, 0.65) !default; // 取消按钮颜色 +$-search-light-bg: $-color-bg !default; // light 类型的容器背景色 + +/* slider */ +$-slider-fs: $-fs-content !default; // 字体大小 +$-slider-handle-radius: 12px !default; // 滑块半径 +$-slider-handle-bg: resultColor( + $open-linear, + 139deg, + $-color-theme, + "dark" "light", + #FFFFFF #F7F7F7 , + 0% 100% +) !default; // 滑块背景 +$-slider-axie-height: 3px !default; // 滑轴高度 +$-slider-color: #333 !default; // 字体颜色 +$-slider-axie-bg: #E5E5E5 !default; // 滑轴的默认背景色 +$-slider-line-color: resultColor( + $open-linear, + 315deg, + $-color-theme, + "dark" "light", + #517CF0 #769EF5 , + 0% 100% +) !default; // 进度条颜色 +$-slider-disabled-color: rgba($-color-black, 0.25) !default; // 禁用状态下字体颜色 + +/* sort-button */ +$-sort-button-fs: $-fs-content !default; // 字号 +$-sort-button-color: $-color-content !default; // 颜色 +$-sort-button-height: 48px !default; // 高度 +$-sort-button-line-height: 3px !default; // 下划线高度 +$-sort-button-line-color: resultColor( + $open-linear, + 315deg, + $-color-theme, + "dark" "light", + rgba(81, 124, 240, 1) rgba(118, 158, 245, 1), + 0% 100% +) !default; // 下划线颜色 + +/* steps */ +$-steps-icon-size: 22px !default; // 图标尺寸 +$-steps-inactive-color: rgba($-color-black, 0.25) !default; // 等待状态文字颜色 +$-steps-finished-color: $-color-theme !default; // 完成文字颜色 +$-steps-icon-text-fs: $-fs-content !default; // 数字图标文字字号 +$-steps-error-color: $-color-danger !default; // 异常颜色 +$-steps-title-fs: $-fs-content !default; // 标题字号 +$-steps-title-fw: $-fw-medium !default; // 标题字重 +$-steps-label-fs: $-fs-secondary !default; // 描述信息字号 +$-steps-description-color: rgba($-color-black, 0.45) !default; // 描述信息颜色 +$-steps-is-icon-width: 30px !default; // 自定义图标的宽度,给左右留白 +$-steps-line-color: rgba($-color-black, 0.15) !default; // 线条颜色 +$-steps-dot-size: 7px !default; // 点状大小 +$-steps-dot-active-size: 9px !default; // 点状高亮大小 + +/* switch */ +$-switch-width: 51px !default; // 宽度 +$-switch-height: 32px !default; // 高度 +$-switch-circle-size: 28px !default; // 圆点大小 +$-switch-border-color: #e5e5e5 !default; // 边框颜色选中状态背景颜色 +$-switch-active-color: resultColor( + $open-linear, + 315deg, + $-color-theme, + "light" "dark", + #4f7cf8 #668df8, + 0% 100% +) !default; // 选中状态背景 +$-switch-active-shadow-color: rgba(0, 83, 162, 0.5) !default; // 选中状态shadow颜色 +$-switch-inactive-color: resultColor( + $open-linear, + 315deg, + #dadada, + "light" "dark" "dark", + #f2f2f2 #dadada #668df8, + 0% 100% 100% +) !default; // 非选中背景颜色 +$-switch-inactive-shadow-color: rgba(155, 155, 155, 0.5) !default; // 非选中状态shadow颜色 + + +/* tabs */ +$-tabs-nav-arrow-fs: 18px !default; // 全部Icon字号 +$-tabs-nav-arrow-open-fs: 14px !default; // 展开Icon字号 +$-tabs-nav-width: 100vw; // tabs 头部切换宽度 +$-tabs-nav-height: 42px !default; // 头部切换高度 +$-tabs-nav-fs: $-fs-content !default; // 头部切换文字大小 +$-tabs-nav-color: rgba($-color-black, 0.85) !default; // 头部切换文字颜色 +$-tabs-nav-bg: $-color-white !default; // 背景颜色 +$-tabs-nav-active-color: $-color-theme !default; // 头部高亮颜色 +$-tabs-nav-disabled-color: rgba($-color-black, 0.25) !default; // 头部禁用颜色 +$-tabs-nav-line-height: 3px !default; // 高亮边框高度 +$-tabs-nav-line-bg-color: resultColor( + $open-linear, + 315deg, + $-color-theme, + "dark" "light", + rgba(81, 124, 240, 1) rgba(118, 158, 245, 1), + 0% 100% +) !default; // 底部条颜色 +$-tabs-nav-map-fs: $-fs-content !default; // map 类型按钮字号 +$-tabs-nav-map-color: rgba($-color-black, 0.85) !default; // map 类型按钮文字颜色 +$-tabs-nav-map-arrow-color: rgba($-color-black, 0.65) !default; // map 类型箭头颜色 +$-tabs-nav-map-btn-before-bg: linear-gradient( + 270deg, + rgba(255, 255, 255, 1) 1%, + rgba(255, 255, 255, 0) 100% +) !default; // 左侧map遮罩阴影 +$-tabs-nav-map-button-back-color: rgba($-color-black, 0.04) !default; // map 类型按钮边框颜色 +$-tabs-nav-map-button-radius: 16px !default; // map 类型按钮圆角大小 +$-tabs-nav-map-modal-bg: $-modal-bg !default; // map 类型蒙层背景色 + +/* tag */ +$-tag-fs: $-fs-secondary !default; // 字号 +$-tag-color: $-color-white !default; // 字体颜色 +$-tag-small-fs: $-fs-aid !default; // 小尺寸字号 +$-tag-info-color: #585858 !default; // info 颜色 +$-tag-primary-color: $-color-theme !default; // 主颜色 +$-tag-danger-color: rgba(250,67,80,1) !default; // danger 颜色 +$-tag-warning-color: rgba(255,144,0,1) !default; // warning 颜色 +$-tag-success-color: rgba(51,187,68,1) !default; // success 颜色 +$-tag-info-bg: resultColor( + $open-linear, + 49deg, + $-color-black, + "dark" "light", + #808080 #999999, + 0% 100% +) !default; // info 背景颜色 +$-tag-primary-bg: resultColor( + $open-linear, + 49deg, + $-color-theme, + "dark" "light", + rgba(81,124,240,1) rgba(118,158,245,1), + 0% 100% +) !default; // 主背景颜色 +$-tag-danger-bg: resultColor( + $open-linear, + 44deg, + $-color-danger, + "dark" "light", + rgba(230,62,70,1) rgba(255,64,73,1), + 0% 100% +) !default; // danger 背景颜色 +$-tag-warning-bg: resultColor( + $open-linear, + 45deg, + $-color-warning, + "dark" "light", + rgba(230,113,55,1) rgba(255,151,76,1), + 0% 100% +) !default; // warning 背景颜色 +$-tag-success-bg: resultColor( + $open-linear, + 45deg, + $-color-success, + "dark" "light", + rgba(32,176,128,1) rgba(43,214,157,1), + 0% 100% +) !default; // success 背景颜色 +$-tag-round-color: rgba(102, 102, 102, 1) !default; // round 字体颜色 +$-tag-round-border-color: rgba(225,225,225,1) !default; // round 边框颜色 +$-tag-round-radius: 12px !default; // round 圆角大小 +$-tag-mark-radius: 6px 2px 6px 2px !default; // mark 圆角大小 +$-tag-close-size: 14px !default; // 关闭按钮字号 +$-tag-close-color: $-tag-info-color !default; // 关闭按钮颜色 +$-tag-close-active-color: rgba($-color-black, 0.45) !default; // 关闭按钮 active 颜色 + +/* toast */ +$-toast-padding: 16px 24px !default; // padding +$-toast-max-width: 300px !default; // 最大宽度 +$-toast-radius: 8px !default; // 圆角大小 +$-toast-bg: $-modal-bg !default; // 背景色 +$-toast-fs: $-fs-content !default; // 字号 +$-toast-with-icon-min-width: 150px !default; // 有图标的情况下最小宽度 +$-toast-icon-size: 39px !default; // 图标大小 +$-toast-loading-padding: 10px !default; // loading 下的padding +$-toast-box-shadow: 0px 6px 16px 0px rgba($-color-black, 0.08) !default; // 外部阴影 + +/* tooltip */ +$-tooltip-bg: rgba(38, 39, 40, 0.8) !default; // 背景色 +$-tooltip-color: $-color-white !default; // 文字颜色 +$-tooltip-radius: 8px !default; // 圆角大小 +$-tooltip-arrow-size: 9px !default; // 箭头大小 +$-tooltip-fs: $-fs-content !default; // 字号 +$-tooltip-blur: 10px !default; // 背景高斯模糊效果 +$-tooltip-padding: 9px 20px !default; // 间距 +$-tooltip-close-size: 6px !default; // 背景高斯模糊效果 +$-tooltip-z-index: 500 !default; +$-tooltip-line-height: 18px !default; // 行高 + +/* popover */ +$-popover-bg: $-color-white !default; // 背景色 +$-popover-color: rgba($-color-black, 0.85) !default; // 文字颜色 +$-popover-box-shadow: 0px 2px 10px 0px rgba($-color-black, 0.1) !default; // 阴影颜色 +$-popover-arrow-box-shadow: 0px 2px 10px 0px rgba($-color-black, 0.2) !default; // 阴影颜色 +$-popover-border-color: rgba($-color-black, 0.09) !default; // 阴影颜色 +$-popover-radius: 4px !default; // 圆角大小 +$-popover-arrow-size: 6px !default; // 箭头大小 +$-popover-fs: $-fs-content !default; // 字号 +$-popover-padding: 15px !default; // 间距 +$-popover-line-height: 18px !default; // 行高 +$-popover-z-index: $-tooltip-z-index !default; + +/* grid-item */ +$-grid-item-fs: 12px !default; // 字号 +$-grid-item-bg: $-color-white !default; // 字号 +$-grid-item-padding: 14px 0px !default; // 内容的 padding +$-grid-item-border-color: $-color-border-light !default; // 边框颜色 + +/* statustip */ +$-statustip-fs: $-fs-content !default; // 字号 +$-statustip-color: rgba($-color-black, 0.45) !default; // 文字颜色 +$-statustip-line-height: 16px !default; // 文字行高 +$-statustip-padding: 5px 10px !default; // 间距 + +/* card */ +$-card-bg: $-color-white !default; // 背景色 +$-card-fs: $-fs-content !default; // 卡片字号 +$-card-padding: 0 $-size-side-padding !default; // 内边距 +$-card-footer-padding: 12px 0 16px !default; // 底部内边距 +$-card-shadow-color: 0px 4px 8px 0px rgba($-color-black, 0.02) !default; // 阴影 +$-card-radius: 8px !default; // 圆角大小 +$-card-line-height: 1.1 !default; // 行高 +$-card-margin: 0 $-size-side-padding !default; // 外边距 +$-card-title-color: rgba($-color-black, 0.85) !default; // 标题颜色 +$-card-title-fs: $-fs-title !default; // 矩形卡片标题字号 +$-card-content-border-color: rgba($-color-black, 0.09) !default; // 内容边框 +$-card-rectangle-title-padding: 15px 15px 12px !default; // 矩形卡片头部内边距 +$-card-rectangle-content-padding: 16px 0 !default; // 矩形卡片内容内边距 +$-card-rectangle-footer-padding: 12px 0 !default; // 矩形卡片底部内边距 +$-card-content-color: rgba($-color-black, 0.45) !default; // 文本内容颜色 +$-card-content-line-height: 1.428 !default; // 文本内容行高 +$-card-content-margin: 13px 0 12px !default; // 内容外边距 +$-card-content-rectangle-margin: 14px 0 12px !default; // 矩形卡片内容外边距 + +/* upload */ +$-upload-size: 80px !default; // upload的外边框默认尺寸 +$-upload-evoke-icon-size: 32px !default; // 唤起项的图标大小 +$-upload-evoke-bg: rgba($-color-black, 0.04) !default; // 唤起项的背景色 +$-upload-evoke-color: rgba($-color-black, 0.25) !default; // 唤起项的图标颜色 +$-upload-evoke-disabled-color: rgba($-color-black, 0.09) !default; // 唤起项禁用颜色 +$-upload-close-icon-size: 16px !default; // 移除按钮尺寸 +$-upload-close-icon-color: rgba($-color-black, 0.65) !default; // 移除按钮颜色 +$-upload-progress-fs: 14px !default; // 进度文字字号 +$-upload-preview-name-fs: 12px !default; // 预览图片名字号 +$-upload-preview-icon-size: 24px !default; // 预览内部图标尺寸 +$-upload-preview-name-bg: rgba($-color-black, 0.6) !default; // 预览文件名背景色 +$-upload-preview-name-height: 22px !default; // 预览文件名背景高度 + +/* curtain */ +$-curtain-content-radius: 24px !default; // 内容圆角 +$-curtain-content-close-color: $-color-white !default; // 关闭按钮颜色 +$-curtain-content-close-fs: $-fs-big !default; // 关闭按钮大小 diff --git a/src/uni_modules/wot-design-uni/components/common/base64.ts b/src/uni_modules/wot-design-uni/components/common/base64.ts new file mode 100644 index 00000000..3fc8a548 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/common/base64.ts @@ -0,0 +1,29 @@ +const _b64chars = [...'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'] +const _mkUriSafe = (src) => src.replace(/[+/]/g, (m0) => (m0 === '+' ? '-' : '_')).replace(/=+$/m, '') +const fromUint8Array = (src, rfc4648 = false) => { + 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 = + typeof btoa === 'function' + ? (s) => btoa(s) + : (s) => { + if (s.charCodeAt() > 255) { + throw new RangeError('The string contains invalid characters.') + } + return fromUint8Array(Uint8Array.from(s, (c: any) => c.charCodeAt(0))) + } +const utob = (src) => unescape(encodeURIComponent(src)) + +export default function encode(src, rfc4648 = false) { + const b64 = _btoa(utob(src)) + return rfc4648 ? _mkUriSafe(b64) : b64 +} diff --git a/src/uni_modules/wot-design-uni/components/common/clickoutside.js b/src/uni_modules/wot-design-uni/components/common/clickoutside.js new file mode 100644 index 00000000..ffc65387 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/common/clickoutside.js @@ -0,0 +1,27 @@ +let queue = [] +let outsideId = 1 + +export function pushToQueue(comp) { + comp.outsideId = ++outsideId + queue.push(comp) +} + +export function removeFromQueue(comp) { + queue = queue.filter((item) => { + return item.outsideId !== comp.outsideId + }) +} + +export function closeOther(comp) { + queue.forEach((item) => { + if (item.outsideId !== comp.outsideId) { + item.close() + } + }) +} + +export default function closeOutside() { + queue.forEach((item) => { + item.close() + }) +} diff --git a/src/uni_modules/wot-design-uni/components/common/component.md b/src/uni_modules/wot-design-uni/components/common/component.md new file mode 100644 index 00000000..1c83a666 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/common/component.md @@ -0,0 +1,53 @@ +### 使用方法 +```javascript + +import VueComponent from '/packages/common/component.js' + +VueComponent({ + props: {}, + beforeCreate(){}, + created(){}, + mounted(){}, + destroyed(){} +}) +``` + +### 映射关系 + +>如果使用封装的`VueComponent`组件,以下小程序`原生属性/方法`必须替换为对应的`封装属性/方法` + +| 封装 | 原生 | +|--------------|----------- | +| props | properties | +| beforeCreate | created | +| created | attached | +| mounted | ready | +| destroyed | detached | + +### 全局样式类 + +* 使用VueComponent组件时,会自动添加custom-class类,外部可以通过此类修改组件内部的样式。 +```html + + 确定 + +``` +```css +.custom{ + background-color: red; +} +``` +### 原生生命周期 + +> 必须要弄清楚的声明周期,执行顺序按照降序排列。 + +| hook | 描述 | +|-------------------------|----------- | +| properties | 类型vue的props。 | +| data | 类型vue的data。 | +| created | 在组件实例刚刚被创建时执行,此时可以拿到data,但是拿不到props的最新值,只能拿到默认值,不能调用setData,可以操作设置this。 | +| properties:observer | 如果实例初始化时给properties传值,会触发observer,先于attached执行 | +| attached | 在组件实例进入页面节点树时执行,可以调用setData。 | +| relations | DOM build,父组件建立连接,双方可以在此hook互相操作对方。 | +| ready | DOM painted。 | + diff --git a/src/uni_modules/wot-design-uni/components/common/dayjs.min.js b/src/uni_modules/wot-design-uni/components/common/dayjs.min.js new file mode 100644 index 00000000..11b8f805 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/common/dayjs.min.js @@ -0,0 +1,2 @@ +/* eslint-disable */ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):t.dayjs=e()}(this,function(){"use strict";const t=60,e=60*t,s=24*e,r=7*s,n=(t,e,s)=>!t||t.length>=e?t:`${Array(e+1-t.length).join(s)}${t}`;class i{constructor(t){this.utc=!1;const e=this.parseConfig(t);this.date=new Date(e),this.timeZone=this.date.getTimezoneOffset()/60,this.timeZoneString=n(String(-1*this.timeZone).replace(/^(.)?(\d)/,"$10$200"),5,"+"),this.mYear=this.date.getFullYear(),this.mMonth=this.date.getMonth(),this.mDay=this.date.getDate(),this.mWeek=this.date.getDay(),this.mHour=this.date.getHours(),this.mMinute=this.date.getMinutes(),this.mSecond=this.date.getSeconds()}parseConfig(t){if(!t)return new Date;if(t instanceof Date)return t;if(/^(\d){8}$/.test(t)){return this.utc=!0,`${t.substr(0,4)}-${t.substr(4,2)}-${t.substr(6,2)}`}return t}year(){return this.mYear}month(){return this.mMonth}unix(){const t=this.utc?60*this.timeZone*60*1e3:0;return Math.floor((this.date.getTime()+t)/1e3)}toString(){return this.date.toUTCString()}startOf(t){switch(t){case"year":return new i(new Date(this.year(),0,1));case"month":return new i(new Date(this.year(),this.month(),1));default:return this}}add(n,a){let h;switch(a){case"m":case"minutes":h=t;break;case"h":case"hours":h=e;break;case"d":case"days":h=s;break;case"w":case"weeks":h=r;break;default:h=1}const u=this.unix()+n*h;return new i(1e3*u)}subtract(t,e){return this.add(-1*t,e)}format(t="YYYY-MM-DDTHH:mm:ssZ"){const e=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];return t.replace(/Y{2,4}|M{1,2}|D{1,2}|d{1,4}|H{1,2}|m{1,2}|s{1,2}|Z{1,2}/g,t=>{switch(t){case"YY":return String(this.mYear).slice(-2);case"YYYY":return String(this.mYear);case"M":return String(this.mMonth+1);case"MM":return n(String(this.mMonth+1),2,"0");case"D":return String(this.mDay);case"DD":return n(String(this.mDay),2,"0");case"d":return String(this.mWeek);case"dddd":return e[this.mWeek];case"H":return String(this.mHour);case"HH":return n(String(this.mHour),2,"0");case"m":return String(this.mMinute);case"mm":return n(String(this.mMinute),2,"0");case"s":return String(this.mSecond);case"ss":return n(String(this.mSecond),2,"0");case"Z":return`${this.timeZoneString.slice(0,-2)}:00`;case"ZZ":return this.timeZoneString;default:return t}})}}return t=>new i(t)}); diff --git a/src/uni_modules/wot-design-uni/components/common/lodash/debounce.js b/src/uni_modules/wot-design-uni/components/common/lodash/debounce.js new file mode 100644 index 00000000..d2e60f88 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/common/lodash/debounce.js @@ -0,0 +1,206 @@ +import isObject from './isObject' +import root from './internal/root' + +/** + * Creates a debounced function that delays invoking `func` until after `wait` + * milliseconds have elapsed since the last time the debounced function was + * invoked, or until the next browser frame is drawn. The debounced function + * comes with a `cancel` method to cancel delayed `func` invocations and a + * `flush` method to immediately invoke them. Provide `options` to indicate + * whether `func` should be invoked on the leading and/or trailing edge of the + * `wait` timeout. The `func` is invoked with the last arguments provided to the + * debounced function. Subsequent calls to the debounced function return the + * result of the last `func` invocation. + * + * **Note:** If `leading` and `trailing` options are `true`, `func` is + * invoked on the trailing edge of the timeout only if the debounced function + * is invoked more than once during the `wait` timeout. + * + * If `wait` is `0` and `leading` is `false`, `func` invocation is deferred + * until the next tick, similar to `setTimeout` with a timeout of `0`. + * + * If `wait` is omitted in an environment with `requestAnimationFrame`, `func` + * invocation will be deferred until the next frame is drawn (typically about + * 16ms). + * + * See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/) + * for details over the differences between `debounce` and `throttle`. + * + * @since 0.1.0 + * @category Function + * @param {Function} func The function to debounce. + * @param {number} [wait=0] + * The number of milliseconds to delay; if omitted, `requestAnimationFrame` is + * used (if available). + * @param {Object} [options={}] The options object. + * @param {boolean} [options.leading=false] + * Specify invoking on the leading edge of the timeout. + * @param {number} [options.maxWait] + * The maximum time `func` is allowed to be delayed before it's invoked. + * @param {boolean} [options.trailing=true] + * Specify invoking on the trailing edge of the timeout. + * @returns {Function} Returns the new debounced function. + * @example + * + * // Avoid costly calculations while the window size is in flux. + * jQuery(window).on('resize', debounce(calculateLayout, 150)) + * + * // Invoke `sendMail` when clicked, debouncing subsequent calls. + * jQuery(element).on('click', debounce(sendMail, 300, { + * 'leading': true, + * 'trailing': false + * })) + * + * // Ensure `batchLog` is invoked once after 1 second of debounced calls. + * const debounced = debounce(batchLog, 250, { 'maxWait': 1000 }) + * const source = new EventSource('/stream') + * jQuery(source).on('message', debounced) + * + * // Cancel the trailing debounced invocation. + * jQuery(window).on('popstate', debounced.cancel) + * + * // Check for pending invocations. + * const status = debounced.pending() ? "Pending..." : "Ready" + */ +function debounce(func, wait, options) { + let lastArgs, lastThis, maxWait, result, timerId, lastCallTime + + let lastInvokeTime = 0 + let leading = false + let maxing = false + let trailing = true + + // Bypass `requestAnimationFrame` by explicitly setting `wait=0`. + const useRAF = !wait && wait !== 0 && typeof root.requestAnimationFrame === 'function' + + if (typeof func !== 'function') { + throw new TypeError('Expected a function') + } + wait = +wait || 0 + if (isObject(options)) { + leading = !!options.leading + maxing = 'maxWait' in options + maxWait = maxing ? Math.max(+options.maxWait || 0, wait) : maxWait + trailing = 'trailing' in options ? !!options.trailing : trailing + } + + function invokeFunc(time) { + const args = lastArgs + const thisArg = lastThis + + lastArgs = lastThis = undefined + lastInvokeTime = time + result = func.apply(thisArg, args) + return result + } + + function startTimer(pendingFunc, wait) { + if (useRAF) { + root.cancelAnimationFrame(timerId) + return root.requestAnimationFrame(pendingFunc) + } + return setTimeout(pendingFunc, wait) + } + + function cancelTimer(id) { + if (useRAF) { + return root.cancelAnimationFrame(id) + } + clearTimeout(id) + } + + function leadingEdge(time) { + // Reset any `maxWait` timer. + lastInvokeTime = time + // Start the timer for the trailing edge. + timerId = startTimer(timerExpired, wait) + // Invoke the leading edge. + return leading ? invokeFunc(time) : result + } + + function remainingWait(time) { + const timeSinceLastCall = time - lastCallTime + const timeSinceLastInvoke = time - lastInvokeTime + const timeWaiting = wait - timeSinceLastCall + + return maxing ? Math.min(timeWaiting, maxWait - timeSinceLastInvoke) : timeWaiting + } + + function shouldInvoke(time) { + const timeSinceLastCall = time - lastCallTime + const timeSinceLastInvoke = time - lastInvokeTime + + // Either this is the first call, activity has stopped and we're at the + // trailing edge, the system time has gone backwards and we're treating + // it as the trailing edge, or we've hit the `maxWait` limit. + return lastCallTime === undefined || timeSinceLastCall >= wait || timeSinceLastCall < 0 || (maxing && timeSinceLastInvoke >= maxWait) + } + + function timerExpired() { + const time = Date.now() + if (shouldInvoke(time)) { + return trailingEdge(time) + } + // Restart the timer. + timerId = startTimer(timerExpired, remainingWait(time)) + } + + function trailingEdge(time) { + timerId = undefined + + // Only invoke if we have `lastArgs` which means `func` has been + // debounced at least once. + if (trailing && lastArgs) { + return invokeFunc(time) + } + lastArgs = lastThis = undefined + return result + } + + function cancel() { + if (timerId !== undefined) { + cancelTimer(timerId) + } + lastInvokeTime = 0 + lastArgs = lastCallTime = lastThis = timerId = undefined + } + + function flush() { + return timerId === undefined ? result : trailingEdge(Date.now()) + } + + function pending() { + return timerId !== undefined + } + + function debounced(...args) { + const time = Date.now() + const isInvoking = shouldInvoke(time) + + lastArgs = args + lastThis = this + lastCallTime = time + + if (isInvoking) { + if (timerId === undefined) { + return leadingEdge(lastCallTime) + } + if (maxing) { + // Handle invocations in a tight loop. + timerId = startTimer(timerExpired, wait) + return invokeFunc(lastCallTime) + } + } + if (timerId === undefined) { + timerId = startTimer(timerExpired, wait) + } + return result + } + + debounced.cancel = cancel + debounced.flush = flush + debounced.pending = pending + return debounced +} + +export default debounce diff --git a/src/uni_modules/wot-design-uni/components/common/lodash/internal/freeGlobal.js b/src/uni_modules/wot-design-uni/components/common/lodash/internal/freeGlobal.js new file mode 100644 index 00000000..59519fe3 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/common/lodash/internal/freeGlobal.js @@ -0,0 +1,4 @@ +/** Detect free variable `global` from Node.js. */ +const freeGlobal = typeof global === 'object' && global !== null && global.Object === Object && global + +export default freeGlobal diff --git a/src/uni_modules/wot-design-uni/components/common/lodash/internal/root.js b/src/uni_modules/wot-design-uni/components/common/lodash/internal/root.js new file mode 100644 index 00000000..4bbbf466 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/common/lodash/internal/root.js @@ -0,0 +1,14 @@ +import freeGlobal from './freeGlobal.js' + +/** Detect free variable `globalThis` */ +// eslint-disable-next-line eqeqeq +const freeGlobalThis = typeof globalThis === 'object' && globalThis !== null && globalThis.Object == Object && globalThis + +/** Detect free variable `self`. */ +const freeSelf = typeof self === 'object' && self !== null && self.Object === Object && self + +/** Used as a reference to the global object. */ +// eslint-disable-next-line no-new-func +const root = freeGlobalThis || freeGlobal || freeSelf || Function('return this')() + +export default root diff --git a/src/uni_modules/wot-design-uni/components/common/lodash/isObject.js b/src/uni_modules/wot-design-uni/components/common/lodash/isObject.js new file mode 100644 index 00000000..e9e81bff --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/common/lodash/isObject.js @@ -0,0 +1,29 @@ +/** + * Checks if `value` is the + * [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types) + * of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) + * + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an object, else `false`. + * @example + * + * isObject({}) + * // => true + * + * isObject([1, 2, 3]) + * // => true + * + * isObject(Function) + * // => true + * + * isObject(null) + * // => false + */ +function isObject(value) { + const type = typeof value + return value != null && (type === 'object' || type === 'function') +} + +export default isObject diff --git a/src/uni_modules/wot-design-uni/components/common/util.js b/src/uni_modules/wot-design-uni/components/common/util.js new file mode 100644 index 00000000..f5f0e625 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/common/util.js @@ -0,0 +1,180 @@ +import debounce from './lodash/debounce' + +/** + * @description 对num自动填充px + * @param {Number} num + * @return {string} num+px + */ +export function addUnit(num) { + return Number.isNaN(Number(num)) ? num : `${num}px` +} + +/** + * @description 获取当前页面栈顶(当前显示的页面) + * @return {wx.Page} + */ +export function getContext() { + const pages = getCurrentPages() + return pages[pages.length - 1] +} + +/** + * @description 判断target是否对象 + * @param obj + * @return {boolean} + */ +export function isObj(obj) { + // Object.prototype.toString.call(obj).match(/\[object (\w+)\]/)[1].toLowerCase() === 'object' + return typeof obj === 'object' +} + +/** + * @description 获取目标原始类型 + * @param target 任意类型 + * @returns {string} type 数据类型 + */ +export function getType(target) { + // 得到原生类型 + const typeStr = Object.prototype.toString.call(target) + // 拿到类型值 + const type = typeStr.match(/\[object (\w+)\]/)[1] + // 类型值转小写并返回 + return type.toLowerCase() +} + +/** + * @description 默认的外部格式化函数 - picker组件 + * @param items + * @param labelKey + * @return {*} + */ +export const defaultDisplayFormat = function (items, { labelKey = 'value' }) { + // 在props中,this被指向了全局data + if (items instanceof Array) { + return items.map((item) => item[labelKey]).toString() + } else { + return items[labelKey] + } +} +/** + * @description 默认函数占位符 - pickerView组件 + * @param value + * @return value + */ +export const defaultFunction = (value) => value +/** + * @description 是否不为空 + * @param value + * @return {Boolean} + */ +export const isDef = (value) => value !== undefined && value !== null + +export { debounce } + +/** + * @description 防止数字小于零 + * @param {Number} num + * @param {String} label 标签 + */ +export const checkNumRange = (num, label = 'value') => { + if (num < 0) { + throw Error(`${label} shouldn't be less than zero`) + } +} +/** + * @description 防止pixel无意义 + * @param {Number} num + * @param {String} label 标签 + */ +export const checkPixelRange = (num, label = 'value') => { + if (num <= 0) { + throw Error(`${label} should be greater than zero`) + } +} + +/** + * @default 渲染视图 + * @param {this} node 节点 + * @param {Object} data 需要渲染的数据 + * @param {Number} delay 延迟多久 + */ +export const renderData = (node, data, delay = 0) => { + const diff = Object.keys(data).reduce((prev, key) => { + if (data[key] !== node.data[key]) { + prev[key] = data[key] + } + return prev + }, {}) + if (Object.keys(diff).length === 0) return + const render = () => node.setData(diff) + delay ? setTimeout(render, delay) : render() +} + +function rgbToHex(r, g, b) { + const hex = ((r << 16) | (g << 8) | b).toString(16) + return '#' + new Array(Math.abs(hex.length - 7)).join('0') + hex +} + +function hexToRgb(hex) { + const rgb = [] + for (let i = 1; i < 7; i += 2) { + rgb.push(parseInt('0x' + hex.slice(i, i + 2))) + } + return rgb +} + +/** + * @default 计算渐变色的中间变量 + * @param {String} startColor 开始颜色 + * @param {String} endColor 结束颜色 + * @param {Number} step 获取渲染位置,默认为中间位置 + * @return {String} 渐变色中间颜色变量 + */ +export const gradient = (startColor, endColor, step = 2) => { + // 将hex转换为rgb + const sColor = hexToRgb(startColor) + const eColor = hexToRgb(endColor) + + // 计算R\G\B每一步的差值 + const rStep = (eColor[0] - sColor[0]) / step + const gStep = (eColor[1] - sColor[1]) / step + const bStep = (eColor[2] - sColor[2]) / step + + const gradientColorArr = [] + for (let i = 0; i < step; i++) { + // 计算每一步的hex值 + gradientColorArr.push(rgbToHex(parseInt(rStep * i + sColor[0]), parseInt(gStep * i + sColor[1]), parseInt(bStep * i + sColor[2]))) + } + return gradientColorArr +} + +/** @description 保证num不超出min和max的范围 */ +export const range = (num, min, max) => Math.min(Math.max(num, min), max) + +/** @description 比较数值是否相等 */ +export const isEqual = (value1, value2) => { + if (value1 === value2) return true + if (!(value1 instanceof Array)) return false + if (!(value2 instanceof Array)) 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 +} + +/** @description 不满10补0 */ +export const padZero = (number, length = 2) => { + number = number + '' + + while (number.length < length) { + number = '0' + number + } + + return number +} + +/** @description 全局变量id */ +export const context = { + id: 1000 +} diff --git a/src/uni_modules/wot-design-uni/components/mixins/basic.js b/src/uni_modules/wot-design-uni/components/mixins/basic.js new file mode 100644 index 00000000..b2b28da8 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/mixins/basic.js @@ -0,0 +1,43 @@ +export default { + methods: { + $emit(...args) { + this.triggerEvent(...args) + }, + /** + * @description 获取节点的样式 + * @param {String} selector -选择器 + * @param {Boolean} all -selectAll + * @return {Promise>} 样式对象,或者所有节点样式的集合 + */ + getRect(selector, all = false) { + return new Promise((resolve) => { + jd.createSelectorQuery() + .in(this) + [all ? 'selectAll' : 'select'](selector) + .boundingClientRect((rect) => { + if (all && Array.isArray(rect) && rect.length) { + resolve(rect) + } + + if (!all && rect) { + resolve(rect) + } + }) + .exec() + }) + }, + /** + * @default 模拟 requestAnimationFrame,支持 Promise 嵌套。 + * @param {Function} cb 下一渲染帧的回调 + */ + requestAnimationFrame(cb = () => void 0) { + return new Promise((resolve, reject) => { + if (typeof cb !== 'function' || !this || !('setData' in this)) return reject + this.setData({}, () => { + resolve() + cb() + }) + }) + } + } +} diff --git a/src/uni_modules/wot-design-uni/components/mixins/cell.js b/src/uni_modules/wot-design-uni/components/mixins/cell.js new file mode 100644 index 00000000..d8d1d7ca --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/mixins/cell.js @@ -0,0 +1,24 @@ +export default Behavior({ + properties: { + border: Boolean + }, + methods: { + /** + * @description 从cellGroup获取此组件的索引 + * @return {Number} 此组件的索引 + */ + getIndex() { + if (!this.parent) return + return this.parent.children.indexOf(this) + }, + /** + * @description 为所有索引非0的组件设置刘海线,此方法由cellGroup调用 + */ + setIndexAndStatus(border) { + const index = this.getIndex() + this.setData({ + border: border && index + }) + } + } +}) diff --git a/src/uni_modules/wot-design-uni/components/mixins/popover.js b/src/uni_modules/wot-design-uni/components/mixins/popover.js new file mode 100644 index 00000000..fd308469 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/mixins/popover.js @@ -0,0 +1,237 @@ +import { pushToQueue, removeFromQueue, closeOther } from '../common/clickoutside' + +/** + * @description 注意点: + * 1. 需要控制的位置: 12个 + * 2. 每一个位置改变都需要控制: + * popLeft(弹出坐标x)/ popTop(弹出坐标Y)/ arrowStyle(三角形位置以及尖角朝向) + * 尖角样式朝向class控制,位置用js控制 + * @param {String} [placement=bottom] - Placement of the popper accepted values: top(-start, -end), right(-start, -end), bottom(-start, -end), left(-start, -end) + * @param {Number} [offset=5] - Amount of pixels the popper will be shifted (can be negative). + * @param {Boolean} [visibleArrow=false] Visibility of the arrow + * @param {Boolean} [value=false] Visibility of the component + * @param {Boolean} [disabled=false] Disabled to change. + */ + +export default function () { + return { + externalClasses: ['custom-arrow', 'custom-pop'], + + data: { + popStyle: {}, + arrowStyle: {}, + showStyle: '' + }, + + props: { + visibleArrow: { + type: Boolean, + value: true + }, + // 显示内容 String || Array + content: { + type: null, + observer(newVal) { + const { mode, selector } = this.data + // 类型校验,支持所有值(除null、undefined。undefined建议统一写成void (0)防止全局undefined被覆盖) + if (newVal === null || newVal === undefined) { + throw Error("value can't be null or undefined") + } + if (selector === 'popover' && mode === 'normal' && typeof newVal !== 'string') { + throw Error('The value type must be a string type in normal mode') + } else if (selector === 'popover' && mode === 'menu' && this.checkType(newVal) !== 'Array') { + throw Error('The value type must be a Array type in menu mode') + } + } + }, + placement: { + type: String, + value: 'bottom' + }, + offset: { + type: Number, + value: 0 + }, + useContentSlot: { + type: Boolean, + value: false + }, + disabled: { + type: Boolean, + value: false + }, + showClose: { + type: Boolean, + value: false + }, + show: { + type: Boolean, + observer(newValue, oldValue) { + if (newValue) { + this.control() + closeOther(this) + } + this.setData({ showStyle: newValue ? 'display: inline-block;' : 'display: none;' }) + this.$emit('change', { show: newValue }) + this.$emit(`${newValue ? 'open' : 'close'}`) + } + } + }, + + mounted() { + this.init() + }, + + beforeCreate() { + pushToQueue(this) + }, + + created() { + this.setData({ showStyle: this.data.show ? 'opacity: 1;' : 'opacity: 0;' }) + }, + + destroyed() { + removeFromQueue(this) + }, + + methods: { + noop() {}, + + open() { + this.setData({ show: true }) + }, + + close() { + this.setData({ show: false }) + }, + + init() { + // 初始化 class + const { placement, visibleArrow, selector } = this.data + if (visibleArrow) { + let arrowClass = [ + `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 = arrowClass.join(' ') + this.setData({ arrowClass }) + } + + // 初始化数据获取 + this.getRect('#target').then((rect) => { + if (!rect) return + this.left = rect.left + this.bottom = rect.bottom + this.width = rect.width + this.height = rect.height + this.top = rect.top + }) + // 用透明度可在初始化时获取到pop尺寸 + this.getRect('#pos').then((rect) => { + if (!rect) return + this.popWidth = rect.width + this.popHeight = rect.height + }) + }, + + toggle(event) { + if (this.data.disabled) return + const { show } = this.data + this.setData({ show: !show }) + }, + + checkType(value) { + return Object.prototype.toString.call(value).slice(8, -1) + }, + + control() { + const { placement, offset } = this.data + // arrow size + const arrowSize = 9 + // 上下位(纵轴)对应的距离左边的距离 + const verticalX = this.width / 2 + // 上下位(纵轴)对应的距离底部的距离 + const verticalY = arrowSize + this.height + 5 + // 左右位(横轴)对应的距离左边的距离 + const horizontalX = this.width + arrowSize + 5 + // 左右位(横轴)对应的距离底部的距离 + const horizontalY = this.height / 2 + + 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: ${(this.popWidth >= this.width ? this.width / 2 : this.popWidth - 25) - offsetX}px;` + ] + ], + [ + 'top-end', + [ + `right: ${offsetX}px; bottom: ${verticalY}px;`, + `right: ${(this.popWidth >= this.width ? this.width / 2 : this.popWidth - 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: ${(this.popWidth >= this.width ? this.width / 2 : this.popWidth - 25) - offsetX}px;`] + ], + [ + 'bottom-end', + [ + `right: ${offsetX}px; top: ${verticalY}px;`, + `right: ${(this.popWidth >= this.width ? this.width / 2 : this.popWidth - 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: ${(this.popHeight >= this.height ? this.height / 2 : this.popHeight - 20) - offsetY}px;` + ] + ], + [ + 'left-end', + [ + `right: ${horizontalX}px; bottom: ${offsetY}px;`, + `bottom: ${(this.popHeight >= this.height ? this.height / 2 : this.popHeight - 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: ${(this.popHeight >= this.height ? this.height / 2 : this.popHeight - 20) - offsetY}px;` + ] + ], + [ + 'right-end', + [ + `left: ${horizontalX}px; bottom: ${offsetY}px;`, + `bottom: ${(this.popHeight >= this.height ? this.height / 2 : this.popHeight - 20) - offsetY}px; transform: translateY(50%);` + ] + ] + ]) + + this.setData({ + popStyle: placements.get(placement)[0], + arrowStyle: placements.get(placement)[1] + }) + } + } + } +} diff --git a/src/uni_modules/wot-design-uni/components/mixins/touch.js b/src/uni_modules/wot-design-uni/components/mixins/touch.js new file mode 100644 index 00000000..cb628aa6 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/mixins/touch.js @@ -0,0 +1,25 @@ +export default function () { + return { + methods: { + touchStart(event) { + const touch = event.touches[0] + this.direction = '' + this.deltaX = 0 + this.deltaY = 0 + this.offsetX = 0 + this.offsetY = 0 + this.startX = touch.clientX + this.startY = touch.clientY + }, + + touchMove(event) { + const touch = event.touches[0] + this.deltaX = touch.clientX - this.startX + this.deltaY = touch.clientY - this.startY + this.offsetX = Math.abs(this.deltaX) + this.offsetY = Math.abs(this.deltaY) + this.direction = this.offsetX > this.offsetY ? 'horizontal' : this.offsetX < this.offsetY ? 'vertical' : '' + } + } + } +} diff --git a/src/uni_modules/wot-design-uni/components/mixins/transition.ts b/src/uni_modules/wot-design-uni/components/mixins/transition.ts new file mode 100644 index 00000000..d07693cb --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/mixins/transition.ts @@ -0,0 +1,160 @@ +import { onBeforeMount, ref, watch } from 'vue' +import { isObj } from '../common/util' + +const getClassNames = (name) => { + if (!name) { + return { + enter: 'enter-class enter-active-class', + 'enter-to': 'enter-to-class enter-active-class', + leave: 'leave-class leave-active-class', + 'leave-to': 'leave-to-class leave-active-class' + } + } + + return { + enter: `wd-${name}-enter wd-${name}-enter-active`, + 'enter-to': `wd-${name}-enter-to wd-${name}-enter-active`, + leave: `wd-${name}-leave wd-${name}-leave-active`, + 'leave-to': `wd-${name}-leave-to wd-${name}-leave-active` + } +} + +const requestAnimationFrame = (cb = () => void 0) => { + return new Promise((resolve, reject) => { + uni + .createSelectorQuery() + .selectViewport() + .boundingClientRect() + .exec(() => { + resolve(true) + cb() + }) + }) +} + +type TransitionName = + | 'fade' + | 'fade-down' + | 'fade-left' + | 'fade-right' + | 'fade-up' + | 'slide-down' + | 'slide-left' + | 'slide-right' + | 'slide-up' + | 'zoom-in' + | 'zoom-out' + +interface Props { + show: boolean + duration?: Record | number + name: TransitionName + customStyle: string + lazyRender: boolean + // 定义进入过渡的开始状态。在元素被插入之前生效,在元素被插入之后的下一帧移除。 + enterClass?: string + // 定义进入过渡生效时的状态。在整个进入过渡的阶段中应用,在元素被插入之前生效,在过渡/动画完成之后移除。这个类可以被用来定义进入过渡的过程时间,延迟和曲线函数。 + enterActiveClass?: string + // 定义进入过渡的结束状态。在元素被插入之后下一帧生效 (与此同时 enter-class 被移除),在过渡/动画完成之后移除。 + enterToClass?: string + // 定义离开过渡的开始状态。在离开过渡被触发时立刻生效,下一帧被移除。 + leaveClass?: string + // 定义离开过渡生效时的状态。在整个离开过渡的阶段中应用,在离开过渡被触发时立刻生效,在过渡/动画完成之后移除。这个类可以被用来定义离开过渡的过程时间,延迟和曲线函数。 + leaveActiveClass?: string + // 定义离开过渡的结束状态。在离开过渡被触发之后下一帧生效 (与此同时 leave-class 被删除),在过渡/动画完成之后移除。 + leaveToClass?: string +} + +const props = withDefaults(defineProps(), { + show: false, + name: 'fade', + duration: 300, + lazyRender: true +}) + +// 初始化是否完成 +const inited = ref(false) +// 是否显示 +const display = ref(false) +// 当前动画状态 +const status = ref('') +// 动画是否结束 +const transitionEnded = ref(false) +// 动画持续时间 +const currentDuration = ref(300) +// 类名 +const classes = ref('') + +const emit = defineEmits(['click', 'before-enter', 'enter', 'before-leave', 'leave', 'after-leave', 'after-enter']) + +onBeforeMount(() => { + if (props.show) { + enter() + } +}) + +watch( + () => props.show, + (newVal) => { + observerShow(newVal) + } +) + +function observerShow(value: boolean) { + value ? enter() : leave() +} + +function enter() { + const classNames = getClassNames(props.name) + const duration = isObj(props.duration) ? (props.duration as any).enter : props.duration + status.value = 'enter' + emit('before-enter') + + requestAnimationFrame(() => { + emit('enter') + classes.value = classNames.enter + currentDuration.value = duration + requestAnimationFrame(() => { + inited.value = true + display.value = true + requestAnimationFrame(() => { + transitionEnded.value = false + classes.value = classNames['enter-to'] + }) + }) + }) +} +function leave() { + if (!display.value) return + const classNames = getClassNames(props.name) + const duration = isObj(props.duration) ? (props.duration as any).leave : props.duration + status.value = 'leave' + emit('before-leave') + + requestAnimationFrame(() => { + emit('leave') + classes.value = classNames.leave + currentDuration.value = duration + + requestAnimationFrame(() => { + transitionEnded.value = false + setTimeout(() => onTransitionEnd(), currentDuration.value) + classes.value = classNames['leave-to'] + }) + }) +} +function onTransitionEnd() { + if (transitionEnded.value) return + + transitionEnded.value = true + if (status.value === 'leave') { + // 离开后触发 + emit('after-leave') + } else if (status.value === 'enter') { + // 进入后触发 + emit('after-enter') + } + if (!props.show && display.value) { + display.value = false + } +} diff --git a/src/uni_modules/wot-design-uni/components/wd-action-sheet/index.js b/src/uni_modules/wot-design-uni/components/wd-action-sheet/index.js new file mode 100644 index 00000000..c0ba5e3b --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-action-sheet/index.js @@ -0,0 +1,101 @@ +import VueComponent from '../common/component' + +VueComponent({ + externalClasses: ['custom-header-class'], + props: { + show: Boolean, + actions: { + type: Array, + value: [] + }, + panels: { + type: Array, + value: [], + observer: 'computedValue' + }, + title: String, + cancelText: String, + closeOnClickAction: { + type: Boolean, + value: true + }, + closeOnClickModal: { + type: Boolean, + value: true + }, + duration: { + type: Number, + value: 200 + }, + zIndex: { + type: Number, + value: 10 + }, + lazyRender: { + type: Boolean, + value: true + }, + safeAreaInsetBottom: { + type: Boolean, + value: true + } + }, + data() { + return { + formatPanels: [] + } + }, + methods: { + isArray() { + return this.data.panels.length && !(this.data.panels[0] instanceof Array) + }, + computedValue() { + this.setData({ + formatPanels: this.isArray() ? [this.data.panels] : this.data.panels + }) + }, + select(event) { + const { rowIndex, colIndex, type } = event.currentTarget.dataset + if (type === 'action') { + this.$emit('select', { + item: this.data.actions[rowIndex], + index: rowIndex + }) + } else if (this.isArray()) { + this.$emit('select', { + item: this.data.panels[colIndex], + index: colIndex + }) + } else { + this.$emit('select', { + item: this.data.panels[rowIndex][colIndex], + rowIndex, + colIndex + }) + } + this.close() + }, + handleClickModal() { + this.$emit('clickmodal') + if (this.data.closeOnClickModal) { + this.close() + } + }, + handleCancel() { + this.$emit('cancel') + this.close() + }, + close() { + this.$emit('close') + }, + handleOpen() { + this.$emit('open') + }, + handleOpened() { + this.$emit('opened') + }, + handleClosed() { + this.$emit('closed') + } + } +}) diff --git a/src/uni_modules/wot-design-uni/components/wd-action-sheet/index.json b/src/uni_modules/wot-design-uni/components/wd-action-sheet/index.json new file mode 100644 index 00000000..f9757ecf --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-action-sheet/index.json @@ -0,0 +1,8 @@ +{ + "component": true, + "usingComponents": { + "wd-icon": "../icon/index", + "wd-loading": "../loading/index", + "wd-popup": "../popup/index" + } +} \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-action-sheet/index.jxml b/src/uni_modules/wot-design-uni/components/wd-action-sheet/index.jxml new file mode 100644 index 00000000..3f1046e4 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-action-sheet/index.jxml @@ -0,0 +1,67 @@ + + + + {{ title }} + + + + + + + + + + + {{ panel.title }} + + + + + + + + \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-action-sheet/index.scss b/src/uni_modules/wot-design-uni/components/wd-action-sheet/index.scss new file mode 100644 index 00000000..ac60e8ed --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-action-sheet/index.scss @@ -0,0 +1,147 @@ +@import '../common/abstracts/variable'; +@import '../common/abstracts/mixin'; + +@include b(action-sheet) { + background-color: #fff; + padding-bottom: 1px; + + @include e(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; + } + + &:active { + background: $-action-sheet-active-color; + } + + @include m(disabled) { + color: $-action-sheet-disabled-color; + } + + @include m(loading) { + display: flex; + align-items: center; + justify-content: center; + line-height: initial; + } + } + + @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 e(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; + } +} \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-action-sheet/wd-action-sheet.vue b/src/uni_modules/wot-design-uni/components/wd-action-sheet/wd-action-sheet.vue new file mode 100644 index 00000000..704244df --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-action-sheet/wd-action-sheet.vue @@ -0,0 +1,323 @@ + + + + + diff --git a/src/uni_modules/wot-design-uni/components/wd-badge/index.js b/src/uni_modules/wot-design-uni/components/wd-badge/index.js new file mode 100644 index 00000000..2f6cc3fc --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-badge/index.js @@ -0,0 +1,39 @@ +import VueComponent from '../common/component' + +VueComponent({ + props: { + value: { + type: String, + value: null, + observer: 'notice' + }, + bgColor: String, + max: { + type: Number, + observer: 'notice' + }, + isDot: { + type: Boolean, + value: false, + observer: 'notice' + }, + hidden: Boolean, + type: String, + top: Number, + right: Number + }, + data: { + content: '' + }, + methods: { + notice () { + if (this.data.isDot) return + let value = this.data.value + const max = this.data.max + if (value && max && !Number.isNaN(value) && !Number.isNaN(max)) { + value = max < value ? `${parseInt(max)}+` : value + } + this.setData({ content: value }) + } + } +}) \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-badge/index.json b/src/uni_modules/wot-design-uni/components/wd-badge/index.json new file mode 100644 index 00000000..828b0494 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-badge/index.json @@ -0,0 +1,7 @@ +{ + "component": true, + "usingComponents": { + "wd-icon": "../icon/index", + "wd-button": "../button/index" + } +} diff --git a/src/uni_modules/wot-design-uni/components/wd-badge/index.jxml b/src/uni_modules/wot-design-uni/components/wd-badge/index.jxml new file mode 100644 index 00000000..5d8d902b --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-badge/index.jxml @@ -0,0 +1,9 @@ +
+ + + {{content}} + +
diff --git a/src/uni_modules/wot-design-uni/components/wd-badge/index.scss b/src/uni_modules/wot-design-uni/components/wd-badge/index.scss new file mode 100644 index 00000000..733c0961 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-badge/index.scss @@ -0,0 +1,51 @@ +@import "./../common/abstracts/_mixin.scss"; +@import "./../common/abstracts/variable.scss"; + +@include b(badge) { + position: relative; + vertical-align: middle; + display: inline-block; + + @include e(content) { + display: inline-block; + 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; + 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; + } + } + } + } +} diff --git a/src/uni_modules/wot-design-uni/components/wd-badge/wd-badge.vue b/src/uni_modules/wot-design-uni/components/wd-badge/wd-badge.vue new file mode 100644 index 00000000..207f1a94 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-badge/wd-badge.vue @@ -0,0 +1,108 @@ + + + + + + diff --git a/src/uni_modules/wot-design-uni/components/wd-button/index.js b/src/uni_modules/wot-design-uni/components/wd-button/index.js new file mode 100644 index 00000000..313d02c6 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-button/index.js @@ -0,0 +1,119 @@ +import VueComponent from '../common/component' +import base64 from '../common/base64' + +const loadingIcon = (color = '#4D80F0', reverse = true) => { + return `` +} + +VueComponent({ + props: { + plain: Boolean, + disabled: Boolean, + round: { + type: Boolean, + value: true + }, + suck: Boolean, + block: Boolean, + type: { + type: String, + value: 'primary' + }, + size: { + type: String, + value: 'medium' + }, + icon: String, + loading: { + type: Boolean, + observer: 'buildLoadingSvg' + }, + loadingColor: String, + openType: String, + formType: String, + hoverStopPropagation: { + type: Boolean, + value: false + }, + lang: { + type: String, + value: 'en' + }, + sessionFrom: String, + sendMessageTitle: String, + sendMessagePath: String, + sendMessageImg: String, + appParameter: String, + showMessageCard: { + type: Boolean, + value: false + } + }, + data: { + hoverStartTime: 20, + hoverStayTime: 70, + loadingIconSvg: '' + }, + methods: { + handleClick (event) { + if (!this.data.disabled && !this.data.loading) { + this.$emit('click', event.detail) + } + }, + handleGetuserinfo (event) { + this.$emit('getuserinfo', event.detail) + }, + + handleConcat (event) { + this.$emit('contact', event.detail) + }, + + handleGetphonenumber (event) { + this.$emit('getphonenumber', event.detail) + }, + + handleError (event) { + this.$emit('error', event.detail) + }, + + handleLaunchapp (event) { + this.$emit('launchapp', event.detail) + }, + + handleOpensetting (event) { + this.$emit('opensetting', event.detail) + }, + + buildLoadingSvg () { + const { loadingColor, type, plain } = this.data + 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) + const svgStr = `"data:image/svg+xml;base64,${base64(svg)}"` + this.setData({ loadingIconSvg: svgStr }) + } + } +}) \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-button/index.json b/src/uni_modules/wot-design-uni/components/wd-button/index.json new file mode 100644 index 00000000..ced2d383 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-button/index.json @@ -0,0 +1,7 @@ +{ + "component": true, + "usingComponents": { + "wd-icon": "../icon/index", + "wd-loading": "../loading/index" + } +} diff --git a/src/uni_modules/wot-design-uni/components/wd-button/index.jxml b/src/uni_modules/wot-design-uni/components/wd-button/index.jxml new file mode 100644 index 00000000..f591649f --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-button/index.jxml @@ -0,0 +1,30 @@ + diff --git a/src/uni_modules/wot-design-uni/components/wd-button/index.scss b/src/uni_modules/wot-design-uni/components/wd-button/index.scss new file mode 100644 index 00000000..d30ea4f3 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-button/index.scss @@ -0,0 +1,503 @@ +@import "./../common/abstracts/_mixin.scss"; +@import "./../common/abstracts/variable.scss"; + +@mixin button-type-style($color, $normal, $active, $disabled, $disabledcolor) { + background: $normal; + color: $color; + font-weight: $-fw-medium; + + &::after { + border-color: $normal; + } + &.wd-button--active { + background: $active; + } + @include when(disabled) { + &.wd-button--active { + background: $disabled; + color: $disabledcolor; + } + + background: $disabled; + color: $disabledcolor; + + &::after { + border-color: $disabled; + } + } + @include when(loading) { + &, + &.wd-button--active { + color: $color; + background: $normal; + } + &::after { + border-color: $normal; + } + } + @include when(suck) { + border-radius: 0; + } +} + +@mixin button-plain-style($color, $normal, $active, $disabled) { + color: $color; + background: transparent; + + &::after { + border-color: $normal; + } + &.wd-button--active { + color: $active; + background: transparent; + + &::after { + border-color: $active; + } + } + @include when(disabled) { + color: $disabled; + background: transparent; + + &::after { + border-color: $disabled; + } + &.wd-button--active { + background: transparent; + + &::after { + border-color: $disabled; + } + } + } + @include when(loading) { + &, + &.wd-button--active { + color: $color; + background: transparent; + + &::after { + border-color: $normal; + } + } + } +} +@include b(button) { + position: relative; + display: inline-flex; + justify-content: center; + align-items: center; + outline: none; + -webkit-appearance: none; + outline: none; + background: transparent; + box-sizing: border-box; + border: none; + color: $-button-normal-color; + transition: all 0.2s; + user-select: none; + font-weight: normal; + + &::after { + display: none; + } + &.wd-button--active { + color: $-button-normal-active-color; + background: $-button-normal-active-bg; + + &::after { + border-color: $-button-normal-border-active-color; + } + } + @include when(disabled) { + color: $-button-normal-disabled-color; + background: $-button-normal-disabled-bg; + + &::after { + border-color: $-button-normal-border-disabled-color; + } + &.wd-button--active { + color: $-button-normal-disabled-color; + background: $-button-normal-disabled-bg; + + &::after { + border-color: $-button-normal-border-disabled-color; + } + } + } + + @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) { + &.wd-button--active { + color: $-button-normal-color; + background: transparent; + + &::after { + border-color: $-button-border-color; + } + } + } + + @include when(primary) { + @include button-type-style( + $-color-white, + $-button-primary-bg-color, + $-button-primary-active-color, + $-button-primary-disabled-color, + $-color-white + ); + } + @include when(success) { + @include button-type-style( + $-color-white, + $-button-success-color, + $-button-success-active-color, + $-button-success-disabled-color, + $-color-white + ); + } + @include when(info) { + @include button-type-style( + $-button-info-color, + $-button-info-bg-color, + $-button-info-active-bg-color, + $-button-info-disabled-bg-color, + $-button-info-disabled-color + ); + } + @include when(warning) { + @include button-type-style( + $-color-white, + $-button-warning-color, + $-button-warning-active-color, + $-button-warning-disabled-color, + $-color-white + ); + } + @include when(error) { + @include button-type-style( + $-color-white, + $-button-error-color, + $-button-error-active-color, + $-button-error-disabled-color, + $-color-white + ); + } + + @include when(small) { + height: $-button-small-height; + padding: $-button-small-padding; + border-radius: $-button-small-radius; + font-size: $-button-small-fs; + font-weight: normal; + + @include when(round) { + border-radius: calc($-button-small-height / 2); + + &::after { + border-radius: $-button-small-height; + } + } + + .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; + + &::after { + border-radius: $-button-medium-radius * 2; + } + @include when(primary) { + box-shadow: $-button-medium-box-shadow-size $-button-primary-box-shadow-color; + } + + @include when(success) { + box-shadow: $-button-medium-box-shadow-size $-button-success-box-shadow-color; + } + + @include when(warning) { + box-shadow: $-button-medium-box-shadow-size $-button-warning-box-shadow-color; + } + + @include when(error) { + box-shadow: $-button-medium-box-shadow-size $-button-error-box-shadow-color; + } + + @include when(plain) { + box-shadow: none; + } + + @include when(round) { + min-width: 118px; + border-radius: calc($-button-medium-height / 2); + + &::after { + border-radius: $-button-medium-height; + } + @include when(icon) { + min-width: 0; + border-radius: 50%; + } + + @include when(text) { + min-width: 0; + border-radius: 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 * 2; + } + &:not(.is-plain) { + @include when(primary) { + box-shadow: $-button-large-box-shadow-size $-button-primary-box-shadow-color; + } + + @include when(success) { + box-shadow: $-button-large-box-shadow-size $-button-success-box-shadow-color; + } + + @include when(warning) { + box-shadow: $-button-large-box-shadow-size $-button-warning-box-shadow-color; + } + + @include when(error) { + box-shadow: $-button-large-box-shadow-size $-button-error-box-shadow-color; + } + } + + @include when(round) { + border-radius: calc($-button-large-height / 2); + + &::after { + border-radius: $-button-large-height; + } + @include when(icon) { + border-radius: 50%; + } + @include when(text) { + border-radius: 0; + } + } + .wd-button__loading { + width: $-button-large-loading; + height: $-button-large-loading; + } + } + + @include when(text) { + color: $-button-primary-color; + padding: 4px 0; + + &::after { + display: none; + } + &.wd-button--active { + color: $-button-primary-active-color; + background: transparent; + } + @include when(disabled) { + color: $-button-normal-disabled-color; + background: transparent; + } + } + + @include when(plain) { + background: $-color-white; + + &::after { + position: absolute; + display: block; + content: ''; + width: 200%; + height: 200%; + left: 0; + top: 0; + border: 1px solid $-button-border-color; + box-sizing: border-box; + transform: scale(0.5); + transform-origin: left top; + } + @include when(primary) { + @include button-plain-style( + $-button-primary-color, + $-button-primary-color, + $-button-primary-active-color, + $-button-primary-disabled-color + ); + &.wd-button--active { + background-color: $-button-primary-plain-active-bg-color; + } + @include when(disabled) { + &.wd-button--active { + background-color: $-button-primary-plain-active-bg-color; + } + opacity: 1; + background-color: $-button-primary-plain-active-bg-color; + color: $-button-primary-plain-disabled-color; + } + } + @include when(success) { + @include button-plain-style( + $-button-success-color, + $-button-success-color, + $-button-success-active-color, + $-button-success-disabled-color + ); + } + @include when(info) { + @include button-plain-style( + $-button-info-plain-normal-color, + $-button-info-bg-color, + $-button-info-active-color, + $-button-info-disabled-color + ); + &::after { + border-color: $-button-info-plain-border-color; + } + &.wd-button--active { + background-color: $-button-info-plain-active-bg-color; + + &::after { + border-color: $-button-info-plain-active-color; + } + } + @include when(disabled) { + &, + &.wd-button--active { + background-color: $-button-info-plain-disabled-bg-color; + + &::after { + border-color: $-button-info-plain-disabled-bg-color; + } + } + } + @include when(loading) { + &::after, + &.wd-button--active::after { + border-color: $-button-info-plain-border-color; + } + } + } + @include when(warning) { + @include button-plain-style( + $-button-warning-color, + $-button-warning-color, + $-button-warning-active-color, + $-button-warning-disabled-color + ); + } + @include when(error) { + @include button-plain-style( + $-button-error-color, + $-button-error-color, + $-button-error-active-color, + $-button-error-disabled-color + ); + } + @include when(suck) { + &.wd-button--active { + background: $-button-suck-active-color; + } + @include when(disabled) { + background: $-color-white; + } + } + } + + @include when(suck) { + display: flex; + font-size: $-button-large-fs; + height: $-button-suck-height; + border-radius: 0; + + &::after { + display: none; + } + } + + @include when(block) { + display: flex; + } + + @include when(icon) { + width: $-button-icon-size; + height: $-button-icon-size; + padding: 0; + border-radius: 50%; + font-size: 0; + color: $-button-icon-color; + + &::after { + display: none; + } + &.wd-button--active { + background: $-button-icon-active-color; + } + .wd-button__icon { + margin-right: 0; + } + @include when(disabled) { + color: $-button-icon-disabled-color; + background: transparent; + + &.wd-button--active { + background: transparent; + } + } + } + + @include e(icon) { + display: block; + margin-right: 6px; + font-size: $-button-icon-fs; + vertical-align: middle; + } + + @include e(text) { + user-select: none; + white-space: nowrap; + } +} +// 微信2.8.0以上版本加了较高层级的默认样式,需要重置掉 +.wd-button { + min-height: auto; + width: auto; +} +@keyframes wd-rotate { + from { + transform: rotate(0deg); + } + + to { + transform: rotate(360deg); + } +} diff --git a/src/uni_modules/wot-design-uni/components/wd-button/wd-button.vue b/src/uni_modules/wot-design-uni/components/wd-button/wd-button.vue new file mode 100644 index 00000000..8fc9e69e --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-button/wd-button.vue @@ -0,0 +1,630 @@ + + + + + diff --git a/src/uni_modules/wot-design-uni/components/wd-calendar-view/components/month/index.js b/src/uni_modules/wot-design-uni/components/wd-calendar-view/components/month/index.js new file mode 100644 index 00000000..ef66f225 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-calendar-view/components/month/index.js @@ -0,0 +1,320 @@ +import VueComponent from '../../../common/component' +import { compareDate, getMonthEndDay, getWeekRange, getDayOffset, getDayByOffset, getDateByDefaultTime } from '../../utils' +import { getType } from '../../../common/util' +import Toast from '../../../toast/toast.js' + +VueComponent({ + data: { + days: [] + }, + props: { + type: { + type: String, + observer: 'setDays' + }, + date: { + type: Number, + observer: 'setDays' + }, + value: { + type: [null, Number, Array], + observer: 'setDays' + }, + minDate: { + type: Number, + observer: 'setDays' + }, + maxDate: { + type: Number, + observer: 'setDays' + }, + firstDayOfWeek: Number, + formatter: { + type: null, + observer: 'setDays' + }, + maxRange: Number, + rangePrompt: String, + allowSameDay: Boolean, + defaultTime: Array + }, + methods: { + setDays () { + const days = [] + const date = new Date(this.data.date) + const year = date.getFullYear() + const month = date.getMonth() + + const totalDay = getMonthEndDay(year, month + 1) + + let value = this.data.value + + if ((this.data.type === 'week' || this.data.type === 'weekrange') && value) { + value = this.getWeekValue() + } + + for (let day = 1; day <= totalDay; day++) { + const date = new Date(year, month, day).getTime() + let type = this.getDayType(date, value) + if (!type && compareDate(date, Date.now()) === 0) { + type = 'current' + } + const dayObj = this.getFormatterDate(date, day, type) + days.push(dayObj) + } + + this.setData({ + days + }) + }, + getDayType (date, value) { + switch (this.data.type) { + case 'date': + case 'datetime': + return this.getDateType(date) + case 'dates': + return this.getDatesType(date) + case 'daterange': + case 'datetimerange': + return this.getDatetimeType(date, value) + case 'week': + return this.getWeektimeType(date, value) + case 'weekrange': + return this.getWeektimeType(date, value) + default: + return this.getDateType(date) + } + }, + getDateType (date) { + if (this.data.value && compareDate(date, this.data.value) === 0) { + return 'selected' + } + + return '' + }, + getDatesType (date) { + if (!this.data.value) return '' + + let type = '' + + this.data.value.some((item) => { + if (compareDate(date, item) === 0) { + type = 'selected' + + return true + } + + return false + }) + + return type + }, + getDatetimeType (date, value) { + const [startDate, endDate] = value || [] + + if (startDate && compareDate(date, startDate) === 0) { + if (this.data.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 '' + } + }, + getWeektimeType (date, value) { + const [startDate, endDate] = 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 '' + } + }, + getWeekValue () { + if (this.data.type === 'week') { + return getWeekRange(this.data.value, this.data.firstDayOfWeek) + } else { + const [startDate, endDate] = this.data.value || [] + + if (startDate) { + const firstWeekRange = getWeekRange(startDate, this.data.firstDayOfWeek) + + if (endDate) { + const endWeekRange = getWeekRange(endDate, this.data.firstDayOfWeek) + + return [firstWeekRange[0], endWeekRange[1]] + } else { + return firstWeekRange + } + } + + return [] + } + }, + handleDateClick (event) { + const { index } = event.currentTarget.dataset + const date = this.data.days[index] + + switch (this.data.type) { + case 'date': + case 'datetime': + this.handleDateChange(date) + break + case 'dates': + this.handleDatesChange(date) + break + case 'daterange': + case 'datetimerange': + this.handleDateRangeChange(date) + break + case 'week': + this.handleWeekChange(date) + break + case 'weekrange': + this.handleWeekRangeChange(date) + break + default: + this.handleDateChange(date) + } + }, + getDate (date, isEnd) { + date = this.data.defaultTime && this.data.defaultTime.length > 0 + ? getDateByDefaultTime(date, isEnd ? this.data.defaultTime[1] : this.data.defaultTime[0]) + : date + + if (date < this.data.minDate) return this.data.minDate + + if (date > this.data.maxDate) return this.data.maxDate + + return date + }, + handleDateChange (date) { + if (date.disabled) return + + if (date.type !== 'selected') { + this.$emit('change', { + value: this.getDate(date.date), + type: 'start' + }) + } + }, + handleDatesChange (date) { + if (date.disabled) return + + const value = this.data.value || [] + if (date.type !== 'selected') { + value.push(this.getDate(date.date)) + } else { + value.splice(value.indexOf(date.date), 1) + } + this.$emit('change', { + value + }) + }, + handleDateRangeChange (date) { + if (date.disabled) return + + let value + let type + const [startDate, endDate] = this.data.value || [] + const compare = compareDate(date.date, startDate) + + // 禁止选择同个日期 + if (!this.data.allowSameDay && compare === 0 && (this.data.type === 'daterange' || this.data.type === 'datetimerange') && !endDate) { + return + } + + if (startDate && !endDate && compare > -1) { + // 不能选择超过最大范围的日期 + if (this.data.maxRange && getDayOffset(date.date, startDate) > this.data.maxRange) { + const maxEndDate = getDayByOffset(startDate, this.data.maxRange - 1) + value = [startDate, this.getDate(maxEndDate, true)] + Toast({ + msg: this.data.rangePrompt || `选择天数不能超过${this.data.maxRange}天`, + context: this + }) + } else { + value = [startDate, this.getDate(date.date, true)] + } + } else if (this.data.type === 'datetimerange' && startDate && endDate) { + // 时间范围类型,且有开始时间和结束时间,需要支持重新点击开始日期和结束日期可以重新修改时间 + if (compare === 0) { + type = 'start' + value = this.data.value + } else if (compareDate(date.date, endDate) === 0) { + type = 'end' + value = this.data.value + } else { + value = [this.getDate(date.date), null] + } + } else { + value = [this.getDate(date.date), null] + } + + this.$emit('change', { + value, + type: type || (value[1] ? 'end' : 'start') + }) + }, + handleWeekChange (date) { + const [weekStart] = getWeekRange(date.date, this.data.firstDayOfWeek) + + // 周的第一天如果是禁用状态,则不可选中 + if (this.getFormatterDate(weekStart, new Date(weekStart).getDate()).disabled) return + + this.$emit('change', { + value: this.getDate(weekStart) + 24 * 60 * 60 * 1000 + }) + }, + handleWeekRangeChange (date) { + const [weekStartDate] = getWeekRange(date.date, this.data.firstDayOfWeek) + + // 周的第一天如果是禁用状态,则不可选中 + if (this.getFormatterDate(weekStartDate, new Date(weekStartDate).getDate()).disabled) return + + let value + const [startDate, endDate] = this.data.value || [] + const [startWeekStartDate] = startDate ? getWeekRange(startDate, this.data.firstDayOfWeek) : [] + const compare = compareDate(weekStartDate, startWeekStartDate) + + if (startDate && !endDate && compare > -1) { + if (!this.data.allowSameDay && compare === 0) return + + value = [this.getDate(startWeekStartDate) + 24 * 60 * 60 * 1000, this.getDate(weekStartDate) + 24 * 60 * 60 * 1000] + } else { + value = [this.getDate(weekStartDate) + 24 * 60 * 60 * 1000, null] + } + + this.$emit('change', { + value + }) + }, + getFormatterDate (date, day, type) { + let dayObj = { + date: date, + text: day, + topInfo: '', + bottomInfo: '', + type, + disabled: compareDate(date, this.data.minDate) === -1 || compareDate(date, this.data.maxDate) === 1 + } + if (this.data.formatter) { + if (getType(this.data.formatter) === 'function') { + dayObj = this.data.formatter(dayObj) + } else { + console.error('[wot-design] error(wd-calendar-view): the formatter prop of wd-calendar-view should be a function') + } + } + + return dayObj + } + } +}) \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-calendar-view/components/month/index.json b/src/uni_modules/wot-design-uni/components/wd-calendar-view/components/month/index.json new file mode 100644 index 00000000..ac3c6bc5 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-calendar-view/components/month/index.json @@ -0,0 +1,6 @@ +{ + "component": true, + "usingComponents": { + "wd-toast": "../../../toast/index" + } +} diff --git a/src/uni_modules/wot-design-uni/components/wd-calendar-view/components/month/index.jxml b/src/uni_modules/wot-design-uni/components/wd-calendar-view/components/month/index.jxml new file mode 100644 index 00000000..683ecf18 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-calendar-view/components/month/index.jxml @@ -0,0 +1,25 @@ + + + + + + {{ utils.formatMonthTitle(date) }} + + + + {{ item.topInfo }} + + {{ item.text }} + + {{ item.bottomInfo }} + + + + diff --git a/src/uni_modules/wot-design-uni/components/wd-calendar-view/components/month/index.scss b/src/uni_modules/wot-design-uni/components/wd-calendar-view/components/month/index.scss new file mode 100644 index 00000000..bd8e9a78 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-calendar-view/components/month/index.scss @@ -0,0 +1,114 @@ +@import '../../../common/abstracts/variable'; +@import '../../../common/abstracts/mixin'; + +@include b(month) { + @include e(title) { + padding: 13px 0; + text-align: center; + 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; + + @include when(disabled) { + .wd-month__day-text { + color: $-calendar-disabled-color; + } + } + @include when(current) { + color: $-calendar-active-color; + } + @include when(selected) { + .wd-month__day-container { + border-radius: $-calendar-active-border; + background: $-calendar-active-color; + color: #fff; + } + } + @include when(middle) { + .wd-month__day-container { + background: $-calendar-range-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: #fff; + border-radius: $-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: #fff; + border-radius: $-calendar-active-border; + } + } + @include when(same) { + .wd-month__day-container { + background: $-calendar-active-color; + color: #fff; + border-radius: $-calendar-active-border; + } + } + } + @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; + } +} \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-calendar-view/components/monthPanel/index.js b/src/uni_modules/wot-design-uni/components/wd-calendar-view/components/monthPanel/index.js new file mode 100644 index 00000000..15a440ab --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-calendar-view/components/monthPanel/index.js @@ -0,0 +1,242 @@ +import VueComponent from '../../../common/component' +import { formatMonthTitle, getMonths, compareMonth, getTimeData } from '../../utils' +import { getType, debounce, isEqual } from '../../../common/util' + +VueComponent({ + props: { + type: { + type: String, + observer (val) { + if ((val === 'datetime' && this.data.value) || (val === 'datetimerange' && this.data.value && this.data.value.length > 0 && this.data.value[0])) { + this.setTime(this.data.value, 'start') + } + } + }, + value: { + type: [null, Number, Array], + observer (val, oldVal) { + if (isEqual(val, this.data.innerValue)) return + + if ((this.data.type === 'datetime' && val) || (this.data.type === 'datetimerange' && val && val.length > 0 && val[0])) { + this.setTime(val, 'start') + } + } + }, + minDate: Number, + maxDate: Number, + firstDayOfWeek: Number, + formatter: null, + maxRange: Number, + rangePrompt: String, + allowSameDay: Boolean, + showPanelTitle: Boolean, + defaultTime: Array, + panelHeight: Number, + timeFilter: null, + hideSecond: Boolean + }, + data: { + title: '', + scrollIntoView: '', + timeValue: [], + timeData: [], + timeType: '', // 当前时间类型,是开始还是结束 + innerValue: '' // 内部保存一个值,用于判断新老值,避免监听器触发 + }, + mounted () { + this.initRect() + this.scrollIntoView() + }, + methods: { + initRect (thresholds = [0, 0.7, 0.8, 0.9, 1]) { + if (!this.data.showPanelTitle) return + + if (this.contentObserver != null) { + this.contentObserver.disconnect() + } + + const contentObserver = this.createIntersectionObserver({ + thresholds, + observeAll: true + }) + + this.contentObserver = contentObserver + + contentObserver.relativeTo('.wd-month-panel__container') + contentObserver.observe('.month', (res) => { + if (res.boundingClientRect.top <= res.relativeRect.top) { + this.setData({ + title: formatMonthTitle(res.dataset.date) + }) + } + }) + }, + scrollIntoView () { + setTimeout(() => { + let activeDate + const type = getType(this.data.value) + if (type === 'array') { + activeDate = this.data.value[0] + } else if (type === 'number') { + activeDate = this.data.value + } + + if (!activeDate) { + activeDate = Date.now() + } + + const months = getMonths(this.data.minDate, this.data.maxDate) + + months.some((month, index) => { + if (compareMonth(month, activeDate) === 0) { + this.setData({ + scrollIntoView: `month${index}` + }) + return true + } + + return false + }) + }, 50) + }, + /** + * 获取时间 picker 的数据 + * @param {timestamp|array} value 当前时间 + * @param {string} type 类型,是开始还是结束 + */ + getTimeData (value, type) { + if (this.data.type === 'datetime') { + return getTimeData({ + date: value, + minDate: this.data.minDate, + maxDate: this.data.maxDate, + filter: this.data.timeFilter, + isHideSecond: this.data.hideSecond + }) + } else { + if (type === 'start') { + return getTimeData({ + date: value[0], + minDate: this.data.minDate, + maxDate: this.data.value[1] ? this.data.value[1] : this.data.maxDate, + filter: this.data.timeFilter, + isHideSecond: this.data.hideSecond + }) + } else { + return getTimeData({ + date: value[1], + minDate: value[0], + maxDate: this.data.maxDate, + filter: this.data.timeFilter, + isHideSecond: this.data.hideSecond + }) + } + } + }, + /** + * 获取 date 的时分秒 + * @param {timestamp} date 时间 + * @param {string} type 类型,是开始还是结束 + */ + getTimeValue (date, type) { + if (this.data.type === 'datetime') { + date = new Date(date) + } else { + if (type === 'start') { + date = new Date(date[0]) + } else { + date = new Date(date[1]) + } + } + + const hour = date.getHours() + const minute = date.getMinutes() + const second = date.getSeconds() + + return this.data.hideSecond ? [hour, minute] : [hour, minute, second] + }, + setTime (value, type) { + if (getType(value) === 'array' && value[0] && value[1] && type === 'start' && this.data.timeType === 'start') { + type = 'end' + } + + this.setData({ + timeData: this.getTimeData(value, type), + timeValue: this.getTimeValue(value, type), + timeType: type + }, () => { + // 重新设置 thresholds + this.initRect([0, 0.58, 0.69, 0.83, 1]) + }) + }, + handleDateChange ({ detail: { value, type } }) { + if (!isEqual(value, this.data.value)) { + // 内部保存一个值,用于判断新老值,避免监听器触发 + this.setData({ + innerValue: value + }) + this.handleChange(value) + } + // datetime 和 datetimerange 类型,需要计算 timeData 并做展示 + if (this.data.type.indexOf('time') > -1) { + this.setTime(value, type) + } + }, + handleChange (value) { + this.$emit('change', { + value + }) + }, + handleTimeChange (event) { + const { value } = event.detail + + if (this.data.type === 'datetime') { + const date = new Date(this.data.value) + date.setHours(value[0]) + date.setMinutes(value[1]) + date.setSeconds(this.data.hideSecond ? 0 : value[2]) + const dateTime = date.getTime() + + this.setData({ + timeData: this.getTimeData(dateTime), + timeValue: value + }) + this.handleChange(dateTime) + } else { + const [start, end] = this.data.value + const dataValue = this.data.timeType === 'start' ? start : end + const date = new Date(dataValue) + date.setHours(value[0]) + date.setMinutes(value[1]) + date.setSeconds(this.data.hideSecond ? 0 : value[2]) + const dateTime = date.getTime() + + if (dateTime === dataValue) return + + const finalValue = [start, end] + if (this.data.timeType === 'start') { + finalValue[0] = dateTime + } else { + finalValue[1] = dateTime + } + + this.setData({ + timeData: this.getTimeData(finalValue, this.data.timeType), + timeValue: value, + innerValue: finalValue // 内部保存一个值,用于判断新老值,避免监听器触发 + }) + + this.handleChange(finalValue) + } + }, + handlePickStart () { + this.$emit('pickstart') + }, + handlePickEnd () { + this.$emit('pickend') + } + }, + beforeCreate () { + this.handleChange = debounce(this.handleChange, 50) + } +}) \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-calendar-view/components/monthPanel/index.json b/src/uni_modules/wot-design-uni/components/wd-calendar-view/components/monthPanel/index.json new file mode 100644 index 00000000..826e2d29 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-calendar-view/components/monthPanel/index.json @@ -0,0 +1,7 @@ +{ + "component": true, + "usingComponents": { + "month": "../month/index", + "wd-picker-view": "../../../pickerView/index" + } +} diff --git a/src/uni_modules/wot-design-uni/components/wd-calendar-view/components/monthPanel/index.jxml b/src/uni_modules/wot-design-uni/components/wd-calendar-view/components/monthPanel/index.jxml new file mode 100644 index 00000000..dc387165 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-calendar-view/components/monthPanel/index.jxml @@ -0,0 +1,51 @@ + + + + + {{ title }} + + + {{ utils.getWeekLabel(item + firstDayOfWeek) }} + + + + + + + {{ timeType === 'start' ? '开始' : '结束' }} + + + + + + \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-calendar-view/components/monthPanel/index.scss b/src/uni_modules/wot-design-uni/components/wd-calendar-view/components/monthPanel/index.scss new file mode 100644 index 00000000..e4bd9b09 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-calendar-view/components/monthPanel/index.scss @@ -0,0 +1,62 @@ +@import '../../../common/abstracts/variable'; +@import '../../../common/abstracts/mixin'; + +@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; + } +} \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-calendar-view/components/year/index.js b/src/uni_modules/wot-design-uni/components/wd-calendar-view/components/year/index.js new file mode 100644 index 00000000..d8ce8041 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-calendar-view/components/year/index.js @@ -0,0 +1,165 @@ +import VueComponent from '../../../common/component' +import { compareMonth, getMonthOffset, getMonthByOffset, getDateByDefaultTime } from '../../utils' +import { getType } from '../../../common/util' +import Toast from '../../../toast/toast.js' + +VueComponent({ + data: { + months: [] + }, + props: { + type: { + type: String, + observer: 'setMonths' + }, + date: { + type: Number, + observer: 'setMonths' + }, + value: { + type: [null, Number, Array], + observer: 'setMonths' + }, + minDate: { + type: Number, + observer: 'setMonths' + }, + maxDate: { + type: Number, + observer: 'setMonths' + }, + formatter: { + type: null, + observer: 'setMonths' + }, + maxRange: Number, + rangePrompt: String, + allowSameDay: Boolean, + defaultTime: Array + }, + methods: { + setMonths () { + const months = [] + const date = new Date(this.data.date) + const year = date.getFullYear() + + const value = this.data.value + const valueType = getType(value) + + if (this.data.type.indexOf('range') > -1 && value && valueType !== 'array') { + 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 = this.getMonthType(date, value) + if (!type && compareMonth(date, Date.now()) === 0) { + type = 'current' + } + const monthObj = this.getFormatterDate(date, month, type) + months.push(monthObj) + } + + this.setData({ + months + }) + }, + getMonthType (date) { + if (this.data.type === 'monthrange') { + const [startDate, endDate] = this.data.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 (this.data.value && compareMonth(date, this.data.value) === 0) { + return 'selected' + } else { + return '' + } + } + }, + handleDateClick (event) { + const { index } = event.currentTarget.dataset + const date = this.data.months[index] + + if (date.disabled) return + + switch (this.data.type) { + case 'month': + this.handleMonthChange(date) + break + case 'monthrange': + this.handleMonthRangeChange(date) + break + default: + this.handleMonthChange(date) + } + }, + getDate (date) { + return this.data.defaultTime && this.data.defaultTime.length > 0 ? getDateByDefaultTime(date, this.data.defaultTime[0]) : date + }, + handleMonthChange (date) { + if (date.type !== 'selected') { + this.$emit('change', { + value: this.getDate(date.date) + }) + } + }, + handleMonthRangeChange (date) { + let value + const [startDate, endDate] = this.data.value || [] + const compare = compareMonth(date.date, startDate) + + // 禁止选择同个日期 + if (!this.data.allowSameDay && !endDate && compare === 0) return + + if (startDate && !endDate && compare > -1) { + if (this.data.maxRange && getMonthOffset(date.date, startDate) > this.data.maxRange) { + const maxEndDate = getMonthByOffset(startDate, this.data.maxRange - 1) + value = [startDate, this.getDate(maxEndDate)] + Toast({ + msg: this.data.rangePrompt || `选择月份不能超过${this.data.maxRange}个月`, + context: this + }) + } else { + value = [startDate, this.getDate(date.date)] + } + } else { + value = [this.getDate(date.date), null] + } + this.$emit('change', { + value + }) + }, + getFormatterDate (date, month, type) { + let monthObj = { + date: date, + text: month + 1, + topInfo: '', + bottomInfo: '', + type, + disabled: compareMonth(date, this.data.minDate) === -1 || compareMonth(date, this.data.maxDate) === 1 + } + if (this.data.formatter) { + if (getType(this.data.formatter) === 'function') { + monthObj = this.data.formatter(monthObj) + } else { + console.error('[wot-design] error(wd-calendar-view): the formatter prop of wd-calendar-view should be a function') + } + } + + return monthObj + } + } +}) \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-calendar-view/components/year/index.json b/src/uni_modules/wot-design-uni/components/wd-calendar-view/components/year/index.json new file mode 100644 index 00000000..ac3c6bc5 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-calendar-view/components/year/index.json @@ -0,0 +1,6 @@ +{ + "component": true, + "usingComponents": { + "wd-toast": "../../../toast/index" + } +} diff --git a/src/uni_modules/wot-design-uni/components/wd-calendar-view/components/year/index.jxml b/src/uni_modules/wot-design-uni/components/wd-calendar-view/components/year/index.jxml new file mode 100644 index 00000000..f29a7c2a --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-calendar-view/components/year/index.jxml @@ -0,0 +1,22 @@ + + + + + + {{ utils.formatYearTitle(date) }} + + + {{ item.topInfo }} + + {{ item.text }}月 + + {{ item.bottomInfo }} + + + diff --git a/src/uni_modules/wot-design-uni/components/wd-calendar-view/components/year/index.scss b/src/uni_modules/wot-design-uni/components/wd-calendar-view/components/year/index.scss new file mode 100644 index 00000000..8e252d89 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-calendar-view/components/year/index.scss @@ -0,0 +1,112 @@ +@import '../../../common/abstracts/variable'; +@import '../../../common/abstracts/mixin'; + +@include b(year) { + @include e(title) { + padding: 13px 0; + text-align: center; + 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; + + @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: #fff; + + &::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; + } + &.is-without-end::after { + display: none; + } + } + @include when(end) { + color: #fff; + + &::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: $-calendar-active-border; + } + } + @include when(same) { + color: #fff; + + .wd-year__month-text { + background: $-calendar-active-color; + border-radius: $-calendar-active-border; + } + } + } + @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; + } +} \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-calendar-view/components/yearPanel/index.js b/src/uni_modules/wot-design-uni/components/wd-calendar-view/components/yearPanel/index.js new file mode 100644 index 00000000..e3033be6 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-calendar-view/components/yearPanel/index.js @@ -0,0 +1,87 @@ +import VueComponent from '../../../common/component' +import { getYears, compareYear, formatYearTitle } from '../../utils' +import { getType } from '../../../common/util' + +VueComponent({ + props: { + type: String, + value: { + type: [null, Number, Array] + }, + minDate: Number, + maxDate: Number, + formatter: null, + maxRange: Number, + rangePrompt: String, + allowSameDay: Boolean, + showPanelTitle: Boolean, + defaultTime: Array, + panelHeight: Number + }, + data: { + title: '', + scrollIntoView: '' + }, + mounted () { + this.initRect() + this.scrollIntoView() + }, + methods: { + initRect (thresholds = [0, 0.15, 0.7, 0.8, 0.9, 1]) { + if (!this.data.showPanelTitle) return + + if (this.contentObserver != null) { + this.contentObserver.disconnect() + } + + const contentObserver = this.createIntersectionObserver({ + thresholds, + observeAll: true + }) + + this.contentObserver = contentObserver + + contentObserver.relativeTo('.wd-year-panel__container') + contentObserver.observe('.year', (res) => { + if (res.boundingClientRect.top <= res.relativeRect.top) { + this.setData({ + title: formatYearTitle(res.dataset.date) + }) + } + }) + }, + scrollIntoView () { + this.requestAnimationFrame().then(() => { + let activeDate + const type = getType(this.data.value) + if (type === 'array') { + activeDate = this.data.value[0] + } else if (type === 'number') { + activeDate = this.data.value + } + + if (!activeDate) { + activeDate = Date.now() + } + + const years = getYears(this.data.minDate, this.data.maxDate) + + years.some((year, index) => { + if (compareYear(year, activeDate) === 0) { + this.setData({ + scrollIntoView: `year${index}` + }) + return true + } + + return false + }) + }) + }, + handleDateChange ({ detail: { value } }) { + this.$emit('change', { + value + }) + } + } +}) \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-calendar-view/components/yearPanel/index.json b/src/uni_modules/wot-design-uni/components/wd-calendar-view/components/yearPanel/index.json new file mode 100644 index 00000000..fe06877d --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-calendar-view/components/yearPanel/index.json @@ -0,0 +1,6 @@ +{ + "component": true, + "usingComponents": { + "year": "../year/index" + } +} diff --git a/src/uni_modules/wot-design-uni/components/wd-calendar-view/components/yearPanel/index.jxml b/src/uni_modules/wot-design-uni/components/wd-calendar-view/components/yearPanel/index.jxml new file mode 100644 index 00000000..f2aa2c89 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-calendar-view/components/yearPanel/index.jxml @@ -0,0 +1,25 @@ + + + + {{ title }} + + + + \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-calendar-view/components/yearPanel/index.scss b/src/uni_modules/wot-design-uni/components/wd-calendar-view/components/yearPanel/index.scss new file mode 100644 index 00000000..9accab5c --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-calendar-view/components/yearPanel/index.scss @@ -0,0 +1,15 @@ +@import '../../../common/abstracts/variable'; +@import '../../../common/abstracts/mixin'; + +@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); + } +} \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-calendar-view/index.js b/src/uni_modules/wot-design-uni/components/wd-calendar-view/index.js new file mode 100644 index 00000000..f78f7e27 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-calendar-view/index.js @@ -0,0 +1,79 @@ +import VueComponent from '../common/component' +import { getDefaultTime } from './utils' + +const current = new Date() +const currentYear = current.getFullYear() +const currentMonth = current.getMonth() +const currentDay = current.getDate() + +VueComponent({ + behaviors: ['jd://form-field'], + props: { + value: { + type: [null, Number, Array] + }, + type: { + type: String, + value: 'date' + }, + minDate: { + type: Number, + value: new Date(currentYear, currentMonth - 6, currentDay).getTime() + }, + maxDate: { + type: Number, + value: new Date(currentYear, currentMonth + 6, currentDay, 23, 59, 59).getTime() + }, + firstDayOfWeek: { + type: Number, + value: 0 + }, + formatter: null, + maxRange: Number, + rangePrompt: String, + allowSameDay: Boolean, + showPanelTitle: { + type: Boolean, + value: true + }, + defaultTime: { + type: [String, Array], + observer (val) { + this.setData({ + formatDefauleTime: getDefaultTime(val) + }) + } + }, + panelHeight: Number, + timeFilter: null, + hideSecond: Boolean + }, + data: { + formatDefauleTime: [] + }, + methods: { + // 对外暴露方法 + scrollIntoView (thresholds) { + const panel = this.getPanel() + panel.initRect && panel.initRect(thresholds) + panel.scrollIntoView() + }, + getPanel () { + return this.data.type.indexOf('month') > -1 ? this.selectComponent('#yearPanel') : this.selectComponent('#monthPanel') + }, + handleChange ({ detail: { value } }) { + this.setData({ + value + }) + this.$emit('change', { + value + }) + }, + handlePickStart () { + this.$emit('pickstart') + }, + handlePickEnd () { + this.$emit('pickend') + } + } +}) \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-calendar-view/index.json b/src/uni_modules/wot-design-uni/components/wd-calendar-view/index.json new file mode 100644 index 00000000..50c0f657 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-calendar-view/index.json @@ -0,0 +1,8 @@ +{ + "component": true, + "usingComponents": { + "wd-icon": "../icon/index", + "year-panel": "./components/yearPanel/index", + "month-panel": "./components/monthPanel/index" + } +} diff --git a/src/uni_modules/wot-design-uni/components/wd-calendar-view/index.jxml b/src/uni_modules/wot-design-uni/components/wd-calendar-view/index.jxml new file mode 100644 index 00000000..f2d8f0e0 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-calendar-view/index.jxml @@ -0,0 +1,39 @@ + + + + diff --git a/src/uni_modules/wot-design-uni/components/wd-calendar-view/index.scss b/src/uni_modules/wot-design-uni/components/wd-calendar-view/index.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/uni_modules/wot-design-uni/components/wd-calendar-view/utils.jds b/src/uni_modules/wot-design-uni/components/wd-calendar-view/utils.jds new file mode 100644 index 00000000..c1ba66f9 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-calendar-view/utils.jds @@ -0,0 +1,176 @@ +var weeks = ['日', '一', '二', '三', '四', '五', '六'] + +/** + * 比较两个日期的月份是否相等 + * @param {timestamp} date1 + * @param {timestamp} date2 + */ +function compareMonth (date1, date2) { + date1 = getDate(date1) + date2 = getDate(date2) + + var year1 = date1.getFullYear() + var year2 = date2.getFullYear() + var month1 = date1.getMonth() + var month2 = date2.getMonth() + + if (year1 === year2) { + return month1 === month2 ? 0 : (month1 > month2 ? 1 : -1) + } + + return year1 > year2 ? 1 : -1 +} + +/** + * 比较两个日期的年份是否相等 + * @param {timestamp} date1 + * @param {timestamp} date2 + */ +function compareYear (date1, date2) { + date1 = getDate(date1) + date2 = getDate(date2) + + var year1 = date1.getFullYear() + var year2 = date2.getFullYear() + + return year1 === year2 ? 0 : (year1 > year2 ? 1 : -1) +} + +function compareDay (date1, date2) { + date1 = getDate(date1) + date2 = getDate(date2) + + var year1 = date1.getFullYear() + var year2 = date2.getFullYear() + var month1 = date1.getMonth() + var month2 = date2.getMonth() + var day1 = date1.getDate() + var day2 = date2.getDate() + + if (year1 === year2) { + if (month1 === month2) { + return day1 === day2 ? 0 : (day1 > day2 ? 1 : -1) + } + + return month1 > month2 ? 1 : -1 + } + + return year1 > year2 ? 1 : -1 +} + +/** + * 根据最小日期和最大日期获取这之间总共有几个月份 + * @param {timestamp} minDate + * @param {timestamp} maxDate + */ +function getMonths (minDate, maxDate) { + var months = [] + var month = getDate(minDate) + month.setDate(1) + + while (compareMonth(month, maxDate) < 1) { + months.push(month.getTime()) + month.setMonth(month.getMonth() + 1) + } + + return months +} + +function getYears (minDate, maxDate) { + var years = [] + var year = getDate(minDate) + year.setMonth(0) + year.setDate(1) + + while (compareYear(year, maxDate) < 1) { + years.push(year.getTime()) + year.setFullYear(year.getFullYear() + 1) + } + + return years +} + +/** + * 根据下标获取星期 + * @param {number} index + */ +function getWeekLabel (index) { + if (index >= 7) { + index = index % 7 + } + + return weeks[index] +} + +/** + * 格式化年月 + * @param {timestamp} date + */ +function formatMonthTitle (date) { + date = getDate(date) + var year = date.getFullYear() + var month = date.getMonth() + 1 + + return year + '年' + month + '月' +} + +/** + * 格式化年 + * @param {timestamp} date + */ +function formatYearTitle (date) { + date = getDate(date) + var year = date.getFullYear() + + return year + '年' +} + +/** + * 获取一个月第一天的样式 + * @param {number} index + * @param {timestamp} date + * @param {number} firstDayOfWeek + */ +function getFirstDayStyle (index, date, firstDayOfWeek) { + if (firstDayOfWeek >= 7) { + firstDayOfWeek = firstDayOfWeek % 7 + } + + if (index !== 0) return '' + + date = getDate(date) + var offset = (7 + date.getDay() - firstDayOfWeek) % 7 + + return 'margin-left: ' + (100 / 7) * offset + '%' +} + +function getItemClass (monthType, value, type) { + var classList = [('is-' + monthType)] + + if (type.indexOf('range') > -1) { + if (!value || !value[1]) { + classList.push('is-without-end') + } + } + + return classList.join(' ') +} + +/** + * 判断是否是范围选择 + * @param {string} type + */ +function isRange (type) { + return type.indexOf('range') > -1 +} + +module.exports = { + getMonths: getMonths, + getWeekLabel: getWeekLabel, + getFirstDayStyle: getFirstDayStyle, + formatMonthTitle: formatMonthTitle, + getYears: getYears, + formatYearTitle: formatYearTitle, + getItemClass: getItemClass, + isRange: isRange +} \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-calendar-view/utils.js b/src/uni_modules/wot-design-uni/components/wd-calendar-view/utils.js new file mode 100644 index 00000000..369f76c8 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-calendar-view/utils.js @@ -0,0 +1,374 @@ +import { getType, padZero } from '../common/util' + +/** + * 比较两个时间的日期是否相等 + * @param {timestamp} date1 + * @param {timestamp} date2 + */ +export function compareDate (date1, date2) { + date1 = new Date(date1) + date2 = new Date(date2) + + const year1 = date1.getFullYear() + const year2 = date2.getFullYear() + const month1 = date1.getMonth() + const month2 = date2.getMonth() + const day1 = date1.getDate() + const day2 = date2.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 {timestamp} date1 + * @param {timestamp} date2 + */ +export function compareMonth (date1, date2) { + date1 = new Date(date1) + date2 = new Date(date2) + + const year1 = date1.getFullYear() + const year2 = date2.getFullYear() + const month1 = date1.getMonth() + const month2 = date2.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, date2) { + date1 = new Date(date1) + date2 = new Date(date2) + + const year1 = date1.getFullYear() + const year2 = date2.getFullYear() + + return year1 === year2 ? 0 : (year1 > year2 ? 1 : -1) +} + +/** + * 获取一个月的最后一天 + * @param {number} year + * @param {number} month + */ +export function getMonthEndDay (year, month) { + return 32 - new Date(year, month - 1, 32).getDate() +} + +/** + * 格式化年月 + * @param {timestamp} date + */ +export function formatMonthTitle (date) { + date = new Date(date) + + const year = date.getFullYear() + const month = date.getMonth() + 1 + + return year + '年' + month + '月' +} + +/** + * 格式化年份 + * @param {timestamp} date + */ +export function formatYearTitle (date) { + date = new Date(date) + + const year = date.getFullYear() + + return year + '年' +} + +/** + * 根据最小日期和最大日期获取这之间总共有几个月份 + * @param {timestamp} minDate + * @param {timestamp} maxDate + */ +export function getMonths (minDate, maxDate) { + const months = [] + const month = new Date(minDate) + month.setDate(1) + + while (compareMonth(month, maxDate) < 1) { + months.push(month.getTime()) + month.setMonth(month.getMonth() + 1) + } + + return months +} + +/** + * 根据最小日期和最大日期获取这之间总共有几年 + * @param {timestamp} minDate + * @param {timestamp} maxDate + */ +export function getYears (minDate, maxDate) { + const years = [] + const year = new Date(minDate) + year.setMonth(0) + year.setDate(1) + + while (compareYear(year, maxDate) < 1) { + years.push(year.getTime()) + year.setFullYear(year.getFullYear() + 1) + } + + return years +} + +/** + * 获取一个日期所在周的第一天和最后一天 + * @param {timestamp} date + */ +export function getWeekRange (date, firstDayOfWeek) { + if (firstDayOfWeek >= 7) { + firstDayOfWeek = firstDayOfWeek % 7 + } + + date = new Date(date) + date.setHours(0, 0, 0, 0) + const year = date.getFullYear() + const month = date.getMonth() + const day = date.getDate() + const week = date.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, date2) { + return (date1 - date2) / (24 * 60 * 60 * 1000) + 1 +} + +/** + * 获取偏移日期 + * @param {timestamp} date + * @param {number} offset + */ +export function getDayByOffset (date, offset) { + date = new Date(date) + date.setDate(date.getDate() + offset) + + return date.getTime() +} + +/** + * 获取月份偏移量 + * @param {timestamp} date1 + * @param {timestamp} date2 + */ +export function getMonthOffset (date1, date2) { + date1 = new Date(date1) + date2 = new Date(date2) + + const year1 = date1.getFullYear() + const year2 = date2.getFullYear() + let month1 = date1.getMonth() + const month2 = date2.getMonth() + + month1 = (year1 - year2) * 12 + month1 + + return month1 - month2 + 1 +} + +/** + * 获取偏移月份 + * @param {timestamp} date + * @param {number} offset + */ +export function getMonthByOffset (date, offset) { + date = new Date(date) + date.setMonth(date.getMonth() + offset) + + return date.getTime() +} + +/** + * 获取默认时间,格式化为数组 + * @param {array|string|null} defaultTime + */ +export function getDefaultTime (defaultTime) { + if (getType(defaultTime) === 'array') { + const startTime = (defaultTime[0] || '00:00:00').split(':').map(item => { + 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, defaultTime) { + date = new Date(date) + date.setHours(defaultTime[0]) + date.setMinutes(defaultTime[1]) + date.setSeconds(defaultTime[2]) + + return date.getTime() +} + +/** + * 获取经过 iteratee 格式化后的长度为 n 的数组 + * @param {number} n + * @param {function} iteratee + */ +const times = (n, iteratee) => { + let index = -1 + const result = Array(n < 0 ? 0 : n) + while (++index < n) { + result[index] = iteratee(index) + } + return result +} + +/** + * 获取时分秒 + * @param {timestamp}} date + */ +const getTime = (date) => { + date = new Date(date) + return [date.getHours(), date.getMinutes(), date.getSeconds()] +} + +/** + * 根据最小最大日期获取时间数据,用于填入picker + * @param {*} param0 + */ +export function getTimeData ({ date, minDate, maxDate, isHideSecond, filter } = {}) { + 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 = [] + let hours = times(24, index => { + return { + label: `${padZero(index)}时`, + value: index, + disabled: index < minHour || index > maxHour + } + }) + let minutes = times(60, index => { + return { + label: `${padZero(index)}分`, + value: index, + disabled: index < minMinute || index > maxMinute + } + }) + let seconds + if (filter && getType(filter) === 'function') { + hours = filter({ + type: 'hour', + values: hours + }) + minutes = filter({ + type: 'minute', + values: minutes + }) + } + + if (!isHideSecond) { + seconds = times(60, index => { + return { + label: `${padZero(index)}秒`, + value: index, + disabled: index < minSecond || index > maxSecond + } + }) + if (filter && getType(filter) === 'function') { + seconds = filter({ + type: 'second', + values: seconds + }) + } + } + + columns = isHideSecond ? [hours, minutes] : [hours, minutes, seconds] + + return columns +} + +/** + * 获取当前是第几周 + * @param {timestamp} date + */ +export function getWeekNumber (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) +} \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-calendar-view/wd-calendar-view.vue b/src/uni_modules/wot-design-uni/components/wd-calendar-view/wd-calendar-view.vue new file mode 100644 index 00000000..f98b7664 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-calendar-view/wd-calendar-view.vue @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-calendar/index.js b/src/uni_modules/wot-design-uni/components/wd-calendar/index.js new file mode 100644 index 00000000..f32dbe4e --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-calendar/index.js @@ -0,0 +1,377 @@ +import VueComponent from '../common/component' +import { isEqual, padZero } from '../common/util' +import { getDefaultTime, getWeekNumber } from '../calendarView/utils' +import cell from '../mixins/cell' +import dayjs from '../common/dayjs.min' + +const current = new Date() +const currentYear = current.getFullYear() +const currentMonth = current.getMonth() +const currentDay = current.getDate() + +const defaultDisplayFormat = (value, type) => { + switch (type) { + case 'date': + return dayjs(value).format('YYYY-MM-DD') + case 'dates': + return value + .map((item) => { + return dayjs(item).format('YYYY-MM-DD') + }) + .join(', ') + case 'daterange': + return `${value[0] ? dayjs(value[0]).format('YYYY-MM-DD') : '开始时间'} 至 ${value[1] ? dayjs(value[1]).format('YYYY-MM-DD') : '结束时间'}` + case 'datetime': + return dayjs(value).format('YYYY-MM-DD HH:mm:ss') + case 'datetimerange': + return `${value[0] ? dayjs(value[0]).format('YY年MM月DD日 HH:mm:ss') : '开始时间'} 至\n${ + value[1] ? dayjs(value[1]).format('YY年MM月DD日 HH:mm:ss') : '结束时间' + }` + case 'week': { + const year = new Date(value).getFullYear() + const week = getWeekNumber(value) + return `${year} 第 ${padZero(week)} 周` + } + case 'weekrange': { + const year1 = new Date(value[0]).getFullYear() + const week1 = getWeekNumber(value[0]) + const year2 = new Date(value[1]).getFullYear() + const week2 = getWeekNumber(value[1]) + return `${value[0] ? `${year1} 第 ${padZero(week1)} 周` : '开始周'} - ${value[1] ? `${year2} 第 ${padZero(week2)} 周` : '结束周'}` + } + case 'month': + return dayjs(value).format('YYYY / MM') + case 'monthrange': + return `${value[0] ? dayjs(value[0]).format('YYYY / MM') : '开始月'} 至 ${value[1] ? dayjs(value[1]).format('YYYY / MM') : '结束月'}` + } +} + +const formatRange = (value, rangeType, type) => { + switch (type) { + case 'daterange': + if (!value) { + return rangeType === 'end' ? '结束时间' : '开始时间' + } + return dayjs(value).format('YYYY年MM月DD日') + case 'datetimerange': + if (!value) { + return rangeType === 'end' ? '结束时间' : '开始时间' + } + return dayjs(value).format('YY年MM月DD日 HH:mm:ss') + case 'weekrange': { + if (!value) { + return rangeType === 'end' ? '结束周' : '开始周' + } + const date = new Date(value) + const year = date.getFullYear() + const week = getWeekNumber(value) + return year + '年第' + week + '周' + } + case 'monthrange': + if (!value) { + return rangeType === 'end' ? '结束月' : '开始月' + } + return dayjs(value).format('YYYY年MM月') + } +} + +VueComponent({ + externalClasses: ['custom-view-class', 'custom-label-class', 'custom-value-class'], + behaviors: [cell, 'jd://form-field'], + relations: { + '../cellGroup/index': { + type: 'ancestor', + linked(target) { + this.parent = target + }, + unlinked() { + this.parent = null + } + } + }, + props: { + value: { + type: [null, Number, Array], + observer(val, oldVal) { + if (isEqual(val, oldVal)) return + + this.setData( + { + calendarValue: val, + confirmBtnDisabled: this.getConfirmBtnStatus(val) + }, + () => { + this.scrollIntoView() + } + ) + + this.setShowValue() + + if (this.data.type.indexOf('range') > -1) { + this.setInnerLabel() + } + } + }, + type: { + type: String, + value: 'date', + observer(val) { + if (this.data.showTypeSwitch) { + const tabs = ['date', 'week', 'month'] + const rangeTabs = ['daterange', 'weekrange', 'monthrange'] + + const index = val.indexOf('range') > -1 ? rangeTabs.indexOf(val) || 0 : tabs.indexOf(val) + this.setData({ + currentTab: index + }) + } + + this.setData({ + panelHeight: this.data.showConfirm ? 338 : 400, + currentType: val + }) + + if (this.data.type.indexOf('range') > -1) { + this.setInnerLabel() + } + } + }, + minDate: { + type: Number, + value: new Date(currentYear, currentMonth - 6, currentDay).getTime() + }, + maxDate: { + type: Number, + value: new Date(currentYear, currentMonth + 6, currentDay, 23, 59, 59).getTime() + }, + firstDayOfWeek: { + type: Number, + value: 0 + }, + formatter: null, + maxRange: Number, + rangePrompt: String, + allowSameDay: Boolean, + defaultTime: { + type: [String, Array], + observer(val) { + this.setData({ + formatDefauleTime: getDefaultTime(val) + }) + } + }, + timeFilter: null, + hideSecond: Boolean, + label: String, + labelWidth: String, + useLabelSlot: Boolean, + useDefaultSlot: Boolean, + disabled: Boolean, + readonly: Boolean, + placeholder: String, + title: String, + alignRight: Boolean, + error: Boolean, + required: Boolean, + size: String, + center: Boolean, + closeOnClickModal: { + type: Boolean, + value: true + }, + zIndex: { + type: Number, + value: 15 + }, + showConfirm: { + type: Boolean, + value: true, + observer(val) { + this.setData({ + panelHeight: val ? 338 : 400 + }) + } + }, + confirmText: String, + displayFormat: null, + innerDisplayFormat: null, + ellipsis: Boolean, + showTypeSwitch: Boolean, + shortcuts: Array, + onShortcutsClick: null, + safeAreaInsetBottom: { + type: Boolean, + value: true + }, + beforeConfirm: null + }, + data: { + pickerShow: false, + calendarValue: '', + lastCalendarValue: '', + panelHeight: 338, + confirmBtnDisabled: true, + showValue: '', + currentTab: 0, + lastTab: 0, + currentType: 'date', + lastCurrentType: 'date', + inited: false, + rangeLabel: [] + }, + methods: { + scrollIntoView() { + const calendarView = this.selectComponent('#calendarView') + calendarView && calendarView.scrollIntoView() + }, + // 对外暴露方法 + open() { + const { disabled, readonly } = this.data + + if (disabled || readonly) return + + this.setData( + { + inited: true, + pickerShow: true, + lastCalendarValue: this.data.calendarValue, + lastTab: this.data.currentTab, + lastCurrentType: this.data.currentType + }, + () => { + setTimeout(() => { + this.scrollIntoView() + + if (this.data.showTypeSwitch) { + const tab = this.selectComponent('#tabs') + tab.scrollIntoView() + tab.updateLineStyle(false) + } + }, 250) + } + ) + }, + // 对外暴露方法 + close() { + this.setData({ + pickerShow: false + }) + setTimeout(() => { + const confirmBtnDisabled = this.getConfirmBtnStatus(this.data.lastCalendarValue) + + this.setData({ + calendarValue: this.data.lastCalendarValue, + currentTab: this.data.lastTab, + currentType: this.data.lastCurrentType, + confirmBtnDisabled + }) + }, 250) + this.$emit('cancel') + }, + handleTypeChange({ detail: { index } }) { + const tabs = ['date', 'week', 'month'] + const rangeTabs = ['daterange', 'weekrange', 'monthrange'] + const type = this.data.type.indexOf('range') > -1 ? rangeTabs[index] : tabs[index] + + this.setData({ + currentTab: index, + currentType: type + }) + }, + getConfirmBtnStatus(value) { + let confirmBtnDisabled = false + // 范围选择未选择满,或者多日期选择未选择日期,按钮置灰不可点击 + if ( + (this.data.type.indexOf('range') > -1 && (!value[0] || !value[1] || !value)) || + (this.data.type === 'dates' && (value.length === 0 || !value)) || + !value + ) { + confirmBtnDisabled = true + } + + return confirmBtnDisabled + }, + handleChange({ detail: { value } }) { + const confirmBtnDisabled = this.getConfirmBtnStatus(value) + this.setData({ + calendarValue: value, + confirmBtnDisabled + }) + + this.$emit('change', { + value + }) + + if (this.data.type.indexOf('range') > -1) { + this.setInnerLabel() + } + + if (!this.data.showConfirm && !confirmBtnDisabled) { + this.handleConfirm() + } + }, + handleConfirm() { + if (this.data.beforeConfirm) { + this.data.beforeConfirm({ + value: this.data.calendarValue, + resolve: (isPass) => { + isPass && this.onConfirm() + } + }) + } else { + this.onConfirm() + } + }, + onConfirm() { + this.setData({ + pickerShow: false, + value: this.data.calendarValue + }) + this.$emit('confirm', { + value: this.data.calendarValue + }) + this.setShowValue() + }, + setInnerLabel() { + const [start, end] = this.data.calendarValue || [] + this.setData({ + rangeLabel: [start, end].map((item, index) => { + return (this.data.innerDisplayFormat || formatRange)(item, index === 0 ? 'start' : 'end', this.data.currentType) + }) + }) + }, + setShowValue() { + if ( + (!(this.data.calendarValue instanceof Array) && this.data.calendarValue) || + (this.data.calendarValue instanceof Array && this.data.calendarValue.length) + ) { + this.setData({ + showValue: (this.data.displayFormat || defaultDisplayFormat)(this.data.calendarValue, this.data.currentType) + }) + } else { + this.setData({ + showValue: '' + }) + } + }, + handleShortcutClick(event) { + const { index } = event.target.dataset + + if (this.data.onShortcutsClick && typeof this.data.onShortcutsClick === 'function') { + const calendarValue = this.data.onShortcutsClick({ + item: this.data.shortcuts[index], + index + }) + const confirmBtnDisabled = this.getConfirmBtnStatus(calendarValue) + this.setData({ + calendarValue, + confirmBtnDisabled + }) + } + + if (!this.data.showConfirm) { + this.handleConfirm() + } + } + } +}) diff --git a/src/uni_modules/wot-design-uni/components/wd-calendar/index.json b/src/uni_modules/wot-design-uni/components/wd-calendar/index.json new file mode 100644 index 00000000..49cc92b1 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-calendar/index.json @@ -0,0 +1,12 @@ +{ + "component": true, + "usingComponents": { + "wd-action-sheet": "../actionSheet/index", + "wd-icon": "../icon/index", + "wd-calendar-view": "../calendarView/index", + "wd-button": "../button/index", + "wd-tabs": "../tabs/index", + "wd-tab": "../tab/index", + "wd-tag": "../tag/index" + } +} diff --git a/src/uni_modules/wot-design-uni/components/wd-calendar/index.jxml b/src/uni_modules/wot-design-uni/components/wd-calendar/index.jxml new file mode 100644 index 00000000..90f9df4b --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-calendar/index.jxml @@ -0,0 +1,87 @@ + + + + + + + + {{ label }} + + + {{ showValue || placeholder || '请选择' }} + + + + + + {{ title || '选择日期' }} + + + + + + + + + {{ item.text }} + + + + + + + {{ rangeLabel[0] }} + + / + + {{ rangeLabel[1] }} + + + + + + {{ confirmText || '确定' }} + + + diff --git a/src/uni_modules/wot-design-uni/components/wd-calendar/index.scss b/src/uni_modules/wot-design-uni/components/wd-calendar/index.scss new file mode 100644 index 00000000..b4d92ef3 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-calendar/index.scss @@ -0,0 +1,165 @@ +@import '../common/abstracts/variable'; +@import '../common/abstracts/mixin'; + +@include b(calendar) { + @include when(border) { + .wd-calendar__cell { + @include halfPixelBorder('top', $-cell-padding); + } + } + @include e(cell) { + position: relative; + display: flex; + padding: 0 $-cell-padding; + background-color: $-color-white; + text-decoration: none; + color: $-cell-title-color; + font-size: $-cell-title-fs; + overflow: hidden; + line-height: $-cell-ling-height; + } + @include e(cell) { + @include when(disabled) { + .wd-calendar__value { + color: $-input-disabled-color; + } + } + @include when(align-right) { + .wd-calendar__value { + text-align: right; + } + } + @include when(error) { + .wd-calendar__value { + color: $-input-error-color; + } + .wd-calendar__arrow { + color: $-input-error-color; + } + } + @include when(large) { + font-size: $-cell-title-fs-large; + + .wd-calendar__arrow { + font-size: $-cell-icon-size-large; + } + } + @include when(center) { + align-items: center; + + .wd-calendar__arrow { + margin-top: 0; + } + } + } + @include e(label) { + position: relative; + width: $-input-cell-label-width; + padding: $-cell-wrapper-padding 0; + margin-right: $-cell-padding; + box-sizing: border-box; + + @include when(required) { + padding-left: 12px; + + &::after { + position: absolute; + left: 0; + top: $-cell-wrapper-padding + 2px; + content: '*'; + font-size: $-cell-required-size; + line-height: 1.1; + color: $-cell-required-color; + } + } + } + @include e(value) { + flex: 1; + padding: $-cell-wrapper-padding 0; + margin-right: 10px; + color: $-cell-value-color; + white-space: pre-wrap; + + @include when(ellipsis) { + @include lineEllipsis; + } + @include m(placeholder) { + color: $-input-placeholder-color; + } + } + @include e(arrow) { + display: block; + margin-top: $-cell-wrapper-padding; + font-size: $-cell-icon-size; + color: $-cell-arrow-color; + line-height: 1.25; + } + + @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 e(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 e(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) { + padding: 12px 25px 14px; + } +} \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-calendar/wd-calendar.vue b/src/uni_modules/wot-design-uni/components/wd-calendar/wd-calendar.vue new file mode 100644 index 00000000..42dad6ef --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-calendar/wd-calendar.vue @@ -0,0 +1,625 @@ + + + + + diff --git a/src/uni_modules/wot-design-uni/components/wd-card/index.js b/src/uni_modules/wot-design-uni/components/wd-card/index.js new file mode 100644 index 00000000..fe9579ec --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-card/index.js @@ -0,0 +1,13 @@ +import VueComponent from '../common/component' + +VueComponent({ + externalClasses: [ + 'custom-title-class', + 'custom-content-class', + 'custom-footer-class' + ], + props: { + title: String, + type: String + } +}) \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-card/index.json b/src/uni_modules/wot-design-uni/components/wd-card/index.json new file mode 100644 index 00000000..a89ef4db --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-card/index.json @@ -0,0 +1,4 @@ +{ + "component": true, + "usingComponents": {} +} diff --git a/src/uni_modules/wot-design-uni/components/wd-card/index.jxml b/src/uni_modules/wot-design-uni/components/wd-card/index.jxml new file mode 100644 index 00000000..2b2d4bdd --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-card/index.jxml @@ -0,0 +1,14 @@ + + + + {{ title }} + + + + + + + + + + \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-card/index.scss b/src/uni_modules/wot-design-uni/components/wd-card/index.scss new file mode 100644 index 00000000..59be8434 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-card/index.scss @@ -0,0 +1,49 @@ +@import "../common/abstracts/variable.scss"; +@import "../common/abstracts/_mixin.scss"; + +@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; + } +} diff --git a/src/uni_modules/wot-design-uni/components/wd-card/wd-card.vue b/src/uni_modules/wot-design-uni/components/wd-card/wd-card.vue new file mode 100644 index 00000000..361cbcff --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-card/wd-card.vue @@ -0,0 +1,82 @@ + + + + + diff --git a/src/uni_modules/wot-design-uni/components/wd-cell-group/index.js b/src/uni_modules/wot-design-uni/components/wd-cell-group/index.js new file mode 100644 index 00000000..7c7622e6 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-cell-group/index.js @@ -0,0 +1,39 @@ +import VueComponent from '../common/component' +import cell from '../mixins/cell' + +VueComponent({ + props: { + title: String, + value: String, + useSlot: { + type: Boolean, + value: false + }, + border: { + type: Boolean, + observer(val) { + this.children && + this.children.forEach((child) => { + child.setIndexAndStatus(val) + }) + } + } + }, + relations: { + cell: { + type: 'descendant', + target: cell, + linked(target) { + this.children = this.children || [] + this.children.push(target) + setTimeout(() => { + this.children.forEach((child) => child.setIndexAndStatus(this.data.border)) + }, 30) + }, + unlinked(target) { + this.children = this.children.filter((child) => child !== target) + this.children.forEach((child) => child.setIndexAndStatus(this.data.border)) + } + } + } +}) diff --git a/src/uni_modules/wot-design-uni/components/wd-cell-group/index.json b/src/uni_modules/wot-design-uni/components/wd-cell-group/index.json new file mode 100644 index 00000000..32640e0d --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-cell-group/index.json @@ -0,0 +1,3 @@ +{ + "component": true +} \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-cell-group/index.jxml b/src/uni_modules/wot-design-uni/components/wd-cell-group/index.jxml new file mode 100644 index 00000000..a4e91e21 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-cell-group/index.jxml @@ -0,0 +1,17 @@ + + + + + {{ title }} + + + + + {{ value }} + + + + + + + \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-cell-group/index.scss b/src/uni_modules/wot-design-uni/components/wd-cell-group/index.scss new file mode 100644 index 00000000..1f110f53 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-cell-group/index.scss @@ -0,0 +1,30 @@ +@import "../common/abstracts/variable.scss"; +@import "../common/abstracts/_mixin.scss"; + +@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; + } +} \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-cell-group/wd-cell-group.vue b/src/uni_modules/wot-design-uni/components/wd-cell-group/wd-cell-group.vue new file mode 100644 index 00000000..e8cb0076 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-cell-group/wd-cell-group.vue @@ -0,0 +1,71 @@ + + + + + diff --git a/src/uni_modules/wot-design-uni/components/wd-cell/index.js b/src/uni_modules/wot-design-uni/components/wd-cell/index.js new file mode 100644 index 00000000..1b9bb492 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-cell/index.js @@ -0,0 +1,66 @@ +import VueComponent from '../common/component' +import cell from '../mixins/cell' + +VueComponent({ + externalClasses: ['custom-icon-class', 'custom-label-class', 'custom-value-class', 'custom-title-class'], + behaviors: [cell], + props: { + title: String, + value: String, + icon: String, + label: String, + isLabel: String, + isLink: { + type: Boolean, + value: false, + observer: 'openClickAble' + }, + to: String, + replace: { + type: Boolean, + value: false + }, + clickable: { + type: Boolean, + value: false + }, + size: String, + titleWidth: String, + center: Boolean, + required: Boolean, + vertical: Boolean + }, + relations: { + '../cellGroup/index': { + type: 'ancestor', + linked(target) { + this.parent = target + }, + unlinked() { + this.parent = null + } + } + }, + methods: { + /** + * @description 开启点击反馈 + * @param {Boolean} clickAble - 触发订阅的实例本身 + */ + openClickAble(clickAble) { + if (!clickAble) return + this.setData({ clickable: true }) + }, + /** + * @description 点击cell的handle + */ + onClick() { + const url = this.data.to + if (url && this.data.isLink) { + jd[this.data.replace ? 'redirectTo' : 'navigateTo']({ url }) + } + if (this.data.clickable) { + this.$emit('click') + } + } + } +}) diff --git a/src/uni_modules/wot-design-uni/components/wd-cell/index.json b/src/uni_modules/wot-design-uni/components/wd-cell/index.json new file mode 100644 index 00000000..2393be1e --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-cell/index.json @@ -0,0 +1,6 @@ +{ + "component": true, + "usingComponents": { + "wd-icon": "../icon/index" + } +} diff --git a/src/uni_modules/wot-design-uni/components/wd-cell/index.jxml b/src/uni_modules/wot-design-uni/components/wd-cell/index.jxml new file mode 100644 index 00000000..ec535980 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-cell/index.jxml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + {{title}} + + + + + + + {{label}} + + + + + + + + + + {{value}} + + + + + + + + \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-cell/index.scss b/src/uni_modules/wot-design-uni/components/wd-cell/index.scss new file mode 100644 index 00000000..d003a263 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-cell/index.scss @@ -0,0 +1,128 @@ +@import "../common/abstracts/variable.scss"; +@import "../common/abstracts/_mixin.scss"; + +@include b(cell) { + position: relative; + padding-left: $-cell-padding; + background-color: $-color-white; + text-decoration: none; + color: $-cell-title-color; + line-height: $-cell-ling-height; + -webkit-tap-highlight-color: transparent; + + @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; + + @include when(vertical) { + display: block; + + .wd-cell__right { + margin-top: $-cell-vertical-top; + } + .wd-cell__value { + text-align: left; + } + } + @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; + margin-right: $-cell-padding; + font-size: $-cell-title-fs; + box-sizing: border-box; + + @include when(required) { + padding-left: 12px; + + &::after { + position: absolute; + content: '*'; + top: 0; + left: 0; + font-size: $-cell-required-size; + color: $-cell-required-color; + } + } + } + @include e(right) { + position: relative; + display: flex; + flex: 1; + } + @include e(title) { + flex: 1; + width: 100%; + font-size: $-cell-title-fs; + margin-right: $-cell-padding; + } + @include e(label) { + margin-top: 2px; + font-size: $-cell-label-fs; + color: $-cell-label-color; + @include lineEllipsis; + } + @include e(icon) { + display: block; + position: relative; + width: $-cell-icon-size; + height: $-cell-icon-size; + line-height: 1.25; + margin-right: $-cell-icon-right; + font-size: $-cell-icon-size; + } + @include e(value) { + position: relative; + flex: 1; + font-size: $-cell-value-fs; + color: $-cell-value-color; + text-align: right; + line-height: $-cell-value-line-height; + vertical-align: top; + } + @include e(arrow-right) { + display: inline-block; + margin-left: 8px; + width: $-cell-arrow-size; + line-height: 1.22; + font-size: $-cell-arrow-size; + color: $-cell-arrow-color; + vertical-align: top; + } + @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__label { + font-size: $-cell-label-fs-large; + } + .wd-cell__icon { + font-size: $-cell-icon-size-large; + width: $-cell-icon-size-large; + height: $-cell-icon-size-large; + } + } + @include when(center) { + .wd-cell__wrapper { + align-items: center; + } + } +} diff --git a/src/uni_modules/wot-design-uni/components/wd-cell/wd-cell.vue b/src/uni_modules/wot-design-uni/components/wd-cell/wd-cell.vue new file mode 100644 index 00000000..36aee010 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-cell/wd-cell.vue @@ -0,0 +1,265 @@ + + + + + diff --git a/src/uni_modules/wot-design-uni/components/wd-checkbox-group/index.js b/src/uni_modules/wot-design-uni/components/wd-checkbox-group/index.js new file mode 100644 index 00000000..8d0ecbfb --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-checkbox-group/index.js @@ -0,0 +1,200 @@ +import VueComponent from '../common/component' +import { checkNumRange, debounce, renderData } from '../common/util' + +VueComponent({ + behaviors: ['jd://form-field'], + relations: { + '../checkbox/index': { + type: 'descendant', + linked (target) { + this.children = this.children || [] + this.children.push(target) + const index = this.children.indexOf(target) + // 如果当前子节点为第一个组件,将其isFirst设置为true + if (index === 0) { + target.setData({ + isFirst: true + }) + } + // 如果当前子节点为最后一个组件,将其isLast设置为true,删掉倒数第二个子节点的isLast + if (index === this.children.length - 1) { + target.setData({ + isLast: true + }) + const [prevChild] = this.children.slice(-2, -1) + prevChild && renderData(prevChild, { isLast: false }) + } + }, + unlinked (target) { + this.children = this.children.filter(child => child !== target) + const index = this.children.indexOf(target) + + if (this.children.length === 0) return + // 如果当前删除的组件为第一个组件,将删除后的第一个组件的isFirst设置为true + if (index === 0) { + this.children[0].setData({ + isFirst: true + }) + } + // 如果当前删除的组件为最后一个组件,将删除后的倒数第一个组件的isLast设置为true + if (index === this.children.length - 1) { + this.children.slice(-1)[0].setData({ + isLast: true + }) + } + } + } + }, + props: { + value: { + type: Array, + value: [], + observer (value, oldVal) { + // 传入的value数组中包括重复的元素,这种情况非法。 + if (new Set(value).size !== value.length) { + throw Error('checkboxGroup\'s bound value includes same value') + } + if (value.length < this.data.min) { + throw Error('checkboxGroup\'s bound value\'s length can\'t be less than min') + } + if (this.data.max !== 0 && value.length > this.data.max) { + throw Error('checkboxGroup\'s bound value\'s length can\'t be large than max') + } + // 每次value变化都会触发重新匹配选中项 + this.children && this.children.length > 0 && this.resetChildren() + } + }, + cell: { + type: Boolean, + value: false, + // 以下内容用于解决父子组件样式隔离的问题 —— START + observer (value) { + this.children && this.children.forEach(child => { + child.setData({ cellBox: value }) + }) + } + // 以下内容用于解决父子组件样式隔离的问题 —— END + }, + shape: { + type: String, + value: 'circle', + observer (value) { + const type = ['circle', 'square', 'button'] + if (type.indexOf(value) === -1) throw Error(`shape must be one of ${type.toString()}`) + this.updateAllChild({ shape: value }) + // 以下内容用于解决父子组件样式隔离的问题 —— START + this.children && this.children.forEach(child => { + child.setData({ buttonBox: value === 'button' }) + }) + // 以下内容用于解决父子组件样式隔离的问题 —— END + } + }, + checkedColor: { + type: String, + observer (value) { + this.updateAllChild({ checkedColor: value }) + } + }, + disabled: { + type: Boolean, + value: null, + observer () { + // 当值修改时需要重新检测 + this.resetChildren() + } + }, + min: { + type: Number, + value: 0, + observer (value) { + checkNumRange(value, 'min') + // 当值修改时需要重新检测 + this.resetChildren() + } + }, + max: { + type: Number, + value: 0, + observer (value) { + checkNumRange(value, 'max') + // 当值修改时需要重新检测 + this.resetChildren() + } + }, + inline: { + type: Boolean, + value: false, + observer (value) { + this.updateAllChild({ inline: value }) + } + }, + size: { + type: String, + observer (value) { + this.updateAllChild({ size: value }) + } + } + }, + methods: { + /** + * @description 当和child建立relation后,用checkboxGroup的props覆盖checkbox中props值为null的属性。 + * @param {Object} data 属性键值对 + */ + updateAllChild (data) { + const keys = Object.keys(data) + this.children && this.children.forEach(child => { + const will = {} + keys.forEach(key => { + if ( + data[key] !== null && + data[key] !== undefined && + child.data[key] === null + ) { + will[key] = data[key] + } + }) + renderData(child, will) + }) + }, + /** + * @description 子节点通知父节点修改子节点选中状态 + * @param {any} value 子组件的标识符 + */ + changeSelectState (value) { + const temp = this.data.value + const index = temp.indexOf(value) + if (index > -1) { + // 已经选中,则从 value 列表中删除子节点的标识符。 + temp.splice(index, 1) + } else { + // 之前未选中,则现在把加子节点的标识符加到 value 列表中。 + temp.push(value) + } + this.setData({ + value: temp + }) + // 操作完之后更新一下 所有节点的 disabled 状态 + this.resetChildren(temp) + this.$emit('change', { + value: temp + }) + }, + /** + * @description 修正子组件的 isChecked 和 finalDisabled + * @param {array} values + */ + resetChildren (values) { + values = values || this.data.value + this.children && this.children.forEach(child => { + // value 对应的节点直接选中 + const isChecked = values.indexOf(child.data.value) > -1 + renderData(child, { isChecked }) + child.checkDisabled() + }) + } + }, + beforeCreate () { + // 设置防抖,避免修改 props(min, max, disabled) 触发多次 + this.resetChildren = debounce(this.resetChildren, 50) + } +}) \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-checkbox-group/index.json b/src/uni_modules/wot-design-uni/components/wd-checkbox-group/index.json new file mode 100644 index 00000000..32640e0d --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-checkbox-group/index.json @@ -0,0 +1,3 @@ +{ + "component": true +} \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-checkbox-group/index.jxml b/src/uni_modules/wot-design-uni/components/wd-checkbox-group/index.jxml new file mode 100644 index 00000000..d066f0c3 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-checkbox-group/index.jxml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-checkbox-group/index.scss b/src/uni_modules/wot-design-uni/components/wd-checkbox-group/index.scss new file mode 100644 index 00000000..a79c32b3 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-checkbox-group/index.scss @@ -0,0 +1,15 @@ +@import "./../common/abstracts/_mixin.scss"; +@import "./../common/abstracts/variable.scss"; + +@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; + } +} diff --git a/src/uni_modules/wot-design-uni/components/wd-checkbox-group/wd-checkbox-group.vue b/src/uni_modules/wot-design-uni/components/wd-checkbox-group/wd-checkbox-group.vue new file mode 100644 index 00000000..f98b7664 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-checkbox-group/wd-checkbox-group.vue @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-checkbox/index.js b/src/uni_modules/wot-design-uni/components/wd-checkbox/index.js new file mode 100644 index 00000000..a820c9d3 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-checkbox/index.js @@ -0,0 +1,194 @@ +import VueComponent from '../common/component' +import { renderData } from '../common/util' + +VueComponent({ + behaviors: ['jd://form-field'], + externalClasses: [ + 'custom-label-class', + 'custom-shape-class' + ], + relations: { + '../checkboxGroup/index': { + type: 'ancestor', + linked (target) { + this.parent = target + this.checkName(this, this.data.value) + + const { shape, checkedColor, inline, size, cell } = this.parent.data + const data = { + shape, + checkedColor, + inline, + size + } + const keys = Object.keys(data) + const will = {} + keys.forEach(key => { + if ( + data[key] !== null && + data[key] !== undefined && + this.data[key] === null + ) { + will[key] = data[key] + } + }) + renderData(this, Object.assign(will, { + isChecked: this.parent.data.value.indexOf(this.data.value) > -1, + cellBox: cell, + buttonBox: cell && shape === 'button' + })) + // disabled 单独设置 + this.checkDisabled() + }, + unlinked () { + this.parent = null + } + } + }, + props: { + value: { + type: null, + observer (value) { + if ( + value === null || + value === undefined + ) { + throw Error('checkbox\'s value can\'t be null or undefined') + } + if (!this.data.inited) return + // 组合使用走这个逻辑 + if (this.parent) { + this.checkName() + return this.parent.resetChildren() + } + + this.setData({ isChecked: value === this.data.trueValue }) + } + }, + shape: { + type: String, + value: null, + observer (target) { + const type = ['circle', 'square', 'button'] + if (type.indexOf(target) === -1) throw Error(`shape must be one of ${type.toString()}`) + } + }, + checkedColor: { + type: String, + value: null + }, + disabled: { + type: Boolean, + value: null, + observer () { + this.checkDisabled() + } + }, + trueValue: { + type: null, + value: true + }, + falseValue: { + type: null, + value: false + }, + size: { + type: String, + value: null + }, + maxWidth: String + }, + data: { + isChecked: false, + inited: false, + // 相同组件的伪类选择器无效,这里配合类名手动模拟 last-child、first-child + isFirst: false, + isLast: false, + finalDisabled: false, + inline: null, + // 以下内容用于解决父子组件样式隔离的问题 —— START + cellBox: false, + buttonBox: false + // 以下内容用于解决父子组件样式隔离的问题 —— END + }, + methods: { + /** + * @description 检测checkbox绑定的value是否和其它checkbox的value冲突 + * @param {Object} self 自身 + * @param myName 自己的标识符 + */ + checkName (self = this, myName = this.data.value) { + this.parent && this.parent.children.forEach(node => { + if ( + node !== self && + node.data.value === myName + ) { + throw Error(`The checkbox's bound value: ${myName} has been used`) + } + }) + }, + /** + * @description 点击checkbox的Event handle + */ + toggle () { + const { value, finalDisabled, trueValue, falseValue, isChecked } = this.data + if (finalDisabled) return + // 复选框单独使用时点击反选,并且在checkbox上触发change事件 + if (this.parent) { + this.$emit('change', { + value: !isChecked + }) + this.parent.changeSelectState(value) + } else { + const newVal = value === trueValue ? falseValue : trueValue + this.setData({ + value: newVal + }) + this.$emit('change', { + value: newVal + }) + } + }, + /** + * @description 检查设置实际 disabled 情况,需要考虑父组件的 min, max 和 value.length 的关系 + * + */ + checkDisabled () { + // 不管 this.data.disabled 是啥,我先设置上 + const config = {} + config.finalDisabled = this.data.disabled + if (!this.parent) { + return renderData(this, config) + } + // 此处用于修正上一步 + const { min, max, disabled, value } = this.parent.data + if ( + // max 生效时,group 已经选满,禁止其它节点再选中。 + (max && value.length >= max && !this.data.isChecked) || + // min 生效时,group 选中的节点数量仅满足最小值,禁止取消已选中的节点。 + (min && value.length <= min && this.data.isChecked) || + // 只要子节点自己要求 disabled,那就 disabled。 + (this.data.disabled === true) || + // 父节点要求全局 disabled,子节点没吱声,那就 disabled。 + (disabled && this.data.disabled === null) + ) { + config.finalDisabled = true + } + renderData(this, config) + } + }, + created () { + if (this.data.value === null) throw Error('checkbox\'s value must be set') + this.setData({ inited: true }) + }, + mounted () { + // 如果没有父组件,设置 isChecked + if (!this.parent) { + this.setData({ + isChecked: this.data.value === this.data.trueValue, + isFirst: true, + isLast: true + }) + } + } +}) \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-checkbox/index.json b/src/uni_modules/wot-design-uni/components/wd-checkbox/index.json new file mode 100644 index 00000000..f5b2a4f2 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-checkbox/index.json @@ -0,0 +1,6 @@ +{ + "component": true, + "usingComponents": { + "wd-icon": "../icon/index" + } +} \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-checkbox/index.jxml b/src/uni_modules/wot-design-uni/components/wd-checkbox/index.jxml new file mode 100644 index 00000000..69823975 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-checkbox/index.jxml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + diff --git a/src/uni_modules/wot-design-uni/components/wd-checkbox/index.scss b/src/uni_modules/wot-design-uni/components/wd-checkbox/index.scss new file mode 100644 index 00000000..e8c15151 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-checkbox/index.scss @@ -0,0 +1,203 @@ +@import "./../common/abstracts/_mixin.scss"; +@import "./../common/abstracts/variable.scss"; + +@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 e(btn-check) { + display: inline-block; + font-size: $-checkbox-icon-size; + margin-right: 4px; + height: $-checkbox-icon-size; + width: $-checkbox-icon-size; + 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 e(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; + } + .wd-checkbox__check { + opacity: 1; + position: absolute; + left: 0; + top: -1px; + transform: translateX(-4.5%); + } + } + + @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; + } + .wd-checkbox__check { + top: 0; + left: 1px; + } + } +} diff --git a/src/uni_modules/wot-design-uni/components/wd-checkbox/wd-checkbox.vue b/src/uni_modules/wot-design-uni/components/wd-checkbox/wd-checkbox.vue new file mode 100644 index 00000000..f98b7664 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-checkbox/wd-checkbox.vue @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-col-picker/index.js b/src/uni_modules/wot-design-uni/components/wd-col-picker/index.js new file mode 100644 index 00000000..76d366bc --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-col-picker/index.js @@ -0,0 +1,425 @@ +import VueComponent from '../common/component' +import { getType, debounce } from '../common/util' +import cell from '../mixins/cell' + +const $container = '.wd-col-picker__selected-container' +const $item = '.wd-col-picker__selected-item' + +VueComponent({ + externalClasses: [ + 'custom-view-class', + 'custom-label-class', + 'custom-value-class' + ], + behaviors: [cell, 'jd://form-field'], + relations: { + '../cellGroup/index': { + type: 'ancestor', + linked (target) { + this.parent = target + }, + unlinked () { + this.parent = null + } + } + }, + data: { + pickerShow: false, + currentCol: 0, + selectList: [], + pickerColSelected: [], + selectShowList: [], + loading: false, + showValue: '', + isChange: false, + lastSelectList: [], + lastPickerColSelected: [], + lineStyle: {}, + scrollLeft: 0 + }, + props: { + value: { + type: Array, + observer (val) { + if (val === this.data.pickerColSelected) return + + this.setData({ + pickerColSelected: val, + selectShowList: val.map((item, colIndex) => { + return this.getSelectedItem(item, colIndex, this.data.selectList)[this.data.labelKey] + }) + }) + this.setShowValue(val) + + this.handleAutoComplete() + } + }, + columns: { + type: Array, + observer (val, oldVal) { + if (val.length && !(val[0] instanceof Array)) { + console.error('[wot design] error(wd-col-picker): the columns props of wd-col-picker should be a two-dimensional array') + return + } + + if (val.length === 0 && !oldVal) return + + const newSelectedList = val.slice(0) + this.setData({ + selectList: newSelectedList, + selectShowList: this.data.pickerColSelected.map((item, colIndex) => { + return this.getSelectedItem(item, colIndex, newSelectedList)[this.data.labelKey] + }), + lastSelectList: newSelectedList + }) + + if (newSelectedList.length > 0) { + this.setData({ + currentCol: newSelectedList.length - 1 + }) + this.setShowValue(this.data.value) + } + } + }, + label: String, + labelWidth: String, + useLabelSlot: Boolean, + useDefaultSlot: Boolean, + disabled: Boolean, + readonly: Boolean, + placeholder: String, + title: String, + columnChange: { + type: null, + observer (fn) { + if (getType(fn) !== 'function') { + throw Error('The type of columnChange must be Function') + } + } + }, + // 外部展示格式化函数 + displayFormat: { + type: null, + observer (fn) { + if (getType(fn) !== 'function') { + throw Error('The type of displayFormat must be Function') + } + } + }, + beforeConfirm: { + type: null, + observer (fn) { + if (getType(fn) !== 'function') { + throw Error('The type of beforeConfirm must be Function') + } + } + }, + alignRight: Boolean, + error: Boolean, + required: Boolean, + size: String, + valueKey: { + type: String, + value: 'value' + }, + labelKey: { + type: String, + value: 'label' + }, + tipKey: { + type: String, + value: 'tip' + }, + loadingColor: { + type: String, + value: '#4D80F0' + }, + closeOnClickModal: { + type: Boolean, + value: true + }, + autoComplete: Boolean, + zIndex: { + type: Number, + value: 15 + }, + safeAreaInsetBottom: { + type: Boolean, + value: true + }, + ellipsis: Boolean + }, + methods: { + // 对外暴露方法,打开弹框 + open () { + this.showPicker() + }, + // 对外暴露方法,关闭弹框 + close () { + this.handlePickerClose() + }, + handlePickerClose () { + this.setData({ + pickerShow: false + }) + const { isChange, lastSelectList, lastPickerColSelected } = this.data + // 如果目前用户正在选择,需要在popup关闭时将数据重置回上次数据,popup 关闭时间 250 + if (isChange) { + setTimeout(() => { + this.setData({ + selectList: lastSelectList.slice(0), + pickerColSelected: lastPickerColSelected.slice(0), + selectShowList: lastPickerColSelected.map((item, colIndex) => { + return this.getSelectedItem(item, colIndex, lastSelectList)[this.data.labelKey] + }), + currentCol: lastSelectList.length - 1, + isChange: false + }) + }, 250) + } + this.$emit('close') + }, + showPicker () { + const { disabled, readonly } = this.data + + if (disabled || readonly) return + + this.setData({ + pickerShow: true, + lastPickerColSelected: this.data.pickerColSelected.slice(0), + lastSelectList: this.data.selectList.slice(0) + }, () => { + setTimeout(() => { + this.updateLineAndScroll() + }, 30) + }) + }, + getSelectedItem (value, colIndex, selectList) { + const { valueKey, labelKey } = this.data + + if (selectList[colIndex]) { + const selecteds = selectList[colIndex].filter(item => { + return item[valueKey] === value + }) + + if (selecteds.length > 0) { + return selecteds[0] + } + } + + return { + [valueKey]: value, + [labelKey]: '' + } + }, + chooseItem (event) { + const { colIndex, index } = event.currentTarget.dataset + const item = this.data.selectList[colIndex][index] + if (item.disabled) return + + const { pickerColSelected, valueKey, selectList } = this.data + + const newPickerColSelected = pickerColSelected.slice(0, colIndex) + newPickerColSelected.push(item[valueKey]) + this.setData({ + isChange: true, + pickerColSelected: newPickerColSelected, + selectList: selectList.slice(0, colIndex + 1), + selectShowList: newPickerColSelected.map((item, colIndex) => { + return this.getSelectedItem(item, colIndex, selectList)[this.data.labelKey] + }) + }) + this.handleColChange(colIndex, item, index) + }, + handleColChange (colIndex, item, index, callback) { + this.setData({ + loading: true + }) + const { columnChange, beforeConfirm } = this.data + columnChange({ + selectedItem: item, + index: colIndex, + rowIndex: index, + resolve: (nextColumn) => { + if (!(nextColumn instanceof Array)) { + console.error('[wot design] error(wd-col-picker): the data of each column of wd-col-picker should be an array') + return + } + + const newSelectList = this.data.selectList.slice(0) + newSelectList[colIndex + 1] = nextColumn + this.setData({ + selectList: newSelectList, + loading: false, + currentCol: colIndex + 1 + + }, () => { + this.updateLineAndScroll(true) + if (typeof callback === 'function') { + this.isCompleting = false + this.setData({ + selectShowList: this.data.pickerColSelected.map((item, colIndex) => { + return this.getSelectedItem(item, colIndex, this.data.selectList)[this.data.labelKey] + }) + }) + callback() + } + }) + }, + finish: (isOk) => { + // 每设置展示数据回显 + if (typeof callback === 'function') { + this.setData({ + loading: false + }) + this.isCompleting = false + return + } + if ((getType(isOk) === 'boolean' && !isOk)) { + this.setData({ + loading: false + }) + return + } + + if (beforeConfirm) { + beforeConfirm(this.data.pickerColSelected, this.data.pickerColSelected.map((item, colIndex) => { + return this.getSelectedItem(item, colIndex, this.data.selectList) + }), (isPass) => { + if (isPass) { + this.onConfirm() + } else { + this.setData({ + loading: false + }) + } + }) + } else { + this.onConfirm() + } + } + }) + }, + onConfirm () { + this.setData({ + isChange: false, + loading: false, + pickerShow: false, + value: this.data.pickerColSelected + }) + this.setShowValue(this.data.pickerColSelected) + this.$emit('confirm', { + value: this.data.pickerColSelected, + selectedItems: this.data.pickerColSelected.map((item, colIndex) => { + return this.getSelectedItem(item, colIndex, this.data.selectList) + }) + }) + }, + handleColClick ({ target: { dataset: { index } } }) { + this.setData({ + isChange: true, + currentCol: index + }, () => { + this.updateLineAndScroll(true) + }) + }, + /** + * @description 更新navBar underline的偏移量 + * @param {Boolean} animation 是否伴随动画 + */ + setLineStyle (animation = true) { + if (!this.inited) return + const { currentCol } = this.data + this.getRect($item, true).then((rects) => { + const rect = rects[currentCol] + // const width = lineWidth || (slidableNum < items.length ? rect.width : (rect.width - 14)) + const width = 16 + let left = rects.slice(0, currentCol).reduce((prev, curr) => prev + curr.width, 0) + left += (rect.width - width) / 2 + const transition = animation + ? 'transition: width 300ms ease, transform 300ms ease;' + : '' + + const lineStyle = ` + transform: translateX(${left}px); + ${transition} + ` + // 防止重复绘制 + this.data.lineStyle !== lineStyle && this.setData({ lineStyle }) + }) + }, + /** + * @description scroll-view滑动到active的tab_nav + */ + lineScrollIntoView () { + if (!this.inited) return + const { currentCol } = this.data + Promise.all([ + this.getRect($item, true), + this.getRect($container) + ]).then(([navItemsRects, navRect]) => { + if (navItemsRects.length === 0) return + // 选中元素 + const selectItem = navItemsRects[currentCol] + // 选中元素之前的节点的宽度总和 + const offsetLeft = navItemsRects.slice(0, currentCol).reduce((prev, curr) => prev + curr.width, 0) + // scroll-view滑动到selectItem的偏移量 + const scrollLeft = offsetLeft - (navRect.width - selectItem.width) / 2 + this.setData({ scrollLeft }) + }) + }, + setShowValue (value) { + const selectedItems = value.map((item, colIndex) => { + return this.getSelectedItem(item, colIndex, this.data.selectList) + }) + + if (this.data.displayFormat) { + this.setData({ + showValue: this.data.displayFormat(selectedItems) + }) + } else { + this.setData({ + showValue: selectedItems.map(item => { + return item[this.data.labelKey] + }).join('') + }) + } + }, + // 递归列数据补齐 + diffColumns (colIndex) { + // colIndex 为 -1 时,item 为空对象,>=0 时则具有 value 属性 + const item = colIndex === -1 ? {} : { [this.data.valueKey]: this.data.value[colIndex] } + this.handleColChange(colIndex, item, -1, () => { + // 如果 columns 长度还小于 value 长度,colIndex + 1,继续递归补齐 + if (this.data.selectList.length < this.data.value.length) { + this.diffColumns(colIndex + 1) + } else { + this.setShowValue(this.data.pickerColSelected) + } + }) + }, + handleAutoComplete () { + if (this.data.autoComplete) { + // 如果 columns 数组长度为空,或者长度小于 value 的长度,自动触发 columnChange 来补齐数据 + if (this.data.selectList.length < this.data.value.length || this.data.selectList.length === 0) { + // isCompleting 是否在自动补全,锁操作 + if (!this.isCompleting) { + // 如果 columns 长度为空,则传入的 colIndex 为 -1 + const colIndex = this.data.selectList.length === 0 ? -1 : (this.data.selectList.length - 1) + this.diffColumns(colIndex) + } + this.isCompleting = true + } + } + } + }, + beforeCreate () { + this.updateLineAndScroll = debounce(function (animation = true) { + this.setLineStyle(animation) + this.lineScrollIntoView() + }, 50) + }, + mounted () { + this.inited = true + } +}) \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-col-picker/index.json b/src/uni_modules/wot-design-uni/components/wd-col-picker/index.json new file mode 100644 index 00000000..7ffac984 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-col-picker/index.json @@ -0,0 +1,8 @@ +{ + "component": true, + "usingComponents": { + "wd-action-sheet": "../actionSheet/index", + "wd-icon": "../icon/index", + "wd-loading": "../loading/index" + } +} \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-col-picker/index.jxml b/src/uni_modules/wot-design-uni/components/wd-col-picker/index.jxml new file mode 100644 index 00000000..cad6664a --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-col-picker/index.jxml @@ -0,0 +1,78 @@ + + + + + + {{ label }} + + + {{ showValue || placeholder || '请选择' }} + + + + + + + + {{ selectShowList[colIndex] || '请选择' }} + + + + + + + + + {{ item[labelKey] }} + {{ item[tipKey] }} + + + + + + + + + + \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-col-picker/index.scss b/src/uni_modules/wot-design-uni/components/wd-col-picker/index.scss new file mode 100644 index 00000000..317111e2 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-col-picker/index.scss @@ -0,0 +1,176 @@ +@import '../common/abstracts/variable'; +@import '../common/abstracts/mixin'; +@import '../actionSheet/index.scss'; + +@include b(col-picker) { + @include when(border) { + .wd-col-picker__cell { + @include halfPixelBorder('top', $-cell-padding); + } + } + @include e(cell) { + position: relative; + display: flex; + padding: 0 $-cell-padding; + align-items: flex-start; + background-color: $-color-white; + text-decoration: none; + color: $-cell-title-color; + font-size: $-cell-title-fs; + overflow: hidden; + line-height: $-cell-ling-height; + } + @include e(cell) { + @include when(disabled) { + .wd-col-picker__value { + color: $-input-disabled-color; + } + } + @include when(align-right) { + .wd-col-picker__value { + text-align: right; + } + } + @include when(error) { + .wd-col-picker__value { + color: $-input-error-color; + } + .wd-col-picker__arrow { + color: $-input-error-color; + } + } + @include when(large) { + font-size: $-cell-title-fs-large; + + .wd-col-picker__arrow { + font-size: $-cell-icon-size-large; + } + } + } + @include e(label) { + position: relative; + width: $-input-cell-label-width; + padding: $-cell-wrapper-padding 0; + margin-right: $-cell-padding; + box-sizing: border-box; + + @include when(required) { + padding-left: 12px; + + &::after { + position: absolute; + left: 0; + top: $-cell-wrapper-padding + 2px; + content: '*'; + font-size: $-cell-required-size; + line-height: 1.1; + color: $-cell-required-color; + } + } + } + @include e(value) { + flex: 1; + padding: $-cell-wrapper-padding 0; + margin-right: 10px; + color: $-cell-value-color; + + @include when(ellipsis) { + @include lineEllipsis; + } + @include m(placeholder) { + color: $-input-placeholder-color; + } + } + @include e(arrow) { + display: block; + margin-top: $-cell-wrapper-padding; + font-size: $-cell-icon-size; + color: $-cell-arrow-color; + line-height: 1.25; + } + @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: $-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; + + .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 e(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; + } +} \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-col-picker/wd-col-picker.vue b/src/uni_modules/wot-design-uni/components/wd-col-picker/wd-col-picker.vue new file mode 100644 index 00000000..f98b7664 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-col-picker/wd-col-picker.vue @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-col/index.js b/src/uni_modules/wot-design-uni/components/wd-col/index.js new file mode 100644 index 00000000..3ef40d61 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-col/index.js @@ -0,0 +1,45 @@ +import VueComponent from '../common/component' +VueComponent({ + data: { + style: '' + }, + props: { + span: { + type: Number, + value: 24, + observer: 'check' + }, + offset: { + type: Number, + value: 0, + observer: 'check' + } + }, + relations: { + '../row/index': { + type: 'ancestor', + linked(target) { + this.parent = target + }, + unlinked() { + this.parent = null + } + } + }, + methods: { + check() { + const { span, offset } = this.data + if (span < 0 || offset < 0) { + console.warn('[wot-design] warning(wd-col): attribute span/offset must be greater than or equal to 0') + } + }, + setGutter(gutter) { + const padding = `${gutter / 2}px` + const style = gutter > 0 ? `padding-left: ${padding}; padding-right: ${padding};background-clip: content-box;` : '' + + if (style !== this.data.style) { + this.setData({ style }) + } + } + } +}) diff --git a/src/uni_modules/wot-design-uni/components/wd-col/index.json b/src/uni_modules/wot-design-uni/components/wd-col/index.json new file mode 100644 index 00000000..32640e0d --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-col/index.json @@ -0,0 +1,3 @@ +{ + "component": true +} \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-col/index.jxml b/src/uni_modules/wot-design-uni/components/wd-col/index.jxml new file mode 100644 index 00000000..1a60667f --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-col/index.jxml @@ -0,0 +1,4 @@ + + + + diff --git a/src/uni_modules/wot-design-uni/components/wd-col/index.scss b/src/uni_modules/wot-design-uni/components/wd-col/index.scss new file mode 100644 index 00000000..42cb9d2e --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-col/index.scss @@ -0,0 +1,15 @@ +@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: 100% / 24 * $i; } + .wd-col__offset-#{$i} { margin-left: 100% / 24 * $i; } + $i : $i + 1; +} \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-col/wd-col.vue b/src/uni_modules/wot-design-uni/components/wd-col/wd-col.vue new file mode 100644 index 00000000..fadc2d7b --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-col/wd-col.vue @@ -0,0 +1,74 @@ + + + + + diff --git a/src/uni_modules/wot-design-uni/components/wd-collapse-item/index.js b/src/uni_modules/wot-design-uni/components/wd-collapse-item/index.js new file mode 100644 index 00000000..02badb39 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-collapse-item/index.js @@ -0,0 +1,123 @@ +import VueComponent from '../common/component' + +const $body = '.wd-collapse-item__body' +VueComponent({ + externalClasses: ['custom-class'], + data: { + height: '', + show: false, + firstItem: false, + isExpand: false, + transD: '0.3s' + }, + props: { + title: String, + disabled: Boolean, + name: { + type: String, + observer (newVal) { + const condition = this.parent && this.parent.checkRepeat(this.parent.children, newVal, 'name') + // 比较数组中是否存在重复数据 + if (condition > -1) { + throw Error('Name attribute cannot be defined repeatedly') + } + } + } + }, + relations: { + '../collapse/index': { + type: 'parent', + linked (target) { + this.parent = target + }, + unlinked () { + this.parent = null + } + } + }, + mounted () { + this.initState() + }, + methods: { + initState () { + const { isExpand, name } = this.data + const { accordion, value } = this.parent.data + if (!value) { + console.warn('[wot-design] warning(wd-collapse-item): there is no value with parent.') + return + } + this.setData({ + show: isExpand, + isExpand: accordion ? value === name : value.indexOf(name) > -1 + }) + this.scrollHeight($body, true) + }, + /** + * 关联组件控制内部值 + * @param {String} key 键值 + * @param String value 键名 + */ + stateControl (key, value) { + this.setData({ [key]: value }) + }, + /** + * 控制折叠面板滚动 + * @param {String} select 选择器名称 + * @param {Boolean} firstRender 是否首次渲染 + */ + scrollHeight (select, firstRender = false) { + const transD = firstRender ? '0s' : '0.3s' + + this.getRect(select).then(rect => { + if (!rect) return + const { height } = rect + if (this.data.isExpand) { + if (height === 0) { + this.setData({ + height: 'auto', + show: true, + transD + }) + return + } + + this.setData({ height: 0, show: true, transD }) + setTimeout(() => { + this.setData({ height: height + 'px' }) + }, 30) + } else { + this.setData({ height: height + 'px', transD }) + setTimeout(() => { + this.setData({ height: 0 }) + }, 30) + } + }) + }, + // 点击触发 + toggle () { + const { disabled, name, isExpand } = this.data + const { accordion } = this.parent.data + if (disabled) return + // 如果是手风琴模式, 那么只展开一个,其余全部折叠 + if (accordion) { + this.parent.children.forEach(item => { + item.stateControl('isExpand', item.data.name === name) + item.scrollHeight($body) + }) + } else { + this.setData({ isExpand: !isExpand }) + this.scrollHeight($body) + } + // 调用父组件方法 switchValue 当前选中的是什么,判断当前是否处于选中状态 + this.parent.switchValue(name, !isExpand) + }, + // 动画结束时触发 + onTransitionend (event) { + if (!this.data.isExpand) { + this.setData({ show: false }) + } else { + this.setData({ height: '' }) + } + } + } +}) \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-collapse-item/index.json b/src/uni_modules/wot-design-uni/components/wd-collapse-item/index.json new file mode 100644 index 00000000..f5b2a4f2 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-collapse-item/index.json @@ -0,0 +1,6 @@ +{ + "component": true, + "usingComponents": { + "wd-icon": "../icon/index" + } +} \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-collapse-item/index.jxml b/src/uni_modules/wot-design-uni/components/wd-collapse-item/index.jxml new file mode 100644 index 00000000..dc7331b6 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-collapse-item/index.jxml @@ -0,0 +1,21 @@ + + + {{ title }} + + + + + + + + diff --git a/src/uni_modules/wot-design-uni/components/wd-collapse-item/index.scss b/src/uni_modules/wot-design-uni/components/wd-collapse-item/index.scss new file mode 100644 index 00000000..0542bafe --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-collapse-item/index.scss @@ -0,0 +1,54 @@ +@import '../common/abstracts/variable'; +@import '../common/abstracts/mixin'; + +@include b(collapse-item) { + @include e(header) { + position: relative; + display: flex; + justify-content: space-between; + align-items: center; + padding: $-collapse-header-padding; + overflow: hidden; + user-select: none; + } + + @include e(title) { + color: $-collapse-title-color; + font-weight: $-fw-medium; + font-size: $-collapse-title-fs; + } + + @include e(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; + transition: height 0.3s ease-in-out; + 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; + } + } +} diff --git a/src/uni_modules/wot-design-uni/components/wd-collapse-item/wd-collapse-item.vue b/src/uni_modules/wot-design-uni/components/wd-collapse-item/wd-collapse-item.vue new file mode 100644 index 00000000..f98b7664 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-collapse-item/wd-collapse-item.vue @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-collapse/index.js b/src/uni_modules/wot-design-uni/components/wd-collapse/index.js new file mode 100644 index 00000000..ae4a1700 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-collapse/index.js @@ -0,0 +1,116 @@ +import VueComponent from '../common/component' +VueComponent({ + externalClasses: ['custom-more-slot-class'], + data: { + contentLineNum: '' + }, + props: { + // [String, Array, Boolean] + value: { + type: null, + observer (newVal, oldVal) { + const { viewmore, accordion } = this.data + // 类型校验,支持所有值(除null、undefined。undefined建议统一写成void (0)防止全局undefined被覆盖) + if (newVal === null || newVal === undefined) { + throw Error('value can\'t be null or undefined') + } + // 手风琴状态下 value 类型只能为 string + if (accordion && typeof newVal !== 'string') { + throw Error('accordion value must be string') + } else if (!accordion && !viewmore && this.checkType(newVal) !== 'Array') { + throw Error('value must be Array') + } + // 初始状态不执行动画 + // 外部修改 value 滚动 + if (oldVal && !viewmore && this.children) { + this.children.forEach((item) => { + const { name, isExpand } = item.data + const condition = newVal === name || newVal.indexOf(name) > -1 + if (isExpand === condition) return + item.stateControl('isExpand', condition) + item.scrollHeight('.wd-collapse-item__body') + }) + } + } + }, + accordion: { + type: Boolean, + value: false + }, + viewmore: { + type: Boolean, + value: false + }, + useMoreSlot: { + type: Boolean, + value: false + }, + lineNum: { + type: Number, + value: 2, + observer (newVal) { + if (newVal <= 0) { + this.setData({ lineNum: 2 }) + throw Error('lineNum must greater than 0') + } + } + } + }, + relations: { + '../collapseItem/index': { + type: 'child', + linked (target) { + this.children = this.children || [] + const repeat = this.checkRepeat(this.children, target.data.name, 'name') + if (repeat === -1) { + this.children.push(target) + this.children[0].stateControl('firstItem', true) + } else { + throw Error('Name attribute cannot be defined repeatedly') + } + }, + unlinked (target) { + this.children = this.children.filter(child => child !== target) + } + } + }, + created () { + const { lineNum, viewmore, value } = this.data + this.setData({ contentLineNum: viewmore && !value ? lineNum : '' }) + }, + methods: { + checkType (value) { + return Object.prototype.toString.call(value).slice(8, -1) + }, + /** + * 检查是否存在重复属性 + * @param {Array} currentList + * @param {String} checkValue 比较的重复值 + * @param {String} key 键名 + */ + checkRepeat (currentList, checkValue, key) { + return currentList.findIndex(item => item.data[key] === checkValue) + }, + /** + * 折叠控制 + * @param {String} name 当前选中的 item name + * @param {Boolean} expanded 是否展开 false: 开->关(删除);true: 关->开(添加) + */ + switchValue (name, expanded) { + const { accordion, viewmore, value } = this.data + if (!accordion && !viewmore && this.checkType(value) === 'Array') { + name = expanded + ? value.concat(name) + : value.filter(item => item !== name) + } else if (viewmore) { + name = !value + } + this.$emit('input', { + value: name + }) + this.$emit('change', { + value: name + }) + } + } +}) \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-collapse/index.json b/src/uni_modules/wot-design-uni/components/wd-collapse/index.json new file mode 100644 index 00000000..f5b2a4f2 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-collapse/index.json @@ -0,0 +1,6 @@ +{ + "component": true, + "usingComponents": { + "wd-icon": "../icon/index" + } +} \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-collapse/index.jxml b/src/uni_modules/wot-design-uni/components/wd-collapse/index.jxml new file mode 100644 index 00000000..a3404c6b --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-collapse/index.jxml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + {{ !value ? '展开' : '折叠' }} + + + + + + + diff --git a/src/uni_modules/wot-design-uni/components/wd-collapse/index.scss b/src/uni_modules/wot-design-uni/components/wd-collapse/index.scss new file mode 100644 index 00000000..71e1fecf --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-collapse/index.scss @@ -0,0 +1,45 @@ +@import "../common/abstracts/variable"; +@import "../common/abstracts/mixin"; + +@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); + } + } +} diff --git a/src/uni_modules/wot-design-uni/components/wd-collapse/wd-collapse.vue b/src/uni_modules/wot-design-uni/components/wd-collapse/wd-collapse.vue new file mode 100644 index 00000000..f98b7664 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-collapse/wd-collapse.vue @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-curtain/index.js b/src/uni_modules/wot-design-uni/components/wd-curtain/index.js new file mode 100644 index 00000000..391af8bc --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-curtain/index.js @@ -0,0 +1,102 @@ +import VueComponent from '../common/component' + +VueComponent({ + props: { + value: { + type: Boolean, + value: false, + observer: 'computedShowImg' + }, + closePosition: String, + src: String, + to: String, + width: { + type: String, + observer: 'computeImgStyle' + }, + closeOnClickModal: Boolean, + hideWhenClose: { + type: Boolean, + value: true + } + }, + data: { + show: false, + imgSucc: true, + imgStyle: '', + imgScale: '1' + }, + methods: { + computedShowImg () { + if (this.data.value && this.data.imgSucc) { + this.setData({ show: true }) + } else { + this.setData({ show: false }) + this.close() + } + }, + computeImgStyle () { + let style = '' + if (this.data.width) { + style += `width: ${this.data.width}px ;` + style += `height: ${this.data.width / this.data.imgScale}px` + } + this.setData({ imgStyle: style }) + }, + beforeenter () { + this.$emit('beforeenter') + }, + enter () { + this.$emit('enter') + }, + afterenter () { + this.$emit('afterenter') + }, + beforeleave () { + this.$emit('beforeleave') + }, + leave () { + this.$emit('leave') + }, + afterleave () { + this.$emit('afterleave') + }, + close () { + this.setData({ + show: false + }) + this.$emit('close') + }, + closed () { + this.$emit('closed') + }, + clickModal () { + this.$emit('clickmodal') + }, + imgLoad (event) { + const { + height, + width + } = event.detail + this.setData({ + imgScale: width / height, + imgSucc: true + }) + this.computeImgStyle() + this.$emit('load') + }, + imgErr () { + this.setData({ imgSucc: false }) + this.$emit('error') + }, + clickImage () { + if (this.data.to) { + jd.navigateTo({ + url: this.data.to + }) + } + this.$emit('click') + this.close() + } + } +}) \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-curtain/index.json b/src/uni_modules/wot-design-uni/components/wd-curtain/index.json new file mode 100644 index 00000000..5cd88a4e --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-curtain/index.json @@ -0,0 +1,7 @@ +{ + "component": true, + "usingComponents": { + "wd-popup": "../popup/index", + "wd-icon": "../icon/index" + } +} \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-curtain/index.jxml b/src/uni_modules/wot-design-uni/components/wd-curtain/index.jxml new file mode 100644 index 00000000..65b497da --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-curtain/index.jxml @@ -0,0 +1,28 @@ + + + + + + + \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-curtain/index.scss b/src/uni_modules/wot-design-uni/components/wd-curtain/index.scss new file mode 100644 index 00000000..f4aa68c3 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-curtain/index.scss @@ -0,0 +1,79 @@ +@import "./../common/abstracts/_mixin.scss"; +@import "./../common/abstracts/variable.scss"; +@import "../modal/index.scss"; +@import "../popup/index.scss"; + +@include b(curtain) { + display: inline-block; + border-radius: $-curtain-content-radius; + overflow-y: visible; + background: transparent; + font-size: 0; + @include e(content) { + position: relative; + display: inline-block; + background: transparent; + border-radius: $-curtain-content-radius; + } + @include e(content-link) { + display: block; + border-radius: $-curtain-content-radius; + } + @include e(content-img) { + display: block; + width: auto; + height: auto; + border-radius: $-curtain-content-radius; + } + @include e(content-close) { + position: absolute; + margin: 0; + padding: 6px; + top: 10px; + right: 10px; + color: $-curtain-content-close-color; + -webkit-tap-highlight-color: transparent; + &.top { + margin: 0 0 0 -18px; + top: -62px; + right: unset; + left: 50%; + bottom: unset; + } + &.top-left { + margin: 0; + top: -62px; + right: unset; + left: -6px; + bottom: unset; + } + &.top-right { + margin: 0; + top: -62px; + right: -6px; + left: unset; + bottom: unset; + } + &.bottom { + margin: 0 0 0 -18px; + top: unset; + right: unset; + left: 50%; + bottom: -62px; + } + &.bottom-left { + margin: 0; + top: unset; + right: unset; + left: -6px; + bottom: -62px; + } + &.bottom-right { + margin: 0; + top: unset; + right: -6px; + left: unset; + bottom: -62px; + } + } +} diff --git a/src/uni_modules/wot-design-uni/components/wd-curtain/wd-curtain.vue b/src/uni_modules/wot-design-uni/components/wd-curtain/wd-curtain.vue new file mode 100644 index 00000000..f98b7664 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-curtain/wd-curtain.vue @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-datetime-picker-view/index.js b/src/uni_modules/wot-design-uni/components/wd-datetime-picker-view/index.js new file mode 100644 index 00000000..738edf39 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-datetime-picker-view/index.js @@ -0,0 +1,531 @@ +import VueComponent from '../common/component' +import { debounce, getType, isDef, range, padZero } from '../common/util' +import pickerViewProps from '../pickerView/props' + +// 本地时间戳 +const currentYear = new Date().getFullYear() +/** @description 判断时间戳是否合法 */ +const isValidDate = date => isDef(date) && !Number.isNaN(date) +/** + * @description 生成n个元素,并使用iterator接口进行填充 + * @param n + * @param iteratee + * @return {any[]} + */ +const times = (n, iteratee) => { + let index = -1 + const result = Array(n < 0 ? 0 : n) + while (++index < n) { + result[index] = iteratee(index) + } + return result +} +/** + * @description 获取某年某月有多少天 + * @param {Number} year + * @param {Number} month + * @return {Number} day + */ +const getMonthEndDay = (year, month) => { + return 32 - new Date(year, month - 1, 32).getDate() +} + +VueComponent({ + /** + * 注意,datetimePickerView和datetimePicker有公共逻辑,抽离成/mixins/datetimePickerView,通过mixins options注入 + */ + behaviors: ['jd://form-field'], + + props: { + value: { + type: null, + observer (val, oldVal) { + if (val === oldVal) return + // 外部传入值更改时 更新picker数据 + const value = this.correctValue(val) + this.updateColumnValue(value) + } + }, + ...pickerViewProps, + // 时间选择器的类型 + type: { + type: String, + value: 'datetime', + observer (target) { + const type = ['date', 'year-month', 'time', 'datetime'] + if (type.indexOf(target) === -1) { + throw Error(`type must be one of ${type}`) + } + // 每次type更新时都需要刷新整个列表 + this.updateValue() + } + }, + // 自定义过滤选项的函数,返回列的选项数组 + filter: { + type: null, + observer (fn) { + if (getType(fn) !== 'function') { + throw Error('The type of filter must be Function') + } + this.updateValue() + } + }, + // 自定义弹出层选项文案的格式化函数,返回一个字符串 + formatter: { + type: null, + observer (fn) { + if (getType(fn) !== 'function') { + throw Error('The type of formatter must be Function') + } + this.updateValue() + } + }, + // 自定义列筛选条件 + columnFormatter: { + type: null, + observer (fn) { + if (getType(fn) !== 'function') { + throw Error('The type of columnFormatter must be Function') + } + this.updateValue() + } + }, + // 最小日期 20(x-10)年1月1日 + minDate: { + type: Number, + value: new Date(currentYear - 10, 0, 1).getTime(), + observer: 'updateValue' + }, + // 最大日期 20(x+10)年1月1日 + maxDate: { + type: Number, + value: new Date(currentYear + 10, 11, 31).getTime(), + observer: 'updateValue' + }, + // 最小小时 + minHour: { + type: Number, + value: 0, + observer: 'updateValue' + }, + // 最大小时 + maxHour: { + type: Number, + value: 23, + observer: 'updateValue' + }, + // 最小分钟 + minMinute: { + type: Number, + value: 0, + observer: 'updateValue' + }, + // 最大分钟 + maxMinute: { + type: Number, + value: 59, + observer: 'updateValue' + }, + startSymbol: Boolean + }, + + data: { + // pickerView的id选择器 + pickerId: 'wd-picker-view', + // 内部保持时间戳的 + innerValue: null, + // 传递给pickerView的columns的数据 + columns: [], + // 传递给pickerView的value的数据 + pickerValue: null, + // 自定义组件是否已经调用created hook + created: false + }, + + methods: { + /** pickerView触发change事件,同步修改pickerValue */ + onChange ({ detail }) { + // 更新pickerView的value + this.setData({ + pickerValue: detail.value + }) + // pickerValue => innerValue + // 这个地方的value返回的是picker数组,实际上在此处我们应该返回 change 的是 value date类型的值 + this.$emit('change', { + value: this.updateInnerValue() + }) + }, + /** + * @description updateValue 防抖函数的占位符 + */ + updateValue () { + }, + + /** + * @description 使用formatter格式化getOriginColumns的结果 + * @return {Array>} 用于传入picker的columns + */ + updateColumns () { + const { formatter, columnFormatter } = this.data + + if (columnFormatter) { + return columnFormatter(this) + } else { + return this.getOriginColumns().map(column => { + return column.values.map(value => { + return { + label: formatter ? formatter(column.type, padZero(value)) : padZero(value), + value + } + }) + }) + } + }, + + /** + * @description 根据getRanges得到的范围计算所有的列的数据 + * @return {{values: any[], type: String}[]} 年 + */ + getOriginColumns () { + const { filter } = this.data + return this.getRanges().map(({ type, range }) => { + let values = times(range[1] - range[0] + 1, index => { + return range[0] + index + }) + + if (filter) { + values = filter(type, values) + } + + return { + type, + values + } + }) + }, + + /** + * @description 根据时间戳生成年月日时分的边界范围 + * @return {Array<{type:String,range:Array}>} + */ + getRanges () { + const { data } = this + if (data.type === 'time') { + return [ + { + type: 'hour', + range: [data.minHour, data.maxHour] + }, + { + type: 'minute', + range: [data.minMinute, data.maxMinute] + } + ] + } + + const { + maxYear, + maxDate, + maxMonth, + maxHour, + maxMinute + } = this.getBoundary('max', data.innerValue) + const { + minYear, + minDate, + minMonth, + minHour, + minMinute + } = this.getBoundary('min', data.innerValue) + + const result = [ + { + type: 'year', + range: [minYear, maxYear] + }, + { + type: 'month', + range: [minMonth, maxMonth] + }, + { + type: 'date', + range: [minDate, maxDate] + }, + { + type: 'hour', + range: [minHour, maxHour] + }, + { + type: 'minute', + range: [minMinute, maxMinute] + } + ] + + if (data.type === 'date') result.splice(3, 2) + if (data.type === 'year-month') result.splice(2, 3) + return result + }, + + /** + * @description 修正时间入参,判定是否为规范时间类型 + * @param {String | Number} value + * @return {String | Number} innerValue + */ + correctValue (value) { + const { data } = this + const isDateType = data.type !== 'time' + if (isDateType && !isValidDate(value)) { + // 是Date类型,但入参不可用,使用最小时间戳代替 + value = data.minDate + } else if (!isDateType && !value) { + // 非Date类型,无入参,使用最小小时代替 + const { minHour } = data + value = `${padZero(minHour)}:00` + } + + // 当type为time时 + if (!isDateType) { + // 非Date类型,直接走此逻辑 + let [hour, minute] = value.split(':') + hour = padZero(range(hour, data.minHour, data.maxHour)) + minute = padZero(range(minute, data.minMinute, data.maxMinute)) + return `${hour}:${minute}` + } + + // date type + value = Math.min(Math.max(value, data.minDate), data.maxDate) + + return value + }, + + /** + * @description 根据时间戳,计算所有选项的范围 + * @param {'min'|'max'} type 类型 + * @param {Number} innerValue 时间戳 + */ + getBoundary (type, innerValue) { + const value = new Date(innerValue) + const boundary = new Date(this.data[`${type}Date`]) + const year = boundary.getFullYear() + let month = 1 + let date = 1 + let hour = 0 + let minute = 0 + + if (type === 'max') { + month = 12 + date = getMonthEndDay(value.getFullYear(), value.getMonth() + 1) + hour = 23 + minute = 59 + } + + if (value.getFullYear() === year) { + month = boundary.getMonth() + 1 + if (value.getMonth() + 1 === month) { + date = boundary.getDate() + if (value.getDate() === date) { + hour = boundary.getHours() + if (value.getHours() === hour) { + minute = boundary.getMinutes() + } + } + } + } + return { + [`${type}Year`]: year, + [`${type}Month`]: month, + [`${type}Date`]: date, + [`${type}Hour`]: hour, + [`${type}Minute`]: minute + } + }, + + /** + * @description 根据传入的值和类型,获取当前的选项数组,便于传入 pickerView + * @param value + * @param type picker类型 + * @return {Array} pickerValue + */ + getPickerValue (value, type) { + const values = [] + const date = new Date(value) + if (type === 'time') { + const pair = value.split(':') + values.push(parseInt(pair[0]), parseInt(pair[1])) + } else { + values.push(date.getFullYear(), date.getMonth() + 1) + if (type === 'date') { + values.push(date.getDate()) + } else if (type === 'datetime') { + values.push(date.getDate(), date.getHours(), date.getMinutes()) + } + } + return values + }, + + /** + * @description 根据传入的value以及type,初始化innerValue,期间会使用format格式化数据 + * @param value + * @return {Array} + */ + updateColumnValue (value) { + const values = this.getPickerValue(value, this.data.type) + // 更新pickerView的value,columns + if (this.data.value !== value) { + this.$emit('change', { + value + }) + } + this.setData({ + innerValue: value, + columns: this.updateColumns(), + pickerValue: values + }) + }, + + /** + * @description 根据当前的选中项 处理innerValue + * @return {date} innerValue + */ + updateInnerValue () { + const { type } = this.data + let values = '' + let innerValue = '' + values = this.picker.getValues() + if (type === 'time') { + innerValue = `${padZero(values[0])}:${padZero(values[1])}` + return innerValue + } + + // 处理年份 索引位0 + const year = values[0] && parseInt(values[0]) + + // 处理月 索引位1 + const month = values[1] && parseInt(values[1]) + + const maxDate = getMonthEndDay(year, month) + + // 处理 date 日期 索引位2 + let date = 1 + if (type !== 'year-month') { + date = (values[2] && parseInt(values[2])) > maxDate ? maxDate : (values[2] && parseInt(values[2])) + } + + // 处理 时分 索引位3,4 + let hour = 0 + let minute = 0 + + if (type === 'datetime') { + hour = values[3] && parseInt(values[3]) + minute = values[4] && parseInt(values[4]) + } + const value = new Date(year, month - 1, date, hour, minute) + + innerValue = this.correctValue(value) + return innerValue + }, + + /** + * @description 选中项改变,多级联动 + */ + columnChange (picker) { + const { type } = this.data + // time 和 year-mouth 无需联动 + if ( + type === 'time' || + type === 'year-month' + ) { + return + } + /** 重新计算年月日时分秒,修正时间。 */ + const values = picker.getValues() + const year = values[0] + const month = values[1] + const maxDate = getMonthEndDay(year, month) + let date = values[2] + if (type === 'year-month') { + date = 1 + } + date = date > maxDate ? maxDate : date + let hour = 0 + let minute = 0 + if (type === 'datetime') { + hour = values[3] + minute = values[4] + } + const value = new Date(year, month - 1, date, hour, minute) + /** 根据计算选中项的时间戳,重新计算所有的选项列表 */ + // 更新选中时间戳 + const innerValue = this.correctValue(value) + // 根据innerValue获取最新的时间表,重新生成对应的数据源 + this.setData({ innerValue }) + const newColumns = this.updateColumns().slice(0, 3) + // 深拷贝联动之前的选中项 + const selectedIndex = picker.data.selectedIndex.slice(0) + /** + * 选中年会修改对应的年份的月数,和月份对应的日期。 + * 选中月,会修改月份对应的日数 + */ + + newColumns.forEach((columns, index) => { + const nextColumnIndex = index + 1 + const nextColumnData = newColumns[nextColumnIndex] + // `日`不控制任何其它列 + if (index === 2) return + picker.setColumnData( + nextColumnIndex, + nextColumnData, + (selectedIndex[nextColumnIndex] <= nextColumnData.length - 1) + ? selectedIndex[nextColumnIndex] + : 0 + ) + }) + }, + onPickStart () { + this.$emit('pickstart') + }, + onPickEnd () { + this.$emit('pickend') + } + }, + + beforeCreate () { + /** + * @description observer触发选项重计算,防抖50秒 + * 防抖函数必须要在实例初始化的时候手动挂载到this上 + */ + this.updateValue = debounce(function () { + // 只有等created hook初始化数据之后,observer才能执行此操作 + if (!this.data.created) return + const { data } = this + const val = this.correctValue(this.data.value) + const isEqual = val === data.innerValue + if (!isEqual) { + this.updateColumnValue(val) + } else { + this.setData({ columns: this.updateColumns() }) + } + }, 50) + // pickerView挂载到全局 + this.picker = this.selectComponent(`#${this.data.pickerId}`) + }, + + created () { + // 小程序基础库v1.9.91无法初始化时兼容JM客户端props传入function + const { filter, formatter, columnFormatter } = this.data + this.setData({ + filter: filter || null, + columnFormatter: columnFormatter || null, + formatter: formatter || null + }) + // 多级联动挂载到pickerView + this.picker.setData({ + columnChange: this.columnChange.bind(this) + }) + // 初始化完毕,打开observer触发render的开关 + this.setData({ created: true }) + // 手动进行一次render + const innerValue = this.correctValue(this.data.value) + this.updateColumnValue(innerValue) + } +}) \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-datetime-picker-view/index.json b/src/uni_modules/wot-design-uni/components/wd-datetime-picker-view/index.json new file mode 100644 index 00000000..620b3bfe --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-datetime-picker-view/index.json @@ -0,0 +1,6 @@ +{ + "component": true, + "usingComponents": { + "wd-picker-view": "../pickerView/index" + } +} \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-datetime-picker-view/index.jxml b/src/uni_modules/wot-design-uni/components/wd-datetime-picker-view/index.jxml new file mode 100644 index 00000000..ae3fe440 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-datetime-picker-view/index.jxml @@ -0,0 +1,14 @@ + diff --git a/src/uni_modules/wot-design-uni/components/wd-datetime-picker-view/index.scss b/src/uni_modules/wot-design-uni/components/wd-datetime-picker-view/index.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/uni_modules/wot-design-uni/components/wd-datetime-picker-view/wd-datetime-picker-view.vue b/src/uni_modules/wot-design-uni/components/wd-datetime-picker-view/wd-datetime-picker-view.vue new file mode 100644 index 00000000..f98b7664 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-datetime-picker-view/wd-datetime-picker-view.vue @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-datetime-picker/index.js b/src/uni_modules/wot-design-uni/components/wd-datetime-picker/index.js new file mode 100644 index 00000000..82472052 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-datetime-picker/index.js @@ -0,0 +1,643 @@ +import VueComponent from '../common/component' +import pickerProps from '../picker/props' +import pickerViewProps from '../pickerView/props' +import cell from '../mixins/cell' +import { getType, padZero, isEqual } from '../common/util' +const currentYear = new Date().getFullYear() + +VueComponent({ + /** + * 注意,datetimePickerView和datetimePicker有公共逻辑,抽离成/mixins/datetimePickerView,通过mixins options注入 + */ + externalClasses: [ + 'custom-view-class', + 'custom-label-class', + 'custom-value-class' + ], + + behaviors: [cell, 'jd://form-field'], + + relations: { + '../cellGroup/index': { + type: 'ancestor', + linked (target) { + this.parent = target + }, + unlinked () { + this.parent = null + } + } + }, + + props: { + ...pickerProps, + ...pickerViewProps, + // 选中项,当 type 为 time 时,类型为字符串,否则为 时间戳 + value: { + type: null, + observer (val, oldVal) { + if (isEqual(val, oldVal)) return + + if (getType(val) === 'array') { + this.setData({ + region: true, + innerValue: this.getDefaultInnerValue({ + isRegion: true + }), + endInnerValue: this.getDefaultInnerValue({ + isRegion: true, + isEnd: true + }) + }) + } else { + // 每次value更新时都需要刷新整个列表 + this.setData({ + innerValue: this.getDefaultInnerValue() + }) + } + this.setShowValue() + } + }, + // 时间选择器的类型 + type: { + type: String, + value: 'datetime' + }, + // 最小日期 20(x-10)年1月1日 + minDate: { + type: Number, + value: new Date(currentYear - 10, 0, 1).getTime() + }, + // 最大日期 20(x+10)年1月1日 + maxDate: { + type: Number, + value: new Date(currentYear + 10, 11, 31).getTime() + }, + // 最小小时 + minHour: { + type: Number, + value: 0 + }, + // 最大小时 + maxHour: { + type: Number, + value: 23 + }, + // 最小分钟 + minMinute: { + type: Number, + value: 0 + }, + // 最大分钟 + maxMinute: { + type: Number, + value: 59 + }, + // 自定义过滤选项的函数,返回列的选项数组 + filter: { + type: null, + observer (fn) { + // 每次变化需要重置picker的filter + this.updateFn('filter', fn) + } + }, + // 自定义弹出层选项文案的格式化函数,返回一个字符串 + formatter: { + type: null, + observer (fn) { + // 每次变化需要重置picker的formatter + this.updateFn('formatter', fn) + } + }, + // 自定义展示文案的格式化函数,返回一个字符串 + displayFormat: { + type: null, + observer (fn) { + if (getType(fn) !== 'function') { + throw Error('The type of displayFormat must be Function') + } + // 每次变化需要重置picker的displayFormat + this.updateFn('displayFormat', fn) + } + }, + // 自定义展示文案的格式化函数,返回一个字符串 + beforeConfirm: { + type: null, + observer (fn) { + if (getType(fn) !== 'function') { + throw Error('The type of beforeConfirm must be Function') + } + // 每次变化需要重置picker的beforeConfirm + this.updateFn('beforeConfirm', fn) + } + }, + // 自定义展示文案的格式化函数,返回一个字符串 + displayFormatTabLabel: { + type: null, + observer (fn) { + if (getType(fn) !== 'function') { + throw Error('The type of displayFormatTabLabel must be Function') + } + // 每次变化需要重置picker的displayFormatTabLabel + this.updateFn('displayFormatTabLabel', fn) + } + }, + defaultValue: { + type: null, + observer (val) { + if (getType(val) === 'array') { + this.setData({ + innerValue: this.getDefaultInnerValue({ + isRegion: true + }), + endInnerValue: this.getDefaultInnerValue({ + isRegion: true, + isEnd: true + }) + }) + } else { + this.setData({ + innerValue: this.getDefaultInnerValue() + }) + } + } + }, + zIndex: { + type: Number, + value: 15 + } + }, + + data: { + child: null, + popupShow: false, + tabLabel: false, + showStart: true, + region: false, + showTabLabel: [], + innerValue: '', + endInnerValue: '', + pickerId: 'wd-datetime-picker-view', + pickerId1: 'wd-datetime-picker-view1', + handleFilter: null, + handleColumnFormatter: null, + handleFormatter: null, + isPicking: false, // 判断pickview是否还在滑动中 + hasConfirmed: false // 判断用户是否点击了确认按钮 + }, + + methods: { + noop () { }, + + getDefaultInnerValue ({ isRegion, isEnd } = {}) { + const { value, defaultValue } = this.data + + if (isRegion) { + if (isEnd) { + return value[1] || (defaultValue && defaultValue.length ? defaultValue[1] : '') + } else { + return value[0] || (defaultValue && defaultValue.length ? defaultValue[0] : '') + } + } else { + return value || defaultValue + } + }, + + // 对外暴露接口,打开弹框 + open () { + this.showPopup() + }, + + // 对外暴露接口,关闭弹框 + close () { + this.onCancel() + }, + + /** + * @description JM小程序无法透过中间变量传递function对象,因此在此处直接设置picker中的props值 + * @param {String} key 修改的键名 + * @param {Function} fn 修改的函数主体 + */ + updateFn (key, fn) { + this.picker && this.picker.setData({ + [key]: fn + }) + + if (this.data.region) { + this.picker1 && this.picker1.setData({ + [key]: fn + }) + } + }, + + /** + * @description 展示popup,小程序有个bug,在picker-view弹出时设置value,会触发change事件,而且会将picker-view的value多次触发change重置为第一项 + */ + showPopup () { + if (this.data.disabled || this.data.readonly) return + + this.$emit('open') + if (this.data.region) { + this.setData({ + popupShow: true, + showStart: true, + innerValue: this.getDefaultInnerValue({ + isRegion: true + }), + endInnerValue: this.getDefaultInnerValue({ + isRegion: true, + isEnd: true + }) + }) + } else { + this.setData({ + popupShow: true, + innerValue: this.getDefaultInnerValue() + }) + } + this.setShowValue(true, false) + }, + + /** + * @description 区域选择时tab标签切换时触发 + */ + tabChange (event) { + this.setData({ + showStart: !this.data.showStart + }) + // 列项刷新多级联动挂载到datetimepickerView + const picker = this.data.showStart ? this.picker : this.picker1 + picker.setData({ + columns: picker.updateColumns() + }) + + this.$emit('toggle', this.data.showStart ? this.data.innerValue : this.data.endInnerValue) + }, + + /** + * @description datetimePickerView change 事件 + */ + onChangeStart ({ detail: { value } }) { + this.setData({ + innerValue: value + }) + if (this.data.region) { + this.setData({ + showTabLabel: [this.setTabLabel(), this.data.showTabLabel[1]] + }) + this.$emit('change', { + value: [value, this.data.endInnerValue] + }) + this.picker.setData({ + columns: this.picker.updateColumns() + }) + this.picker1.setData({ + columns: this.picker1.updateColumns() + }) + } else { + this.$emit('change', { + value: this.data.innerValue + }) + } + }, + + /** + * @description 区域选择 下方 datetimePickerView change 事件 + */ + onChangeEnd ({ detail: { value } }) { + this.setData({ + endInnerValue: value, + showTabLabel: [this.data.showTabLabel[0], this.setTabLabel(1)] + }) + this.$emit('change', { + value: [this.data.innerValue, value] + }) + this.picker.setData({ + columns: this.picker.updateColumns() + }) + this.picker1.setData({ + columns: this.picker1.updateColumns() + }) + }, + + /** + * @description 点击取消按钮触发。关闭popup,触发cancel事件。 + */ + onCancel () { + this.setData({ + popupShow: false + }) + setTimeout(() => { + if (this.data.region) { + this.setData({ + innerValue: this.getDefaultInnerValue({ + isRegion: true + }), + endInnerValue: this.getDefaultInnerValue({ + isRegion: true, + isEnd: true + }) + }) + } else { + this.setData({ + innerValue: this.getDefaultInnerValue() + }) + } + }, 200) + + this.$emit('cancel') + }, + + /** picker触发confirm事件,同步触发confirm事件 */ + onConfirm () { + if (this.data.loading) return + + // 如果当前还在滑动且未停止下来,则锁住先不确认,等滑完再自动确认,避免pickview值未更新 + if (this.data.isPicking) { + this.setData({ + hasConfirmed: true + }) + return + } + + const { beforeConfirm } = this.data + if (beforeConfirm) { + beforeConfirm(this.data.innerValue, isPass => { + isPass && this.handleConfirm() + }, this) + } else { + this.handleConfirm() + } + }, + + onPickStart () { + this.setData({ + isPicking: true + }) + }, + + onPickEnd () { + this.setData({ + isPicking: false + }) + + // 延迟一会,因为组件层级嵌套过多,日期的计算时间也较长 + setTimeout(() => { + if (this.data.hasConfirmed) { + this.setData({ + hasConfirmed: false + }) + this.onConfirm() + } + }, 50) + }, + + handleConfirm () { + if (this.data.loading || this.data.disabled) { + this.setData({ + popupShow: false + }) + return + } + const value = this.data.region ? [this.data.innerValue, this.data.endInnerValue] : this.data.innerValue + this.setData({ + popupShow: false, + value + }) + this.$emit('confirm', { + value + }) + this.setShowValue(false, true) + }, + + /** + * @description 设置区域选择 tab 标签展示值 + * @param {Number} index 索引标志位,有三个有效值; 0(默认):上方picker索引; 1:下方picker索引; + * @return {String} showTabLabel + */ + setTabLabel (index = 0) { + if (this.data.region) { + const items = index === 0 ? this.picker.picker.getSelects() : this.picker1.picker.getSelects() + return this.defaultDisplayFormat(items, true) + } + }, + + /** + * @description 设置展示值 + * @param {Boolean} tab 是否修改tab展示值(尽在区域选择情况下生效) + * @param {Boolean} isConfirm 是否提交当前修改 + */ + setShowValue (tab = false, isConfirm = false) { + const { value, region } = this.data + if (region) { + const items = this.picker.picker.getSelects() + const endItems = this.picker1.picker.getSelects() + this.setData({ + showValue: tab + ? this.data.showValue + : [(value[0] || isConfirm ? this.defaultDisplayFormat(items) : ''), (value[1] || isConfirm ? this.defaultDisplayFormat(endItems) : '')], + showTabLabel: [this.defaultDisplayFormat(items, true), this.defaultDisplayFormat(endItems, true)] + }) + } else { + const items = this.picker.picker.getSelects() + this.setData({ + showValue: (value || isConfirm) ? this.defaultDisplayFormat(items) : '' + }) + } + }, + + /** + * @description 设置展示值 + * @param {Object} items 获取到的选中项 包含 { value, label } + * @param {Boolean} tabLabel 当前返回的是否是展示tab上的标签 + * @return {String} showValue / showTabLabel + */ + defaultDisplayFormat (items, tabLabel = false) { + if (items.length === 0) return '' + + if (tabLabel && this.data.displayFormatTabLabel) { + return this.data.displayFormatTabLabel(items) + } + + if (this.data.displayFormat) { + return this.data.displayFormat(items) + } + + // 如果使用了自定义的的formatter,defaultDisplayFormat无效 + if (this.data.formatter) { + /** + * 不建议使用 this.picker.picker.getLabels() 拉取 + * 在初始展示时,需要使用模拟 nextTick 来等待内部 pickerView 渲染后labels才可得到format后的labels + * 但使用模拟nextTick会造成页面延迟展示问题,对用户感知来讲不友好,因此不适用该方法 + */ + const map = (items) => { + const typeMaps = { + datetime: ['year', 'month', 'date', 'hour', 'minute'], + date: ['year', 'month', 'date'], + time: ['hour', 'minute'], + 'year-month': ['year', 'month'] + } + return items.map((item, index) => { + return this.data.formatter(typeMaps[this.data.type][index], item.value) + }) + } + return map(items).join('') + } + + switch (this.data.type) { + case 'date': + return `${items[0].label}-${items[1].label}-${items[2].label}` + case 'year-month': + return `${items[0].label}-${items[1].label}` + case 'time': + return `${items[0].label}:${items[1].label}` + case 'datetime': + return `${items[0].label}-${items[1].label}-${items[2].label} ${items[3].label}:${items[4].label}` + } + }, + + /** + * @description 区域选择time禁用规则,根据传入的位置标志以及日期类型 返回该节点是否禁用 + * @param {String} isStart 时间段类型 true:start | false:end + * @param {Array} column 当前遍历到的列数组 + * @param {Number} cindex 外层column的索引(对应每一个类型) + * @param {Number / String} value 遍历到的当前值 + * @param {Array} currentValue 当前选中的值 this.pickerValue + * @param {Array} boundary 当前变量的限制值,决定禁用的边界值 + * @return {Boolean} disabled + */ + columnDisabledRules (isStart, columns, cIndex, value, currentValue, boundary) { + const { type } = this.data + // 0年 1月 2日 3時 4分 + // startPicker 除最小值外 还需要有一个时间限制, endPicker 时间选择后, startPicker 的 添加一个时间限制boundary min->boundary + // endPicker 除最小值外 还需要有一个时间限制, startPicker 时间选择后, endPicker 的 添加一个时间限制boundary boundary->max + const column = columns[cIndex] + // 根据当前选择年确认 ranges[0][0] minYear ranges[0][1] maxYear 以此类推 + if (type === 'datetime') { + const year = boundary[0] + const month = boundary[1] + const date = boundary[2] + const hour = boundary[3] + const minute = boundary[4] + + if (column.type === 'year') { + return isStart ? value > year : value < year + } + if (column.type === 'month' && currentValue[0] === year) { + return isStart ? value > month : value < month + } + if (column.type === 'date' && (currentValue[0] === year && currentValue[1] === month)) { + return isStart ? value > date : value < date + } + if (column.type === 'hour' && currentValue[0] === year && currentValue[1] === month && currentValue[2] === date) { + return isStart ? value > hour : value < hour + } + if (column.type === 'minute' && currentValue[0] === year && currentValue[1] === month && currentValue[2] === date && currentValue[3] === hour) { + return isStart ? value > minute : value < minute + } + } else if (type === 'year-month') { + const year = boundary[0] + const month = boundary[1] + + if (column.type === 'year') { + return isStart ? value > year : value < year + } + if (column.type === 'month' && currentValue[0] === year) { + return isStart ? value > month : value < month + } + } else if (type === 'date') { + const year = boundary[0] + const month = boundary[1] + const date = boundary[2] + + if (column.type === 'year') { + return isStart ? value > year : value < year + } + if (column.type === 'month' && currentValue[0] === year) { + return isStart ? value > month : value < month + } + if (column.type === 'date' && currentValue[0] === year && currentValue[1] === month) { + return isStart ? value > date : value < date + } + } else if (type === 'time') { + const hour = boundary[0] + const minute = boundary[1] + + if (column.type === 'hour') { + return isStart ? value > hour : value < hour + } + if (column.type === 'minute' && currentValue[0] === hour) { + return isStart ? value > minute : value < minute + } + } + + return false + }, + + /** + * @description 自定义列项筛选规则,对每列单项进行禁用校验,最终返回传入PickerView的columns数组 + * @param {Component} picker datetimePickerView对象 + * @return {Array} columns + */ + customColumnFormatter (picker) { + const { type, innerValue, endInnerValue } = this.data + const { startSymbol, formatter } = picker.data + // 校准上下方picker的value值,与内部innerValue对应 + const start = picker.correctValue(innerValue) + const end = picker.correctValue(endInnerValue) + /** + * 如果是上方picekr 那么将下方picker的值作为下边界 + * 如果是下方picekr 那么将上方picker的值作为上边界 + */ + const currentValue = startSymbol ? picker.getPickerValue(start, type) : picker.getPickerValue(end, type) + const boundary = startSymbol ? picker.getPickerValue(end, type) : picker.getPickerValue(start, type) + // 获取当前picekr中的源列数组 + const columns = picker.getOriginColumns() + + const mapColumns = (columns) => { + // 此时index是最外层知道当前的索引即可得到当前是哪个时间段 + return columns.map((column, cIndex) => { + return column.values.map((value, index) => { + const disabled = this.columnDisabledRules(startSymbol, columns, cIndex, value, currentValue, boundary) + return { + label: formatter ? formatter(column.type, padZero(value)) : padZero(value), + value, + disabled + } + }) + }) + } + return mapColumns(columns) + } + }, + + beforeCreate () { + // pickerView挂载到全局 + this.picker = this.selectComponent(`#${this.data.pickerId}`) + this.picker1 = this.selectComponent(`#${this.data.pickerId1}`) + }, + + created () { + const { value } = this.data + + if (getType(value) === 'array') { + this.setData({ + region: true, + innerValue: this.getDefaultInnerValue({ + isRegion: true + }), + endInnerValue: this.getDefaultInnerValue({ + isRegion: true, + isEnd: true + }) + }) + } else { + this.setData({ + innerValue: this.getDefaultInnerValue() + }) + } + this.setShowValue(true, false) + + this.updateFn('columnFormatter', getType(value) === 'array' ? this.customColumnFormatter.bind(this) : null) + } +}) \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-datetime-picker/index.json b/src/uni_modules/wot-design-uni/components/wd-datetime-picker/index.json new file mode 100644 index 00000000..a40f9e68 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-datetime-picker/index.json @@ -0,0 +1,8 @@ +{ + "component": true, + "usingComponents": { + "wd-datetime-picker-view": "../datetimePickerView/index", + "wd-popup": "../popup/index", + "wd-icon": "../icon/index" + } +} \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-datetime-picker/index.jxml b/src/uni_modules/wot-design-uni/components/wd-datetime-picker/index.jxml new file mode 100644 index 00000000..ca0db907 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-datetime-picker/index.jxml @@ -0,0 +1,126 @@ + + + + + + + {{ label }} + + + + + {{showValue[0] ? showValue[0] : placeholder}} + 至 + {{showValue[1] ? showValue[1] : placeholder}} + + + {{ showValue ? showValue : placeholder }} + + + + + + + + + + + + {{ cancelButtonText }} + + + {{ title }} + + + {{ confirmButtonText }} + + + + + + 开始时间 + {{showTabLabel[0]}} + + + 结束时间 + {{showTabLabel[1]}} + + + + + + + + + + + diff --git a/src/uni_modules/wot-design-uni/components/wd-datetime-picker/index.scss b/src/uni_modules/wot-design-uni/components/wd-datetime-picker/index.scss new file mode 100644 index 00000000..f05f3301 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-datetime-picker/index.scss @@ -0,0 +1,175 @@ +@import "./../common/abstracts/_mixin.scss"; +@import "./../common/abstracts/variable.scss"; + +@include b(picker) { + @include e(popup) { + border-radius: 16px 16px 0px 0px; + } + + @include when(border) { + .wd-picker__field { + @include halfPixelBorder("top", $-cell-padding); + } + } + @include when(large) { + .wd-picker__field { + font-size: $-cell-title-fs-large; + } + .wd-picker__arrow { + font-size: $-cell-icon-size-large; + } + } + @include when(error) { + .wd-picker__value, + .wd-picker__arrow, + .wd-picker__placeholder { + color: $-input-error-color; + } + } + @include when(align-right) { + .wd-picker__value { + text-align: right; + } + } + @include e(field) { + position: relative; + display: flex; + padding: 0 $-cell-padding; + align-items: flex-start; + background-color: $-color-white; + text-decoration: none; + color: $-cell-title-color; + font-size: $-cell-title-fs; + overflow: hidden; + line-height: $-cell-ling-height; + + @include when(disabled) { + .wd-picker__value { + color: $-input-disabled-color; + } + } + } + + @include when(disabled) { + .wd-picker__value { + color: $-picker-column-disabled-color; + } + } + + @include e(label) { + position: relative; + width: $-input-cell-label-width; + padding: $-cell-wrapper-padding 0; + margin-right: $-cell-padding; + color: $-cell-title-color; + box-sizing: border-box; + + @include when(required) { + padding-left: 12px; + + &::after { + position: absolute; + left: 0; + top: $-cell-wrapper-padding + 2px; + content: "*"; + font-size: $-cell-required-size; + line-height: 1.1; + color: $-cell-required-color; + } + } + } + + @include e(value) { + width: 0; + flex: 1; + padding: $-cell-wrapper-padding 0; + margin-right: 10px; + color: $-cell-value-color; + + @include when(ellipsis) { + @include lineEllipsis; + } + } + + @include e(placeholder) { + color: $-input-placeholder-color; + } + + @include e(arrow) { + display: block; + margin-top: $-cell-wrapper-padding; + font-size: $-cell-icon-size; + color: $-cell-arrow-color; + line-height: 1.25; + } + + @include e(toolbar) { + position: relative; + display: flex; + font-size: $-picker-toolbar-fs; + height: $-picker-toolbar-height; + line-height: $-picker-action-height; + justify-content: space-between; + align-items: center; + box-sizing: border-box; + } + + @include e(action) { + display: block; + border: none; + outline: none; + font-size: $-picker-toolbar-fs; + color: $-picker-toolbar-finish-color; + background: transparent; + padding: 24px 15px 14px 15px; + + @include m(cancel) { + color: $-picker-toolbar-cancel-color; + } + + @include when(loading) { + color: $-picker-loading-button-color; + } + } + @include e(title) { + display: block; + float: 1; + color: $-picker-toolbar-title-color; + } + + @include e(region-tabs) { + display: flex; + } + + @include e(region) { + width: 50%; + display: inline-block; + color: $-picker-region-color; + text-align: center; + padding: 14px 0; + font-size: $-picker-region-fs; + line-height: 16px; + transition: all 0.15s ease-out; + + @include when(active) { + background: $-picker-region-bg-active-color; + color: $-color-white; + } + } + + @include e(region-time) { + font-size: 16px; + margin-top: 2px; + } + + @include e(hidden) { + visibility: hidden; + overflow: hidden; + height: 0; + } + + @include e(show) { + visibility: visible; + height: auto; + } +} diff --git a/src/uni_modules/wot-design-uni/components/wd-datetime-picker/wd-datetime-picker.vue b/src/uni_modules/wot-design-uni/components/wd-datetime-picker/wd-datetime-picker.vue new file mode 100644 index 00000000..f98b7664 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-datetime-picker/wd-datetime-picker.vue @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-divider/index.js b/src/uni_modules/wot-design-uni/components/wd-divider/index.js new file mode 100644 index 00000000..11816a26 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-divider/index.js @@ -0,0 +1,7 @@ +import VueComponent from '../common/component' + +VueComponent({ + props: { + color: String + } +}) \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-divider/index.json b/src/uni_modules/wot-design-uni/components/wd-divider/index.json new file mode 100644 index 00000000..467ce294 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-divider/index.json @@ -0,0 +1,3 @@ +{ + "component": true +} diff --git a/src/uni_modules/wot-design-uni/components/wd-divider/index.jxml b/src/uni_modules/wot-design-uni/components/wd-divider/index.jxml new file mode 100644 index 00000000..18520ba5 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-divider/index.jxml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-divider/index.scss b/src/uni_modules/wot-design-uni/components/wd-divider/index.scss new file mode 100644 index 00000000..9d5fda19 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-divider/index.scss @@ -0,0 +1,22 @@ +@import '../common/abstracts/variable'; +@import '../common/abstracts/mixin'; + +@include b(divider) { + position: relative; + display: flex; + padding: $-divider-padding; + align-items: center; + color: $-divider-color; + font-size: $-divider-fs; + + @include e(line) { + display: block; + flex: 1; + height: 1px; + transform: scaleY(0.5); + background: $-divider-line-color; + } + @include e(content) { + padding: 0 12px; + } +} diff --git a/src/uni_modules/wot-design-uni/components/wd-divider/wd-divider.vue b/src/uni_modules/wot-design-uni/components/wd-divider/wd-divider.vue new file mode 100644 index 00000000..f98b7664 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-divider/wd-divider.vue @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-drop-menu-item/index.js b/src/uni_modules/wot-design-uni/components/wd-drop-menu-item/index.js new file mode 100644 index 00000000..5c528c5d --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-drop-menu-item/index.js @@ -0,0 +1,170 @@ +import VueComponent from '../common/component' +import { debounce } from '../common/util' +import { pushToQueue, removeFromQueue } from '../common/clickoutside' + +VueComponent({ + externalClasses: ['custom-title', 'custom-icon'], + data: { + showWrapper: false, + showPop: false, + position: '', + positionStyle: '', + transName: '', + zIndex: '12', + displayTitle: '', + modal: true, + closeOnClickModal: true, + duration: 0 + }, + props: { + // 当前选中的value + value: { + type: null, + observer (val) { + if (typeof val !== 'number' && typeof val !== 'string') { + console.warn('[wot-design] warning(wd-drop-menu-item): the type of value should be a number or a string.') + return + } + + this.updateTitle() + } + }, + // 可能是 array || String + options: { + type: Array, + observer () { + this.updateTitle() + } + }, + useDropItemSlot: Boolean, + disabled: Boolean, + iconName: { + type: String, + value: 'check' + }, + title: { + type: String, + observer () { + this.updateTitle() + } + }, + valueKey: { + type: String, + value: 'value' + }, + labelKey: { + type: String, + value: 'label' + }, + tipKey: { + type: String, + value: 'tip' + } + }, + relations: { + '../dropMenu/index': { + type: 'parent', + linked (target) { + this.parent = target + }, + unlinked () { + this.parent = null + } + } + }, + beforeCreate () { + pushToQueue(this) + this.updateTitle = debounce(this.updateTitle, 50) + }, + destroyed () { + removeFromQueue(this) + }, + mounted () { + this.setDisplayTitle() + }, + methods: { + setDisplayTitle () { + const { title, value, options, valueKey, labelKey } = this.data + + if (title) { + this.setData({ + displayTitle: title + }) + return + } + for (let i = 0, len = options.length; i < len; i++) { + if (value === options[i][valueKey]) { + this.setData({ + displayTitle: options[i][labelKey] + }) + return + } + } + + console.warn('[wot-design] warning(wd-drop-menu-item): no value is matched in the options option.') + }, + updateTitle () { + this.setDisplayTitle() + this.parent && this.parent.updateTitle() + }, + /** + * 父组件更改子组件内部 data 值 + * @param {string} key 键名 + * @param {null} value 键值 + */ + set (key, value) { + this.setData({ + [key]: value + }) + }, + // 模拟单选操作 默认根据 value 选中操作 + choose (event) { + if (this.data.disabled) return + const index = event.currentTarget.dataset.index + const { valueKey } = this.data + const item = this.data.options[index] + this.setData({ + value: (item[valueKey] !== '' && item[valueKey] !== undefined) ? item[valueKey] : item + }) + this.close() + this.$emit('change', { + value: (item[valueKey] !== '' && item[valueKey] !== undefined) ? item[valueKey] : item, + selectedItem: item + }) + this.parent.updateTitle() + }, + // 外部关闭弹出框 + close () { + this.setData({ showPop: false }) + this.parent.fold(-1) + }, + open () { + const { direction, modal, closeOnClickModal, duration, offset } = this.parent.data + this.setData({ + showWrapper: true, + showPop: true, + duration, + modal, + closeOnClickModal, + position: direction === 'down' ? 'top' : 'bottom', + positionStyle: direction === 'down' ? `top: ${offset}px; bottom: 0;` : `top: 0; bottom: ${offset}px` + }) + this.$emit('open') + }, + onPopupClose () { + this.setData({ + showWrapper: false + }) + this.$emit('closed') + }, + handleOpen () { + this.$emit('open') + }, + handleOpened () { + this.$emit('opened') + }, + handleClose () { + this.$emit('close') + } + } +}) \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-drop-menu-item/index.json b/src/uni_modules/wot-design-uni/components/wd-drop-menu-item/index.json new file mode 100644 index 00000000..b07c9ae5 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-drop-menu-item/index.json @@ -0,0 +1,8 @@ +{ + "component": true, + "usingComponents": { + "wd-icon": "../icon/index", + "wd-transition": "../transition/index", + "wd-popup": "../popup/index" + } +} \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-drop-menu-item/index.jxml b/src/uni_modules/wot-design-uni/components/wd-drop-menu-item/index.jxml new file mode 100644 index 00000000..c8d7e0d0 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-drop-menu-item/index.jxml @@ -0,0 +1,46 @@ + + + + + + {{ item[labelKey] ? item[labelKey] : item }} + {{ item[tipKey] }} + + + + + + + diff --git a/src/uni_modules/wot-design-uni/components/wd-drop-menu-item/index.scss b/src/uni_modules/wot-design-uni/components/wd-drop-menu-item/index.scss new file mode 100644 index 00000000..3a4a6df0 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-drop-menu-item/index.scss @@ -0,0 +1,57 @@ +@import '../common/abstracts/variable'; +@import '../common/abstracts/mixin'; +@import '../popup/index.scss'; + +@include b(drop-item) { + position: fixed; + right: 0; + left: 0; + overflow: hidden; + font-size: $-drop-menu-item-fs; + color: $-drop-menu-item-color; + width: 100%; + z-index: 101; + + .wd-drop-item__popup { + position: absolute; + max-height: 80%; + } + + @include e(option) { + display: flex; + height: $-drop-menu-item-height; + line-height: $-drop-menu-item-height; + padding: 0 $-drop-menu-side-padding; + justify-content: space-between; + align-items: center; + transition: color .2s; + + @include when(active) { + color: $-drop-menu-item-color-active; + } + } + + @include e(title){ + display: block; + } + + @include e(tip) { + display: inline-block; + color: $-drop-menu-item-color-tip; + font-size: $-drop-menu-item-fs-tip; + margin-left: 2px; + } + + @include e(icon){ + display: block; + font-size: $-drop-menu-option-check-size; + } + + @include e(modal) { + position: fixed; + left: 0; + right: 0; + background: rgba(0, 0, 0, 0.7); + height: 100%; + } +} \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-drop-menu-item/wd-drop-menu-item.vue b/src/uni_modules/wot-design-uni/components/wd-drop-menu-item/wd-drop-menu-item.vue new file mode 100644 index 00000000..f98b7664 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-drop-menu-item/wd-drop-menu-item.vue @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-drop-menu/index.js b/src/uni_modules/wot-design-uni/components/wd-drop-menu/index.js new file mode 100644 index 00000000..1f40b70f --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-drop-menu/index.js @@ -0,0 +1,120 @@ +import VueComponent from '../common/component' +import { closeOther } from '../common/clickoutside' + +VueComponent({ + data: { + // 保存的永远是当前选中的值 + titleList: [], + // -1表示折叠 + currentIndex: -1, + offset: 0 + }, + props: { + zIndex: { + type: Number, + value: 12 + }, + direction: { + type: String, + value: 'down', + observer (newValue) { + if (newValue !== 'up' && newValue !== 'down') { + this.setData({ direction: 'down' }) + console.warn('[wot design] warning(wd-drop-menu): direction must be \'up\' or \'down\'') + } + } + }, + modal: { + type: Boolean, + value: true + }, + closeOnClickModal: { + type: Boolean, + value: true + }, + duration: { + type: Number, + value: 200 + } + }, + relations: { + '../dropMenuItem/index': { + type: 'child', + linked (target) { + this.children = this.children || [] + this.children.push(target) + }, + unlinked (target) { + this.children = this.children.filter(child => child !== target) + } + } + }, + created () { + const { windowHeight } = jd.getSystemInfoSync() + this.windowHeight = windowHeight + this.children = [] + }, + mounted () { + this.updateTitle() + }, + methods: { + noop () { }, + toggle (event) { + // 如果重复展开相同的选项,则折叠选项卡 + const { index } = event.currentTarget.dataset + const child = this.children[index] + // 点击当前 menu, 关闭其他 menu + if (!child.data.disabled) { + const currentIndex = index === this.data.currentIndex ? -1 : index + closeOther(child) + this.fold(currentIndex) + } + }, + /** + * 控制菜单内容是否展开 + * @param {Number} currentIndex 当前选的索引 + */ + fold (currentIndex) { + this.setData({ currentIndex }) + + if (currentIndex === -1) { + this.children.forEach((item, index) => { + item.set('showPop', false) + }) + return + } + + this.getRect('.wd-drop-menu').then(rect => { + if (!rect) return + const { top, bottom } = rect + + if (this.data.direction === 'down') { + this.setData({ + offset: bottom + }) + } else { + this.setData({ + offset: this.windowHeight - top + }) + } + + // 选中当前关掉其他的 + this.children.forEach((item, index) => { + currentIndex === index ? item.open() : item.set('showPop', false) + }) + }) + }, + // 重设选中的 value 菜单列表 + updateTitle () { + const titleList = [] + this.children.forEach((item, index) => { + const { displayTitle, disabled } = item.data + titleList.push({ + title: displayTitle, + disabled: disabled + }) + }) + this.setData({ titleList }) + } + } +}) \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-drop-menu/index.json b/src/uni_modules/wot-design-uni/components/wd-drop-menu/index.json new file mode 100644 index 00000000..f5b2a4f2 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-drop-menu/index.json @@ -0,0 +1,6 @@ +{ + "component": true, + "usingComponents": { + "wd-icon": "../icon/index" + } +} \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-drop-menu/index.jxml b/src/uni_modules/wot-design-uni/components/wd-drop-menu/index.jxml new file mode 100644 index 00000000..189f5e75 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-drop-menu/index.jxml @@ -0,0 +1,21 @@ + + + + + {{ item.title }} + + + + + + diff --git a/src/uni_modules/wot-design-uni/components/wd-drop-menu/index.scss b/src/uni_modules/wot-design-uni/components/wd-drop-menu/index.scss new file mode 100644 index 00000000..36ec95d3 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-drop-menu/index.scss @@ -0,0 +1,73 @@ +@import '../common/abstracts/variable'; +@import '../common/abstracts/mixin'; + +@include b(drop-menu) { + box-sizing: border-box; + color: $-drop-menu-color; + font-size: $-drop-menu-fs; + position: relative; + + @include e(list) { + display: flex; + text-align: center; + background-color: #fff; + } + @include e(item) { + flex: 1; + min-width: 0; + height: $-drop-menu-height; + line-height: $-drop-menu-height; + text-align: center; + + @include when(active) { + font-weight: $-fw-medium; + + .wd-drop-menu__item-title::after { + opacity: 1; + } + .wd-drop-menu__arrow { + transform: scale(0.6) rotate(-180deg); + transform-origin: center center; + } + } + @include when(disabled) { + color: $-drop-menu-disabled-color; + } + } + + @include e(item-title) { + position: relative; + display: inline-block; + max-width: 100%; + padding: 0 $-drop-menu-side-padding; + box-sizing: border-box; + + &::after { + position: absolute; + content: ''; + width: 19px; + height: $-drop-menu-line-height; + bottom: 6px; + left: 50%; + transform: translate(-50%, 0); + background: $-drop-menu-line-color; + border-radius: $-drop-menu-line-height; + transition: opacity .15s; + opacity: 0; + } + } + + @include e(item-title-text) { + position: relative; + @include lineEllipsis; + } + + @include e(arrow) { + position: absolute; + display: inline-block; + top: 0; + right: -4px; + transition: transform 0.3s; + transform: scale(0.6); + } +} \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-drop-menu/wd-drop-menu.vue b/src/uni_modules/wot-design-uni/components/wd-drop-menu/wd-drop-menu.vue new file mode 100644 index 00000000..f98b7664 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-drop-menu/wd-drop-menu.vue @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-grid-item/index.js b/src/uni_modules/wot-design-uni/components/wd-grid-item/index.js new file mode 100644 index 00000000..efbfbe86 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-grid-item/index.js @@ -0,0 +1,89 @@ +import VueComponent from '../common/component' + +VueComponent({ + externalClasses: ['custom-text', 'custom-icon'], + + data: { + style: '', + gutterContentStyle: '', + iconStyle: '', + itemClass: '', + gutter: 0, + square: false, + border: true + }, + + props: { + icon: { + type: String, + value: '' + }, + iconSize: { + type: String, + value: '26px' + }, + text: String, + url: String, + linkType: { + type: String, + value: 'navigateTo' + }, + useSlot: Boolean, + useIconSlot: Boolean, + useTextSlot: Boolean, + // badge属性 + isDot: Boolean, + type: String, + value: null, + max: Number + }, + + relations: { + '../grid/index': { + type: 'parent', + linked (target) { + this.parent = target + }, + unlinked () { + this.parent = null + } + } + }, + + mounted () { + this.init() + }, + + methods: { + init () { + if (!this.parent) return + const { children, data } = this.parent + const { column, gutter, square, border, bgColor } = data + const width = column ? 100 / column + '%' : 100 / children.length + '%' + // 单独定义间隔 + const gutterStyle = gutter ? `padding:${gutter}px ${gutter}px 0 0; background-color: transparent;` : '' + // 单独定义正方形 + const squareStyle = square ? `background-color:transparent; padding-bottom: 0; padding-top:${width}` : '' + // 间隔+正方形 + const gutterContentStyle = (gutter && square) ? `right: ${gutter}px; bottom:${gutter}px;height: auto; background-color: ${bgColor}` : `background-color: ${bgColor}` + this.setData({ + border, + square, + gutter, + gutterContentStyle, + style: `width: ${width}; ${squareStyle || gutterStyle}` + }) + }, + click (event) { + if (!this.parent.data.clickable) return + const { url, linkType } = this.data + if (url) { + jd[linkType]({ url }) + } + this.$emit('itemclick') + }, + set (key, value) { + this.setData({ [key]: value }) + } + } +}) \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-grid-item/index.json b/src/uni_modules/wot-design-uni/components/wd-grid-item/index.json new file mode 100644 index 00000000..91e835ab --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-grid-item/index.json @@ -0,0 +1,7 @@ +{ + "component": true, + "usingComponents": { + "wd-badge": "../badge/index", + "wd-icon": "../icon/index" + } +} \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-grid-item/index.jxml b/src/uni_modules/wot-design-uni/components/wd-grid-item/index.jxml new file mode 100644 index 00000000..e25ab22c --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-grid-item/index.jxml @@ -0,0 +1,28 @@ + + + + + + + + + + + + {{ text }} + + + diff --git a/src/uni_modules/wot-design-uni/components/wd-grid-item/index.scss b/src/uni_modules/wot-design-uni/components/wd-grid-item/index.scss new file mode 100644 index 00000000..74f732aa --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-grid-item/index.scss @@ -0,0 +1,113 @@ +@import "../common/abstracts/variable"; +@import "../common/abstracts/mixin"; + +@include b(grid-item) { + height: 100%; + font-size: $-grid-item-fs; + box-sizing: border-box; + + position: relative; + float: left; + + display: flex; + flex-direction: column; + justify-content: center; + text-align: center; + overflow: hidden; + + @include when(border) { + &::before { + content: ""; + position: absolute; + width: 100%; + height: 1px; + transform: scaleY(0.5); + background-color: $-grid-item-border-color; + top: 0; + left: 0; + z-index: 1; + } + &::after { + content: ""; + position: absolute; + width: 1px; + transform: scaleX(0.5); + height: 100%; + background-color: $-grid-item-border-color; + bottom: 0; + right: 0; + } + } + + // 第一行元素 + @include when(first) { + &::after { + content: ""; + position: absolute; + width: 1px; + transform: scaleX(0.5); + height: 100%; + background-color: $-grid-item-border-color; + bottom: 0; + right: 0; + } + } + + // 每行右侧的元素 + @include when(right) { + &::after { + display: none; + } + } + + @include when(last) { + &::after { + display: none; + } + } + @include e(wrapper) { + display: inline-block; + margin: 0 auto; + } + + @include e(content) { + height: 100%; + padding: $-grid-item-padding; + position: relative; + display: flex; + flex-direction: column; + justify-content: center; + + @include when(square) { + box-sizing: border-box; + position: absolute; + top: 0; + right: 0; + left: 0; + padding: 0; + height: 100%; + } + + @include when(round) { + &::after { + content: " "; + position: absolute; + top: 0; + left: 0; + width: 200%; + height: 200%; + border: 1px solid $-grid-item-border-color; + transform-origin: top left; + transform: scale(0.5); + box-sizing: border-box; + } + } + } + + @include e(text) { + margin-top: 8px; + font-size: $-grid-item-fs; + line-height: $-grid-item-fs; + @include lineEllipsis; + } +} diff --git a/src/uni_modules/wot-design-uni/components/wd-grid-item/wd-grid-item.vue b/src/uni_modules/wot-design-uni/components/wd-grid-item/wd-grid-item.vue new file mode 100644 index 00000000..f98b7664 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-grid-item/wd-grid-item.vue @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-grid/index.js b/src/uni_modules/wot-design-uni/components/wd-grid/index.js new file mode 100644 index 00000000..671d83d0 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-grid/index.js @@ -0,0 +1,74 @@ +import VueComponent from '../common/component' +const nextTick = () => new Promise(resolve => setTimeout(resolve, 20)) + +VueComponent({ + props: { + clickable: Boolean, + square: Boolean, + column: { + type: Number, + observer (val, oldVal) { + if (val === oldVal) return + if (val <= 0) { + throw Error('The number of columns attribute value is invalid. The attribute must be greater than 0 and it is not recommended to use a larger value attribute.') + } + oldVal && this.init() + } + }, + border: { + type: Boolean, + value: false, + observer (val) { + val && Promise.resolve().then(nextTick).then(() => { + this.init() + }) + } + }, + bgColor: { + type: String, + value: '#ffffff' + }, + gutter: Number + }, + + relations: { + '../gridItem/index': { + type: 'child', + linked (target) { + this.children = this.children || [] + this.children.push(target) + }, + unlinked (target) { + this.children = this.children.filter(child => child !== target) + } + } + }, + + created () { + this.init() + }, + + methods: { + init () { + if (!this.children) return + + this.children.forEach((item, index) => { + if (this.data.border) { + const { column } = this.data + if (column) { + const isRightItem = this.children.length - 1 === index || (index + 1) % column === 0 + const isFirstLine = (index + 1) <= column + + isFirstLine && item.set('itemClass', 'is-first') + isRightItem && item.set('itemClass', 'is-right') + !isFirstLine && item.set('itemClass', 'is-border') + } else { + item.set('itemClass', 'is-first') + } + this.children.length - 1 === index && item.set('itemClass', item.data.itemClass + ' is-last') + } + item.init() + }) + } + } +}) \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-grid/index.json b/src/uni_modules/wot-design-uni/components/wd-grid/index.json new file mode 100644 index 00000000..f5b2a4f2 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-grid/index.json @@ -0,0 +1,6 @@ +{ + "component": true, + "usingComponents": { + "wd-icon": "../icon/index" + } +} \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-grid/index.jxml b/src/uni_modules/wot-design-uni/components/wd-grid/index.jxml new file mode 100644 index 00000000..abbe3236 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-grid/index.jxml @@ -0,0 +1,4 @@ + + + + diff --git a/src/uni_modules/wot-design-uni/components/wd-grid/index.scss b/src/uni_modules/wot-design-uni/components/wd-grid/index.scss new file mode 100644 index 00000000..69960220 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-grid/index.scss @@ -0,0 +1,9 @@ +@import '../common/abstracts/variable'; +@import '../common/abstracts/mixin'; + +@include b(grid) { + position: relative; + box-sizing: border-box; + overflow: hidden; + height: auto; +} \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-grid/wd-grid.vue b/src/uni_modules/wot-design-uni/components/wd-grid/wd-grid.vue new file mode 100644 index 00000000..f98b7664 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-grid/wd-grid.vue @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-icon/index.js b/src/uni_modules/wot-design-uni/components/wd-icon/index.js new file mode 100644 index 00000000..df7a2869 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-icon/index.js @@ -0,0 +1,17 @@ +import VueComponent from '../common/component' + +VueComponent({ + props: { + name: { + type: String, + observer (val) { + this.setData({ + isImageUrl: val.indexOf('/') > -1 + }) + } + }, + color: String, + size: String, + customStyle: String + } +}) \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-icon/index.json b/src/uni_modules/wot-design-uni/components/wd-icon/index.json new file mode 100644 index 00000000..467ce294 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-icon/index.json @@ -0,0 +1,3 @@ +{ + "component": true +} diff --git a/src/uni_modules/wot-design-uni/components/wd-icon/index.jxml b/src/uni_modules/wot-design-uni/components/wd-icon/index.jxml new file mode 100644 index 00000000..d28993fb --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-icon/index.jxml @@ -0,0 +1,4 @@ + + + \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-icon/index.scss b/src/uni_modules/wot-design-uni/components/wd-icon/index.scss new file mode 100644 index 00000000..0ee6df79 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-icon/index.scss @@ -0,0 +1,207 @@ +@font-face { + font-family: 'wd-icons'; + src: + url('./wd-icons.ttf') format('truetype'); + font-weight: normal; + font-style: normal; +} + +.wd-icon { + display: inline-block; + font-family: 'wd-icons' !important; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + + /* Better Font Rendering =========== */ + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.wd-icon-keyboard-collapse:before { + content: "\e900"; +} +.wd-icon-keyboard-delete:before { + content: "\e901"; +} +.wd-icon-arrow-thin-up:before { + content: "\e898"; +} +.wd-icon-arrow-thin-down:before { + content: "\e899"; +} +.wd-icon-rotate:before { + content: "\e902"; +} +.wd-icon-setting:before { + content: "\e903"; +} +.wd-icon-arrow-right:before { + content: "\e904"; +} +.wd-icon-arrow-down:before { + content: "\e905"; +} +.wd-icon-arrow-up:before { + content: "\e906"; +} +.wd-icon-arrow-left:before { + content: "\e907"; +} +.wd-icon-fill-camera:before { + content: "\e908"; +} +.wd-icon-star-on:before { + content: "\e909"; +} +.wd-icon-check-bold:before { + content: "\e90a"; +} +.wd-icon-error-fill:before { + content: "\e90b"; +} +.wd-icon-warn-bold:before { + content: "\e90c"; +} +.wd-icon-close-outline:before { + content: "\e90d"; +} +.wd-icon-close-bold:before { + content: "\e90e"; +} +.wd-icon-check-outline:before { + content: "\e90f"; +} +.wd-icon-wifi-error:before { + content: "\e910"; +} +.wd-icon-subscribe:before { + content: "\e911"; +} +.wd-icon-detection:before { + content: "\e912"; +} +.wd-icon-read:before { + content: "\e913"; +} +.wd-icon-fill-arrow-down:before { + content: "\e914"; +} +.wd-icon-dong:before { + content: "\e915"; +} +.wd-icon-edit-outline:before { + content: "\e916"; +} +.wd-icon-add-circle:before { + content: "\e917"; +} +.wd-icon-copy:before { + content: "\e918"; +} +.wd-icon-bags:before { + content: "\e919"; +} +.wd-icon-decrease:before { + content: "\e91a"; +} +.wd-icon-jdm:before { + content: "\e91b"; +} +.wd-icon-spool:before { + content: "\e91c"; +} +.wd-icon-download:before { + content: "\e91d"; +} +.wd-icon-phone-compute:before { + content: "\e91e"; +} +.wd-icon-computer:before { + content: "\e91f"; +} +.wd-icon-clock:before { + content: "\e920"; +} +.wd-icon-view:before { + content: "\e921"; +} +.wd-icon-eye-close:before { + content: "\e922"; +} +.wd-icon-picture:before { + content: "\e923"; +} +.wd-icon-evaluation:before { + content: "\e924"; +} +.wd-icon-goods:before { + content: "\e925"; +} +.wd-icon-lenovo:before { + content: "\e926"; +} +.wd-icon-list:before { + content: "\e927"; +} +.wd-icon-note:before { + content: "\e928"; +} +.wd-icon-video:before { + content: "\e929"; +} +.wd-icon-warning:before { + content: "\e92a"; +} +.wd-icon-camera:before { + content: "\e92b"; +} +.wd-icon-transfer:before { + content: "\e92c"; +} +.wd-icon-keywords:before { + content: "\e92e"; +} +.wd-icon-chat:before { + content: "\e931"; +} +.wd-icon-delete-thin:before { + content: "\e932"; +} +.wd-icon-add:before { + content: "\e933"; +} +.wd-icon-scan:before { + content: "\e934"; +} +.wd-icon-translate-bold:before { + content: "\e935"; +} +.wd-icon-close:before { + content: "\e936"; +} +.wd-icon-search:before { + content: "\e937"; +} +.wd-icon-delete:before { + content: "\e939"; +} +.wd-icon-more:before { + content: "\e93a"; +} +.wd-icon-thin-arrow-left:before { + content: "\e93b"; +} +.wd-icon-filter:before { + content: "\e93c"; +} +.wd-icon-phone:before { + content: "\e93d"; +} +.wd-icon-refresh:before { + content: "\e93e"; +} +.wd-icon-check:before { + content: "\e93f"; +} diff --git a/src/uni_modules/wot-design-uni/components/wd-icon/wd-icon.vue b/src/uni_modules/wot-design-uni/components/wd-icon/wd-icon.vue new file mode 100644 index 00000000..df51e753 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-icon/wd-icon.vue @@ -0,0 +1,241 @@ + + + + + diff --git a/src/uni_modules/wot-design-uni/components/wd-icon/wd-icons.svg b/src/uni_modules/wot-design-uni/components/wd-icon/wd-icons.svg new file mode 100644 index 00000000..66373a0f --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-icon/wd-icons.svg @@ -0,0 +1,72 @@ + + + +Generated by IcoMoon + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-icon/wd-icons.ttf b/src/uni_modules/wot-design-uni/components/wd-icon/wd-icons.ttf new file mode 100644 index 00000000..c3b93c58 Binary files /dev/null and b/src/uni_modules/wot-design-uni/components/wd-icon/wd-icons.ttf differ diff --git a/src/uni_modules/wot-design-uni/components/wd-img-cropper/index.js b/src/uni_modules/wot-design-uni/components/wd-img-cropper/index.js new file mode 100644 index 00000000..3a52ce8a --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-img-cropper/index.js @@ -0,0 +1,551 @@ +import VueComponent from '../common/component' + +// 延时动画设置 +let CHANGE_TIME = null +// 移动节流 +let MOVE_THROTTLE = null +// 节流标志 +let MOVE_THROTTLE_FLAG = true +// 图片设置尺寸,此值不变(记录最初设定的尺寸) +let INIT_IMGWIDTH = null +// 图片设置尺寸,此值不变(记录最初设定的尺寸) +let INIT_IMGHEIGHT = null +// 顶部裁剪框占比 +const TOP_PERCENT = 0.85 + +VueComponent({ + props: { + show: { + type: Boolean, + value: false, + observer (val) { + if (val) { + INIT_IMGWIDTH = this.data.imgWidth + INIT_IMGHEIGHT = this.data.imgHeight + this.data.info = jd.getSystemInfoSync() + const cutSize = this.data.info.windowWidth - this.data.offset * 2 + this.setData({ + cutWidth: cutSize, + cutHeight: cutSize, + cutTop: (this.data.info.windowHeight * TOP_PERCENT - cutSize) / 2, + cutLeft: this.data.offset, + canvasScale: this.data.exportScale, + canvasHeight: cutSize, + canvasWidth: cutSize + }) + // 根据开发者设置的图片目标尺寸计算实际尺寸 + this.initImageSize() + // 初始化canvas + this.initCanvas() + // 加载图片 + this.data.imgSrc && this.loadImg() + } else { + this.resetImg() + } + } + }, + cancelButtonText: { + type: String, + value: '取消' + }, + confirmButtonText: { + type: String, + value: '完成' + }, + // 是否禁用旋转 + disabledRotate: Boolean, + /** canvas绘图参数 start **/ + // canvasToTempFilePath —— fileType + fileType: { + type: String, + value: 'png' + }, + // canvasToTempFilePath —— quality + quality: { + type: Number, + value: 1 + }, + // 设置导出图片尺寸 + exportScale: { + type: Number, + value: 2 + }, + /** canvas绘图参数 end **/ + // 图片源路径 + imgSrc: { + type: null, + value: '', + observer (val) { + val && this.loadImg() + } + }, + // 图片宽 + imgWidth: { + type: null, + value: null + }, + // 图片高 + imgHeight: { + type: null, + value: null + }, + // 最大缩放 + maxScale: { + type: Number, + value: 3 + }, + /** Watching Data **/ + // 旋转角度 + imgAngle: { + type: Number, + value: 0, + observer (val) { + if (val % 90) { + this.setData({ + imgAngle: Math.round(val / 90) * 90 + }) + } + } + }, + // 是否开启动画 + isAnimation: { + type: Boolean, + value: false, + observer (val) { + // 开启过渡300毫秒之后自动关闭 + clearTimeout(CHANGE_TIME) + if (val) { + CHANGE_TIME = setTimeout(() => { + this.setData({ isAnimation: false }) + }, 300) + } + } + } + }, + + data: { + // 裁剪框的宽高 + picWidth: 0, + picHeight: 0, + cutWidth: 0, + cutHeight: 0, + offset: 20, + // 裁剪框的距顶距左 + cutLeft: 0, + cutTop: 0, + // canvas最终成像宽高 + canvasWidth: '', + canvasHeight: '', + canvasScale: 2, + // 当前缩放大小 + imgScale: 1, + // // 图片宽高 + // imgWidth: null, + // imgHeight: null, + // 图片中心轴点距左的距离 + imgLeft: jd.getSystemInfoSync().windowWidth / 2, + imgTop: jd.getSystemInfoSync().windowHeight / 2 * TOP_PERCENT, + // 是否移动中设置 同时控制背景颜色是否高亮 + IS_TOUCH_END: true, + // 记录移动中的双指位置 [0][1]分别代表两根手指 [1]做待用参数 + movingPosRecord: [{ + x: '', + y: '' + }, { + x: '', + y: '' + }], + // 双指缩放时 两个坐标点斜边长度 + fingerDistance: '' + }, + + methods: { + /** + * @description 对外暴露:控制旋转角度 + * @param {Number} angle 角度 + */ + setRoate (angle) { + if (!angle || this.data.disabledRotate) return + this.setData({ + isAnimation: true, + imgAngle: angle + }) + // 设置旋转后需要判定旋转后宽高是否不符合贴边的标准 + this.detectImgPosIsEdge() + }, + + /** + * @description 对外暴露:初始化图片的大小和角度以及距离 + */ + resetImg () { + const { windowHeight, windowWidth } = jd.getSystemInfoSync() + this.setData({ + imgScale: 1, + imgAngle: 0, + imgLeft: windowWidth / 2, + imgTop: windowHeight / 2 * TOP_PERCENT + }) + }, + + /** + * @description 加载图片资源文件,并初始化裁剪框内图片信息 + */ + loadImg () { + if (!this.data.imgSrc) return + + jd.getImageInfo({ + src: this.data.imgSrc, + success: (res) => { + // 存储img图片信息 + this.data.imgInfo = res + // 计算最后图片尺寸 + this.computeImgSize() + // 初始化尺寸 + this.resetImg() + }, + fail: () => { + this.setData({ imgSrc: '' }) + } + }) + }, + + /** + * @description 设置图片尺寸,使其有一边小于裁剪框尺寸 + * 1、图片宽或高 小于裁剪框,自动放大至一边与高平齐 + * 2、图片宽或高 大于裁剪框,自动缩小至一边与高平齐 + */ + computeImgSize () { + let { + picWidth, + picHeight, + imgInfo, + cutWidth, + cutHeight + } = this.data + if (!INIT_IMGHEIGHT && !INIT_IMGWIDTH) { + // 没有设置宽高,写入图片的真实宽高 + picWidth = imgInfo.width + picHeight = imgInfo.height + /** + * 设 a = imgWidth; b = imgHeight; x = cutWidth; y = cutHeight + * 共有三种宽高比:1、a/b > x/y 2、a/b < x/y 3、a/b = x/y + * 1、已知 b = y => a = a/b*y + * 2、已知 a = x => b = b/a*x + * 3、可用上方任意公式 + */ + if (picWidth / picHeight > cutWidth / cutHeight) { + picHeight = cutHeight + picWidth = imgInfo.width / imgInfo.height * cutHeight + } else { + picWidth = cutWidth + picHeight = imgInfo.height / imgInfo.width * cutWidth + } + } else if (INIT_IMGHEIGHT && !INIT_IMGWIDTH) { + picWidth = imgInfo.width / imgInfo.height * INIT_IMGHEIGHT + } else if ((!INIT_IMGHEIGHT && INIT_IMGWIDTH) || (INIT_IMGHEIGHT && INIT_IMGWIDTH)) { + picHeight = imgInfo.height / imgInfo.width * INIT_IMGWIDTH + } + + this.setData({ + picWidth, + picHeight + }) + }, + + /** + * @description canvas 初始化 + */ + initCanvas () { + if (!this.data.ctx) { + this.data.ctx = jd.createCanvasContext('wd-img-cropper-canvas', this) + } + }, + + /** + * @description 图片初始化,处理宽高特殊单位 + */ + initImageSize () { + // 处理宽高特殊单位 %>px + if (INIT_IMGWIDTH && typeof INIT_IMGWIDTH === 'string' && INIT_IMGWIDTH.indexOf('%') !== -1) { + const width = INIT_IMGWIDTH.replace('%', '') + INIT_IMGWIDTH = this.data.info.windowWidth / 100 * width + this.setData({ + picWidth: INIT_IMGWIDTH + }) + } else if (INIT_IMGWIDTH && typeof INIT_IMGWIDTH === 'number') { + this.setData({ + picWidth: INIT_IMGWIDTH + }) + } + if (INIT_IMGHEIGHT && typeof INIT_IMGHEIGHT === 'string' && INIT_IMGHEIGHT.indexOf('%') !== -1) { + const height = this.data.imgHeight.replace('%', '') + INIT_IMGHEIGHT = this.data.imgHeight = this.data.info.windowHeight / 100 * height + this.setData({ + picWidth: INIT_IMGHEIGHT + }) + } else if (INIT_IMGHEIGHT && typeof INIT_IMGHEIGHT === 'number') { + this.setData({ + picWidth: INIT_IMGWIDTH + }) + } + }, + + /** + * @description 图片拖动边缘检测:检测移动或缩放时 是否触碰到图片边缘位置 + */ + detectImgPosIsEdge (scale) { + const { + cutLeft, + cutTop, + cutWidth, + cutHeight, + imgAngle + } = this.data + const imgScale = scale || this.data.imgScale + let { picWidth, picHeight, imgLeft, imgTop } = this.data + // 翻转后宽高切换 + if (imgAngle / 90 % 2) { + picWidth = this.data.picHeight + picHeight = this.data.picWidth + } + // 左 + imgLeft = picWidth * imgScale / 2 + cutLeft >= imgLeft ? imgLeft : picWidth * imgScale / 2 + cutLeft + // 右 + imgLeft = cutLeft + cutWidth - picWidth * imgScale / 2 <= imgLeft ? imgLeft : cutLeft + cutWidth - picWidth * imgScale / 2 + // 上 + imgTop = picHeight * imgScale / 2 + cutTop >= imgTop ? imgTop : picHeight * imgScale / 2 + cutTop + // 下 + imgTop = cutTop + cutHeight - picHeight * imgScale / 2 <= imgTop ? imgTop : cutTop + cutHeight - picHeight * imgScale / 2 + + this.setData({ + imgLeft, + imgTop, + imgScale + }) + }, + + /** + * @description 缩放边缘检测:检测移动或缩放时 是否触碰到图片边缘位置 + */ + detectImgScaleIsEdge () { + const { cutHeight, cutWidth, imgAngle } = this.data + let { picWidth, picHeight, imgScale } = this.data + // 翻转后宽高切换 + if (imgAngle / 90 % 2) { + picWidth = this.data.picHeight + picHeight = this.data.picWidth + } + if (picWidth * imgScale < cutWidth) { + imgScale = cutWidth / picWidth + } + if (picHeight * imgScale < cutHeight) { + imgScale = cutHeight / picHeight + } + this.detectImgPosIsEdge(imgScale) + }, + + /** + * @description 节流 + */ + throttle () { + if (this.data.info.platform === 'android') { + clearTimeout(MOVE_THROTTLE) + MOVE_THROTTLE = setTimeout(() => { + MOVE_THROTTLE_FLAG = true + }, 1000 / 40) + } else { + !MOVE_THROTTLE_FLAG && (MOVE_THROTTLE_FLAG = true) + } + }, + + /** + * @description {图片区} 开始拖动 + */ + handleImgTouchStart (event) { + // 如果处于在拖动中,背景为淡色展示全部,拖动结束则为 0.85 透明度 + this.setData({ IS_TOUCH_END: false }) + if (event.touches.length === 1) { + // 单指拖动 + this.data.movingPosRecord[0] = { + x: (event.touches[0].clientX - this.data.imgLeft), + y: (event.touches[0].clientY - this.data.imgTop) + } + } else { + // 以两指为坐标点 做直角三角形 a2 + b2 = c2 + const width = Math.abs(event.touches[1].clientX - event.touches[0].clientX) + const height = Math.abs(event.touches[1].clientY - event.touches[0].clientY) + this.data.fingerDistance = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2)) + } + }, + + /** + * @description {图片区} 拖动中 + */ + handleImgTouchMove (event) { + if (this.data.IS_TOUCH_END || !MOVE_THROTTLE_FLAG) return + // 节流 + this.throttle() + if (event.touches.length === 1) { + // 单指拖动 + const { x, y } = this.data.movingPosRecord[0] + const left = event.touches[0].clientX - x + const top = event.touches[0].clientY - y + this.data.imgLeft = left + this.data.imgTop = top + this.detectImgPosIsEdge() + this.setData({ + imgLeft: this.data.imgLeft, + imgTop: this.data.imgTop + }) + } else { + // 以两指为坐标点 做直角三角形 a2 + b2 = c2 + const width = Math.abs(event.touches[1].clientX - event.touches[0].clientX) + const height = Math.abs(event.touches[1].clientY - event.touches[0].clientY) + const hypotenuse = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2)) + const scale = this.data.imgScale * (hypotenuse / this.data.fingerDistance) + this.data.imgScale = Math.min(scale, this.data.maxScale) + this.detectImgScaleIsEdge() + this.data.fingerDistance = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2)) + } + }, + + /** + * @description {图片区} 拖动结束 + */ + handleImgTouchEnd () { + this.setData({ IS_TOUCH_END: true }) + }, + + /** + * @description 图片已加载完成 + */ + handleImgLoaded (res) { + this.$emit('imgloaded', res) + }, + + /** + * @description 图片加载失败 + */ + handleImgLoadError (err) { + this.$emit('imgloaderror', err) + }, + + /** + * @description 旋转图片 + */ + handleRotate () { + this.setRoate(this.data.imgAngle - 90) + }, + + /** + * @description 取消裁剪图片 + */ + handleCancel () { + this.$emit('cancel') + this.setData({ + show: false, + imgSrc: '' + }) + }, + + /** + * @description 完成裁剪 + */ + handleConfirm (event) { + this.draw() + }, + + /** + * @description canvas 绘制图片输出成文件类型 + */ + canvasToImage () { + const _this = this + const { + fileType, + quality, + cutHeight, + cutWidth, + exportScale + } = this.data + jd.canvasToTempFilePath({ + width: cutWidth * exportScale, + height: Math.round(cutHeight * exportScale), + destWidth: cutWidth * exportScale, + destHeight: Math.round(cutHeight * exportScale), + fileType, + quality, + canvasId: 'wd-img-cropper-canvas', + success (res) { + _this.setData({ + show: false + }) + _this.$emit('confirm', { + tempFilePath: res.tempFilePath, + width: cutWidth * exportScale, + height: cutHeight * exportScale + }) + }, + fail () { + _this.setData({ + show: false + }) + } + }, this) + }, + + /** + * @description canvas绘制,用canvas模拟裁剪框 对根据图片当前的裁剪信息进行模拟 + */ + draw () { + if (!this.data.imgSrc) return + const { + imgSrc, + picWidth, + picHeight, + imgLeft, + imgTop, + imgScale, + imgAngle, + cutLeft, + cutTop, + cutHeight, + cutWidth, + exportScale, + disabledRotate + } = this.data + const draw = () => { + // 图片真实大小 + const width = picWidth * imgScale * exportScale + const height = picHeight * imgScale * exportScale + // 取裁剪框和图片的交集 + const x = imgLeft - cutLeft + const y = imgTop - cutTop + // 如果直接使用canvas绘制的图片会有锯齿,因此需要*设备像素比 + // 设置(x, y)设置图片在canvas中的位置 + this.data.ctx.translate(x * exportScale, y * exportScale) + // 设置 旋转角度 + if (!disabledRotate) { + this.data.ctx.rotate(imgAngle * Math.PI / 180) + } + // drawImage 的 旋转是根据以当前图片的图片水平垂直方向为x、y轴,并在x y轴上移动 + this.data.ctx.drawImage(imgSrc, -width / 2, -height / 2, width, height) + // 绘制图片 + this.data.ctx.draw(false, () => { + this.canvasToImage() + }) + } + + if (this.data.ctx.width !== cutWidth || this.data.ctx.height !== cutHeight) { + this.setData({ + canvasHeight: cutHeight, + canvasWidth: cutWidth + }, () => { + draw() + }) + } else { + draw() + } + }, + preventTouchMove () { } + } +}) \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-img-cropper/index.json b/src/uni_modules/wot-design-uni/components/wd-img-cropper/index.json new file mode 100644 index 00000000..828b0494 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-img-cropper/index.json @@ -0,0 +1,7 @@ +{ + "component": true, + "usingComponents": { + "wd-icon": "../icon/index", + "wd-button": "../button/index" + } +} diff --git a/src/uni_modules/wot-design-uni/components/wd-img-cropper/index.jxml b/src/uni_modules/wot-design-uni/components/wd-img-cropper/index.jxml new file mode 100644 index 00000000..c2489606 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-img-cropper/index.jxml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{cancelButtonText}} + {{confirmButtonText}} + + + + diff --git a/src/uni_modules/wot-design-uni/components/wd-img-cropper/index.scss b/src/uni_modules/wot-design-uni/components/wd-img-cropper/index.scss new file mode 100644 index 00000000..36be9689 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-img-cropper/index.scss @@ -0,0 +1,199 @@ +@import "./../common/abstracts/_mixin.scss"; +@import "./../common/abstracts/variable.scss"; + +@include b(img-cropper) { + background: rgba(0, 0, 0, 1); + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + z-index: 1; + // 裁剪框包裹器 + @include e(wrapper) { + position: relative; + background: rgba(0, 0, 0, 0.45); + } + @include e(cut) { + z-index: 9; + position: absolute; + width: 100vw; + height: 100vh; + display: flex; + flex-direction: column; + pointer-events: none; + + .wd-img-cropper__cut--top, + .wd-img-cropper__cut--bottom, + .wd-img-cropper__cut--left, + .wd-img-cropper__cut--right { + // 拖动中背景蒙层为0 拖动结束为0.85 + background-color: rgba(0, 0, 0, 0.85); + transition: background 0.2s; + @include when(hightlight) { + background-color: rgba(0, 0, 0, 0); + } + } + .wd-img-cropper__cut--bottom, + .wd-img-cropper__cut--right { + flex: auto; + } + @include m(middle) { + display: flex; + } + @include m(body) { + // 若需要变化窗体大小,支持控制窗体的大小来控制下方所有对应的展示 + background-color: transparent; + position: relative; + // 节选框的窗体最外部边缘线 + &::before { + content: ""; + border: 1px solid #fff; + width: calc(200% - 1px); + height: calc(200% - 1px); + position: absolute; + transform: scale(0.5) translate(-1px, -1px); + top: -50%; + left: -50%; + } + + // 结算框对角尺寸 + $border-size: 2px; + // 节选框的四个角 + .is-left-top, + .is-left-bottom, + .is-right-top, + .is-right-bottom { + &::before { + content: ""; + position: absolute; + width: $border-size; + height: 20px; + background-color: #fff; + } + &::after { + content: ""; + position: absolute; + width: 20px; + height: $border-size; + background-color: #fff; + } + } + .is-left-top { + &::before, + &::after { + left: -$border-size; + top: -$border-size; + } + } + .is-left-bottom { + &::before, + &::after { + left: -$border-size; + bottom: -$border-size; + } + } + .is-right-top { + &::before, + &::after { + right: -$border-size; + top: -$border-size; + } + } + .is-right-bottom { + &::before, + &::after { + right: -$border-size; + bottom: -$border-size; + } + } + + // 内部网格线 + .is-gridlines-x, + .is-gridlines-y { + width: 100%; + height: 100%; + position: absolute; + left: 0; + top: 0; + display: flex; + } + .is-gridlines-x { + justify-content: center; + &::before { + content: ""; + display: inline-block; + width: 66.66%; + height: 200%; + border: 1px solid #fff; + border-top: none; + border-bottom: none; + transform: scale(0.5) translate(0, -50%); + } + } + // 内部网格线 - y轴 + .is-gridlines-y { + align-items: center; + &::after { + content: ""; + flex-shrink: 0; + display: inline-block; + width: 200%; + height: 66.66%; + border: 1px solid #fff; + border-left: none; + border-right: none; + transform: scale(0.5) translate(-50%, 0); + } + } + } + } + @include e(img) { + z-index: 2; + top: 0; + left: 0; + position: absolute; + border: none; + width: 100%; + backface-visibility: hidden; + transform-origin: center; + } + @include e(canvas) { + position: fixed; + background: white; + width: 150px; + height: 150px; + z-index: 10; + top: -200%; + pointer-events: none; + } + @include e(footer) { + position: fixed; + z-index: 10; + bottom: 10px; + width: 100%; + height: 15vh; + text-align: center; + @include m(button) { + position: relative; + text-align: left; + margin: 0 20px; + padding-top: 4vh; + // line-height: 32px; + box-sizing: border-box; + .is-cancel { + display: inline-block; + color: #fff; + font-size: 16px; + } + .is-confirm { + position: absolute; + right: 0; + // height: 32px; + width: 56px; + border-radius: 16px; + font-size: 16px; + } + } + } +} diff --git a/src/uni_modules/wot-design-uni/components/wd-img-cropper/wd-img-cropper.vue b/src/uni_modules/wot-design-uni/components/wd-img-cropper/wd-img-cropper.vue new file mode 100644 index 00000000..b4fed275 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-img-cropper/wd-img-cropper.vue @@ -0,0 +1,5 @@ + + + + + diff --git a/src/uni_modules/wot-design-uni/components/wd-img/index.js b/src/uni_modules/wot-design-uni/components/wd-img/index.js new file mode 100644 index 00000000..1f946f0e --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-img/index.js @@ -0,0 +1,55 @@ +/* + * @Author: weisheng + * @Date: 2023-06-10 23:10:23 + * @LastEditTime: 2023-06-11 14:08:09 + * @LastEditors: weisheng + * @Description: + * @FilePath: \wot-design-uni\src\uni_modules\wot-design-uni\components\wd-img\index.js + * 记得注释 + */ +import VueComponent from '../common/component' +import { isDef, addUnit } from '../common/util' + +VueComponent({ + externalClasses: ['custom-image'], + props: { + src: String, + round: Boolean, + mode: String, + lazyLoad: Boolean, + width: { + type: String, + observer: 'setStyle' + }, + height: { + type: String, + observer: 'setStyle' + } + }, + data: { + style: '' + }, + created() { + this.setStyle() + }, + methods: { + handleError({ detail }) { + this.$emit('error', detail) + }, + handleClick() { + this.$emit('click') + }, + handleLoad({ detail }) { + this.$emit('load', detail) + }, + setStyle() { + const { width, height } = this.data + let style = '' + + style = isDef(width) && `width: ${addUnit(width)};` + style = isDef(height) ? style + `height: ${addUnit(height)}` : style + + this.setData({ style }) + } + } +}) diff --git a/src/uni_modules/wot-design-uni/components/wd-img/index.json b/src/uni_modules/wot-design-uni/components/wd-img/index.json new file mode 100644 index 00000000..467ce294 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-img/index.json @@ -0,0 +1,3 @@ +{ + "component": true +} diff --git a/src/uni_modules/wot-design-uni/components/wd-img/index.jxml b/src/uni_modules/wot-design-uni/components/wd-img/index.jxml new file mode 100644 index 00000000..78068740 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-img/index.jxml @@ -0,0 +1,9 @@ + + + diff --git a/src/uni_modules/wot-design-uni/components/wd-img/index.scss b/src/uni_modules/wot-design-uni/components/wd-img/index.scss new file mode 100644 index 00000000..90cada36 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-img/index.scss @@ -0,0 +1,18 @@ +@import "./../common/abstracts/_mixin.scss"; +@import "./../common/abstracts/variable.scss"; + +@include b(img) { + position: relative; + display: inline-block; + + image { + display: block; + width: 100%; + height: 100%; + box-sizing: border-box; + } + @include when(round) { + overflow: hidden; + border-radius: 50%; + } +} diff --git a/src/uni_modules/wot-design-uni/components/wd-img/wd-img.vue b/src/uni_modules/wot-design-uni/components/wd-img/wd-img.vue new file mode 100644 index 00000000..1641091e --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-img/wd-img.vue @@ -0,0 +1,21 @@ + + + + + diff --git a/src/uni_modules/wot-design-uni/components/wd-input-number/index.js b/src/uni_modules/wot-design-uni/components/wd-input-number/index.js new file mode 100644 index 00000000..f65e09ec --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-input-number/index.js @@ -0,0 +1,187 @@ +import VueComponent from '../common/component' +import { debounce, getType } from '../common/util' + +VueComponent({ + data: { + minDisabled: false, + maxDisabled: false + }, + + behaviors: ['jd://form-field'], + + props: { + value: { + type: null, + observer (value, oldValue) { + this.splitDisabled(value) + } + }, + min: { + type: Number, + value: 1, + observer: 'updateBoundary' + }, + max: { + type: Number, + value: Number.MAX_SAFE_INTEGER, + observer: 'updateBoundary' + }, + step: { + type: Number, + value: 1 + }, + stepStrictly: Boolean, + precision: { + type: Number, + value: 0 + }, + disabled: { + type: Boolean, + observer (val) { + this.setData({ + minDisabled: val, + maxDisabled: val + }) + } + }, + withoutInput: Boolean, + inputWidth: String, + allowNull: Boolean, + placeholder: String + }, + + methods: { + updateBoundary () { + const _this = this + debounce(function () { + const value = _this.formatValue(_this.data.value) + _this.setValue(value) + _this.splitDisabled(value) + }, 30)() + }, + + splitDisabled (value) { + const { disabled, min, max, step } = this.data + this.setData({ + minDisabled: disabled || value <= min || this.changeStep(value, -step) < min, + maxDisabled: disabled || value >= max || this.changeStep(value, step) > max + }) + }, + + toPrecision (value) { + return parseFloat(Math.round(value * Math.pow(10, this.data.precision)) / Math.pow(10, this.data.precision)).toFixed(this.data.precision) + }, + + getPrecision (value) { + if (value === undefined) return 0 + const valueString = value.toString() + const dotPosition = valueString.indexOf('.') + let precision = 0 + if (dotPosition !== -1) { + precision = valueString.length - dotPosition - 1 + } + return precision + }, + + toStrictlyStep (value) { + const stepPrecision = this.getPrecision(this.data.step) + const precisionFactory = Math.pow(10, stepPrecision) + return Math.round(value / this.data.step) * precisionFactory * this.data.step / precisionFactory + }, + + setValue (value, change = true) { + const type = getType(value) + + if (this.data.allowNull && (type === 'null' || type === 'undefined' || value === '')) { + this.dispatchChangeEvent(value, change) + return + } + + if (this.data.stepStrictly) { + value = this.toStrictlyStep(value) + } + if ((value || value === 0) && this.data.precision !== undefined) { + value = this.toPrecision(value) + } + if (value > this.data.max) value = this.toPrecision(this.data.max) + if (value < this.data.min) value = this.toPrecision(this.data.min) + + this.dispatchChangeEvent(value, change) + }, + + changeStep (val, step) { + val = Number(val) + + if (isNaN(val)) { + return this.data.min + } + + const precisionFactory = Math.pow(10, this.data.precision) + return this.toPrecision((val * precisionFactory + step * precisionFactory) / precisionFactory) + }, + + sub () { + if (this.data.minDisabled) return + + const newValue = this.changeStep(this.data.value, -this.data.step) + this.dispatchChangeEvent(newValue) + }, + + add () { + if (this.data.maxDisabled) return + + const newValue = this.changeStep(this.data.value, this.data.step) + this.dispatchChangeEvent(newValue) + }, + + handleInput (event) { + const value = event.detail.value || '' + this.dispatchChangeEvent(value) + }, + + handleFocus (event) { + this.$emit('focus', event.detail) + }, + + handleBlur () { + const value = this.formatValue(this.data.value) + this.setValue(value) + this.$emit('blur', { + value + }) + }, + + dispatchChangeEvent (value, change = true) { + this.setData({ value }) + change && this.$emit('change', { value }) + }, + + formatValue (value) { + const type = getType(value) + + if (this.data.allowNull && (type === 'null' || type === 'undefined' || value === '')) { + return '' + } + + value = Number(value) + + if (isNaN(value)) { + value = this.data.min + } + + if (this.data.stepStrictly) { + value = this.toStrictlyStep(value) + } + + if (this.data.precision !== undefined) { + value = value.toFixed(this.data.precision) + } + + return value + } + }, + + created () { + this.dispatchChangeEvent(this.formatValue(this.data.value)) + } +}) \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-input-number/index.json b/src/uni_modules/wot-design-uni/components/wd-input-number/index.json new file mode 100644 index 00000000..f5b2a4f2 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-input-number/index.json @@ -0,0 +1,6 @@ +{ + "component": true, + "usingComponents": { + "wd-icon": "../icon/index" + } +} \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-input-number/index.jxml b/src/uni_modules/wot-design-uni/components/wd-input-number/index.jxml new file mode 100644 index 00000000..13a75688 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-input-number/index.jxml @@ -0,0 +1,23 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-input-number/index.scss b/src/uni_modules/wot-design-uni/components/wd-input-number/index.scss new file mode 100644 index 00000000..907cbd07 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-input-number/index.scss @@ -0,0 +1,108 @@ +@import "./../common/abstracts/_mixin.scss"; +@import "./../common/abstracts/variable.scss"; + +@include b(input-number) { + display: inline-block; + font-size: 0; + user-select: none; + line-height: 1.15; + + @include e(action) { + position: relative; + display: inline-block; + width: $-input-number-btn-width; + height: $-input-number-height; + vertical-align: middle; + color: $-input-number-icon-color; + -webkit-tap-highlight-color: transparent; + box-sizing: border-box; + + // 左右加减号的边框 + &::after { + position: absolute; + content: ""; + width: calc(200% - 2px); + height: calc(200% - 2px); + left: 0; + top: 0; + border: 1px solid $-input-number-border-color; + border-top-left-radius: $-input-number-radius * 2; + border-bottom-left-radius: $-input-number-radius * 2; + transform: scale(0.5); + transform-origin: left top; + } + &:last-child::after { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + border-top-right-radius: $-input-number-radius * 2; + border-bottom-right-radius: $-input-number-radius * 2; + } + @include when(disabled) { + color: $-input-number-disabled-color; + } + } + + @include e(inner) { + position: relative; + display: inline-block; + vertical-align: middle; + } + + @include e(input) { + position: relative; + display: block; + width: $-input-number-input-width; + height: $-input-number-height; + padding: 0 2px; + box-sizing: border-box; + z-index: 1; + background: transparent; + border: none; + outline: none; + text-align: center; + color: $-input-number-color; + font-size: $-input-number-fs; + -webkit-appearance: none; + -webkit-tap-highlight-color: transparent; + } + + @include e(input-border) { + position: absolute; + width: 100%; + height: calc(200% - 2px); + left: 0; + top: 0; + border-top: 1px solid $-input-number-border-color; + border-bottom: 1px solid $-input-number-border-color; + transform: scaleY(0.5); + transform-origin: left top; + z-index: 0; + } + + @include e(action-icon) { + position: absolute; + display: inline-block; + font-size: $-input-number-icon-size; + width: $-input-number-icon-size; + height: $-input-number-icon-size; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + } + + @include when(disabled) { + .wd-input-number__input { + color: $-input-number-disabled-color; + z-index: inherit; + } + .wd-input-number__sub, + .wd-input-number__add { + color: $-input-number-disabled-color; + } + } + @include when(without-input) { + .wd-input-number__action:last-child::after { + border-left: none; + } + } +} \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-input-number/wd-input-number.vue b/src/uni_modules/wot-design-uni/components/wd-input-number/wd-input-number.vue new file mode 100644 index 00000000..b4fed275 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-input-number/wd-input-number.vue @@ -0,0 +1,5 @@ + + + + + diff --git a/src/uni_modules/wot-design-uni/components/wd-input/index.js b/src/uni_modules/wot-design-uni/components/wd-input/index.js new file mode 100644 index 00000000..5061d1cc --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-input/index.js @@ -0,0 +1,230 @@ +import VueComponent from '../common/component' +import cell from '../mixins/cell' + +VueComponent({ + externalClasses: [ + 'custom-textarea-container-class', + 'custom-textarea-class', + 'custom-input-class', + 'custom-label-class' + ], + behaviors: [cell, 'jd://form-field'], + relations: { + '../cellGroup/index': { + type: 'ancestor', + linked (target) { + this.parent = target + }, + unlinked () { + this.parent = null + } + } + }, + data: { + isPwdVisible: false, + clearing: false + }, + props: { + // 原生属性 + placeholder: String, + placeholderStyle: String, + placeholderClass: String, + autoHeight: { + type: Boolean, + value: false + }, + fixed: { + type: Boolean, + value: false + }, + cursorSpacing: { + type: Number, + value: 0 + }, + cursor: { + type: Number, + value: -1 + }, + showConfirmBar: { + type: Boolean, + value: true + }, + selectionStart: { + type: Number, + value: -1 + }, + selectionEnd: { + type: Number, + value: -1 + }, + adjustPosition: { + type: Boolean, + value: true + }, + holdKeyboard: { + type: Boolean, + value: false + }, + confirmType: { + type: String, + value: 'done' + }, + confirmHold: { + type: Boolean, + value: false + }, + focus: { + type: Boolean, + value: false + }, + type: { + type: String, + value: 'text' + }, + maxlength: { + type: Number, + value: -1 + }, + disabled: { + type: Boolean, + value: false + }, + alignRight: Boolean, + // 原生属性结束 + value: { + type: null, + observer (newVal) { + const { disabled, readonly, clearable } = this.data + // 类型校验,支持所有值(除null、undefined。undefined建议统一写成void (0)防止全局undefined被覆盖) + if (newVal === null || newVal === undefined) { + throw Error('value can\'t be null or undefined') + } + this.setData({ + value: newVal, + showClear: clearable && !disabled && !readonly && newVal + }) + } + }, + minlength: Number, + showPassword: Boolean, + clearable: { + type: Boolean, + value: false + }, + showClear: { + type: Boolean, + value: false + }, + readonly: { + type: Boolean, + value: false + }, + useSuffixSlot: { + type: Boolean, + value: false + }, + usePrefixSlot: { + type: Boolean, + value: false + }, + prefixIcon: String, + suffixIcon: String, + showWordLimit: { + type: Boolean, + value: false + }, + showWordCount: { + type: Boolean, + value: false + }, + suffix: String, + suffixCount: Number, + label: String, + labelWidth: String, + useLabelSlot: Boolean, + size: String, + error: Boolean, + center: Boolean, + noBorder: Boolean, + required: Boolean + }, + created () { + this.initState() + }, + methods: { + // 状态初始化 + initState () { + const { showPassword, disabled, readonly, value, clearable, maxlength, showWordLimit } = this.data + let newVal + if (showWordLimit && maxlength && (value.toString().length > maxlength)) { + newVal = value.toString().substring(0, maxlength) + } + this.setData({ + showClear: !disabled && !readonly && clearable && value, + showWordCount: !disabled && !readonly && maxlength && showWordLimit, + showPwdVisible: !disabled && !readonly && showPassword, + value: newVal || value + }) + }, + togglePwdVisible () { + // password属性设置false不生效,置空生效 + this.setData({ isPwdVisible: !this.data.isPwdVisible }) + }, + clear () { + this.setData({ value: '' }) + this.requestAnimationFrame() + .then(() => this.requestAnimationFrame()) + .then(() => this.requestAnimationFrame()) + .then(() => { + this.setData({ focus: true }, () => { + this.$emit('clear') + this.$emit('change', { + value: '' + }) + }) + }) + }, + // 失去焦点时会先后触发change、blur,未输入内容但失焦不触发 change 只触发 blur + handleBlur ({ detail }) { + this.setData({ focus: false }) + this.$emit('change', { + value: this.data.value + }) + this.$emit('blur', { + value: this.data.value, + // textarea 有 cursor + cursor: detail.cursor ? detail.cursor : null + }) + }, + handleFocus ({ detail }) { + if (this.data.clearing) { + this.data.clearing = false + return + } + this.setData( + { focus: true }, + () => this.$emit('focus', detail) + ) + }, + // input事件需要传入 + handleInput ({ detail }) { + this.setData({ value: detail.value }) + this.$emit('input', detail) + }, + handleKeyboardheightchange (event) { + this.$emit('keyboardheightchange', event.detail) + }, + handleConfirm ({ detail }) { + this.$emit('confirm', detail) + }, + handleLineChange (event) { + this.$emit('linechange', event.detail) + }, + onClickSuffixIcon () { + this.$emit('clicksuffixicon') + }, + onClickPrefixIcon () { + this.$emit('clickprefixicon') + } + } +}) \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-input/index.json b/src/uni_modules/wot-design-uni/components/wd-input/index.json new file mode 100644 index 00000000..f5b2a4f2 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-input/index.json @@ -0,0 +1,6 @@ +{ + "component": true, + "usingComponents": { + "wd-icon": "../icon/index" + } +} \ No newline at end of file diff --git a/src/uni_modules/wot-design-uni/components/wd-input/index.jxml b/src/uni_modules/wot-design-uni/components/wd-input/index.jxml new file mode 100644 index 00000000..54881058 --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/wd-input/index.jxml @@ -0,0 +1,145 @@ + + + + + + + + + {{ label }} + + + + + + + + {{ value }} + +