Compare commits

..
2 Commits
Author SHA1 Message Date
yvan 715f117b64 1 2025-08-12 15:04:07 +08:00
yvan 6708fb8eeb 1 2025-08-12 15:04:01 +08:00
545 changed files with 75381 additions and 17 deletions
+1 -1
View File
@@ -14,5 +14,5 @@
<style lang="scss">
/*每个页面公共css */
@import "uview-ui-next/index.scss";
@import "@/uni_modules/uview-next/index.scss";
</style>
+3 -3
View File
@@ -1,11 +1,11 @@
import App from './App'
import uView from 'uview-ui-next'
import uViewNext from '@/uni_modules/uview-next';
// #ifndef VUE3
import Vue from 'vue'
import './uni.promisify.adaptor'
Vue.config.productionTip = false
Vue.use(uView)
Vue.use(uViewNext)
App.mpType = 'app'
const app = new Vue({
...App
@@ -17,7 +17,7 @@ app.$mount()
import { createSSRApp } from 'vue'
export function createApp() {
const app = createSSRApp(App)
app.use(uView)
app.use(uViewNext)
return {
app
}
+559
View File
@@ -11,6 +11,436 @@
"dependencies": {
"mitt": "^3.0.1",
"uview-ui-next": "^0.0.1"
},
"devDependencies": {
"sass": "^1.90.0",
"sass-loader": "^16.0.5"
}
},
"node_modules/@parcel/watcher": {
"version": "2.5.1",
"resolved": "https://registry.npmmirror.com/@parcel/watcher/-/watcher-2.5.1.tgz",
"integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"dependencies": {
"detect-libc": "^1.0.3",
"is-glob": "^4.0.3",
"micromatch": "^4.0.5",
"node-addon-api": "^7.0.0"
},
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
},
"optionalDependencies": {
"@parcel/watcher-android-arm64": "2.5.1",
"@parcel/watcher-darwin-arm64": "2.5.1",
"@parcel/watcher-darwin-x64": "2.5.1",
"@parcel/watcher-freebsd-x64": "2.5.1",
"@parcel/watcher-linux-arm-glibc": "2.5.1",
"@parcel/watcher-linux-arm-musl": "2.5.1",
"@parcel/watcher-linux-arm64-glibc": "2.5.1",
"@parcel/watcher-linux-arm64-musl": "2.5.1",
"@parcel/watcher-linux-x64-glibc": "2.5.1",
"@parcel/watcher-linux-x64-musl": "2.5.1",
"@parcel/watcher-win32-arm64": "2.5.1",
"@parcel/watcher-win32-ia32": "2.5.1",
"@parcel/watcher-win32-x64": "2.5.1"
}
},
"node_modules/@parcel/watcher-android-arm64": {
"version": "2.5.1",
"resolved": "https://registry.npmmirror.com/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz",
"integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-darwin-arm64": {
"version": "2.5.1",
"resolved": "https://registry.npmmirror.com/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz",
"integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-darwin-x64": {
"version": "2.5.1",
"resolved": "https://registry.npmmirror.com/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz",
"integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-freebsd-x64": {
"version": "2.5.1",
"resolved": "https://registry.npmmirror.com/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz",
"integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-arm-glibc": {
"version": "2.5.1",
"resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz",
"integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-arm-musl": {
"version": "2.5.1",
"resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz",
"integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-arm64-glibc": {
"version": "2.5.1",
"resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz",
"integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-arm64-musl": {
"version": "2.5.1",
"resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz",
"integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-x64-glibc": {
"version": "2.5.1",
"resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz",
"integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-x64-musl": {
"version": "2.5.1",
"resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz",
"integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-win32-arm64": {
"version": "2.5.1",
"resolved": "https://registry.npmmirror.com/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz",
"integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-win32-ia32": {
"version": "2.5.1",
"resolved": "https://registry.npmmirror.com/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz",
"integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-win32-x64": {
"version": "2.5.1",
"resolved": "https://registry.npmmirror.com/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz",
"integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/braces": {
"version": "3.0.3",
"resolved": "https://registry.npmmirror.com/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"fill-range": "^7.1.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/chokidar": {
"version": "4.0.3",
"resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-4.0.3.tgz",
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
"dev": true,
"license": "MIT",
"dependencies": {
"readdirp": "^4.0.1"
},
"engines": {
"node": ">= 14.16.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/detect-libc": {
"version": "1.0.3",
"resolved": "https://registry.npmmirror.com/detect-libc/-/detect-libc-1.0.3.tgz",
"integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==",
"dev": true,
"license": "Apache-2.0",
"optional": true,
"bin": {
"detect-libc": "bin/detect-libc.js"
},
"engines": {
"node": ">=0.10"
}
},
"node_modules/fill-range": {
"version": "7.1.1",
"resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"to-regex-range": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/immutable": {
"version": "5.1.3",
"resolved": "https://registry.npmmirror.com/immutable/-/immutable-5.1.3.tgz",
"integrity": "sha512-+chQdDfvscSF1SJqv2gn4SRO2ZyS3xL3r7IW/wWEEzrzLisnOlKiQu5ytC/BVNcS15C39WT2Hg/bjKjDMcu+zg==",
"dev": true,
"license": "MIT"
},
"node_modules/is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
"dev": true,
"license": "MIT",
"optional": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-glob": {
"version": "4.0.3",
"resolved": "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"is-extglob": "^2.1.1"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true,
"license": "MIT",
"optional": true,
"engines": {
"node": ">=0.12.0"
}
},
"node_modules/micromatch": {
"version": "4.0.8",
"resolved": "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.8.tgz",
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"braces": "^3.0.3",
"picomatch": "^2.3.1"
},
"engines": {
"node": ">=8.6"
}
},
"node_modules/mitt": {
@@ -19,6 +449,135 @@
"integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==",
"license": "MIT"
},
"node_modules/neo-async": {
"version": "2.6.2",
"resolved": "https://registry.npmmirror.com/neo-async/-/neo-async-2.6.2.tgz",
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
"dev": true,
"license": "MIT"
},
"node_modules/node-addon-api": {
"version": "7.1.1",
"resolved": "https://registry.npmmirror.com/node-addon-api/-/node-addon-api-7.1.1.tgz",
"integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
"dev": true,
"license": "MIT",
"optional": true
},
"node_modules/picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"dev": true,
"license": "MIT",
"optional": true,
"engines": {
"node": ">=8.6"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/readdirp": {
"version": "4.1.2",
"resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-4.1.2.tgz",
"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 14.18.0"
},
"funding": {
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/sass": {
"version": "1.90.0",
"resolved": "https://registry.npmmirror.com/sass/-/sass-1.90.0.tgz",
"integrity": "sha512-9GUyuksjw70uNpb1MTYWsH9MQHOHY6kwfnkafC24+7aOMZn9+rVMBxRbLvw756mrBFbIsFg6Xw9IkR2Fnn3k+Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"chokidar": "^4.0.0",
"immutable": "^5.0.2",
"source-map-js": ">=0.6.2 <2.0.0"
},
"bin": {
"sass": "sass.js"
},
"engines": {
"node": ">=14.0.0"
},
"optionalDependencies": {
"@parcel/watcher": "^2.4.1"
}
},
"node_modules/sass-loader": {
"version": "16.0.5",
"resolved": "https://registry.npmmirror.com/sass-loader/-/sass-loader-16.0.5.tgz",
"integrity": "sha512-oL+CMBXrj6BZ/zOq4os+UECPL+bWqt6OAC6DWS8Ln8GZRcMDjlJ4JC3FBDuHJdYaFWIdKNIBYmtZtK2MaMkNIw==",
"dev": true,
"license": "MIT",
"dependencies": {
"neo-async": "^2.6.2"
},
"engines": {
"node": ">= 18.12.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
},
"peerDependencies": {
"@rspack/core": "0.x || 1.x",
"node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0",
"sass": "^1.3.0",
"sass-embedded": "*",
"webpack": "^5.0.0"
},
"peerDependenciesMeta": {
"@rspack/core": {
"optional": true
},
"node-sass": {
"optional": true
},
"sass": {
"optional": true
},
"sass-embedded": {
"optional": true
},
"webpack": {
"optional": true
}
}
},
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"is-number": "^7.0.0"
},
"engines": {
"node": ">=8.0"
}
},
"node_modules/uview-ui-next": {
"version": "0.0.1",
"resolved": "https://registry.npmmirror.com/uview-ui-next/-/uview-ui-next-0.0.1.tgz",
+4
View File
@@ -12,5 +12,9 @@
"dependencies": {
"mitt": "^3.0.1",
"uview-ui-next": "^0.0.1"
},
"devDependencies": {
"sass": "^1.90.0",
"sass-loader": "^16.0.5"
}
}
+2 -2
View File
@@ -146,7 +146,7 @@ export default {
display: flex;
gap: 20rpx;
/deep/ .u-button {
:deep(.u-button) {
margin: 0;
}
}
@@ -168,7 +168,7 @@ export default {
margin-top: 10rpx;
}
/deep/ .u-text {
:deep(.u-text) {
margin-bottom: 8rpx;
}
}
+4 -4
View File
@@ -252,7 +252,7 @@ export default {
flex-wrap: wrap;
gap: 20rpx;
/deep/ .u-button {
:deep(.u-button) {
margin: 0;
}
}
@@ -277,7 +277,7 @@ export default {
margin-right: 20rpx;
border-radius: 20rpx 20rpx 8rpx 20rpx;
/deep/ .u-text {
:deep(.u-text) {
color: white !important;
}
}
@@ -304,7 +304,7 @@ export default {
max-width: 70%;
padding: 20rpx;
/deep/ .u-text {
:deep(.u-text) {
line-height: 1.5;
word-break: break-word;
white-space: pre-wrap;
@@ -321,7 +321,7 @@ export default {
align-items: center;
gap: 20rpx;
/deep/ .u-input {
:deep(.u-input) {
flex: 1;
}
}
+1 -1
View File
@@ -203,7 +203,7 @@ export default {
padding: 40rpx 30rpx;
}
/deep/ .u-form-item {
:deep(.u-form-item) {
margin-bottom: 30rpx;
}
</style>
+2 -2
View File
@@ -208,7 +208,7 @@ export default {
border-radius: 10rpx;
}
/deep/ .u-icon {
:deep(.u-icon) {
position: absolute;
top: -8rpx;
right: -8rpx;
@@ -238,7 +238,7 @@ export default {
padding: 40rpx 30rpx;
}
/deep/ .u-form-item {
:deep(.u-form-item) {
margin-bottom: 30rpx;
}
</style>
+3 -3
View File
@@ -155,7 +155,7 @@ export default {
margin-left: 30rpx;
flex: 1;
/deep/ .u-text {
:deep(.u-text) {
margin-bottom: 8rpx;
}
}
@@ -170,7 +170,7 @@ export default {
display: flex;
flex-direction: column;
/deep/ .u-text:first-child {
:deep(.u-text:first-child) {
margin-bottom: 8rpx;
}
}
@@ -203,7 +203,7 @@ export default {
display: flex;
gap: 20rpx;
/deep/ .u-button {
:deep(.u-button) {
flex: 1;
}
}
+1 -1
View File
@@ -83,7 +83,7 @@ export default {
flex: 1;
margin-left: 20rpx;
/deep/ .u-text {
:deep(.u-text) {
margin-bottom: 8rpx;
}
}
@@ -0,0 +1,212 @@
# 宠物管家小程序开发助手 v2.0
## 身份设定
你是一位资深的uni-app全栈开发专家,专精宠物管理领域的小程序开发。拥有5年+宠物行业项目经验,深谙用户心理和业务痛点。你的特点是:
- 🎯 **需求敏感**:能快速识别用户真实需求背后的业务逻辑
- 💡 **方案务实**:提供的解决方案既考虑技术可行性又兼顾开发效率
- 🚀 **执行高效**:直接输出生产级代码,避免过度设计和无效沟通
- 🔧 **持续优化**:基于反馈快速迭代,追求最佳用户体验
## 技术能力矩阵
### 核心技术栈(精通级)
```javascript
{
"前端框架": "uni-app + Vue3 Composition API",
"UI组件库": "uView UI Next (已集成)",
"状态管理": "Pinia + 本地存储策略",
"工具函数": "uni.$u工具库 + 自定义utils",
"样式方案": "SCSS + uni.scss变量系统",
"数据持久化": "uni.setStorageSync + 数据版本管理"
}
```
### 业务领域专长
- **宠物档案系统**:多宠物管理、信息结构化存储、图片处理优化
- **记录追踪系统**:时间线展示、多媒体记录、数据统计分析
- **AI交互设计**:对话流程、上下文管理、智能推荐算法
- **社交功能架构**:家庭共享、权限控制、消息推送机制
- **领养业务流程**:信息发布、匹配算法、审核工作流
## 项目上下文理解
### 当前项目状态
```
✅ 已完成:基础架构 + Tab导航 + 宠物管理核心功能
🔄 进行中:功能完善和用户体验优化
📋 待开发:AI助手、领养专区、用户系统、高级功能
```
### 技术债务识别
- uView UI组件需要按需优化,避免包体积过大
- 图片上传和存储策略需要统一规范
- 数据结构需要考虑后续云端同步的兼容性
- 页面性能优化,特别是列表渲染和图片加载
### 用户画像深度分析
```javascript
const userProfiles = {
"核心用户": {
"年龄段": "25-40岁",
"特征": "有稳定收入,注重宠物健康,喜欢记录分享",
"痛点": "记录繁琐、信息分散、缺乏专业指导",
"期望": "简单易用、数据安全、功能实用"
},
"家庭成员": {
"角色": "配偶、父母、子女",
"需求": "共同参与宠物照料,信息同步",
"技术水平": "中等偏下,需要简化操作"
}
}
```
## 设计哲学与规范
### 视觉设计原则
```scss
// 色彩语义化系统
$pet-primary: #FF8A80; // 温暖珊瑚红 - 主要操作
$pet-secondary: #81C784; // 自然绿 - 健康相关
$pet-accent: #64B5F6; // 友好蓝 - 信息提示
$pet-warning: #FFB74D; // 温和橙 - 注意事项
$pet-success: #A5D6A7; // 清新绿 - 成功状态
$pet-neutral: #F5F5F5; // 中性灰 - 背景色
// 禁用色彩(避免AI味设计)
// ❌ 紫色渐变、霓虹色、过饱和色彩
// ❌ 玻璃拟态、过度阴影、复杂渐变
```
### 交互设计准则
- **一致性优先**:相同功能使用相同交互模式
- **反馈及时**:每个操作都有明确的视觉反馈
- **容错性强**:重要操作有确认机制,支持撤销
- **学习成本低**:符合用户直觉,减少认知负担
## 开发工作流
### 代码输出标准
```vue
<!-- 标准模板结构 -->
<template>
<view class="page-container">
<!-- 页面内容 -->
</view>
</template>
<script>
import { ref, reactive, onMounted } from 'vue'
export default {
name: 'PageName',
setup() {
// 响应式数据
const state = reactive({})
// 生命周期
onMounted(() => {
initPage()
})
// 方法定义
const initPage = () => {}
return {
state,
initPage
}
}
}
</script>
<style lang="scss" scoped>
.page-container {
// 页面样式
}
</style>
```
### 质量检查清单
- [ ] **功能完整性**:所有交互逻辑正确实现
- [ ] **数据安全性**:输入验证、异常处理完备
- [ ] **性能优化**:避免不必要的重渲染和内存泄漏
- [ ] **兼容性测试**:微信小程序环境下正常运行
- [ ] **用户体验**:加载状态、错误提示、空状态处理
### 分批交付策略
```
第一批:宠物档案管理增强版
├── 宠物信息编辑优化
├── 多图片上传和管理
├── 记录类型扩展
└── 数据导出功能
第二批:智能记账与统计
├── 消费记录管理
├── 图表统计展示
├── 预算提醒功能
└── 数据分析报告
第三批:家庭共享系统
├── 成员邀请机制
├── 权限管理系统
├── 消息通知功能
└── 数据同步策略
第四批:AI助手与领养
├── 智能对话系统
├── 知识库集成
├── 领养信息管理
└── 匹配推荐算法
```
## 响应模式
### 需求分析阶段
收到需求后,我会:
1. **业务理解**:分析功能背后的用户价值
2. **技术评估**:确认实现方案和技术难点
3. **方案设计**:提供最优的架构和实现路径
4. **风险识别**:预判可能的问题和解决方案
### 代码实现阶段
输出内容包括:
- **完整页面代码**:可直接运行的Vue文件
- **数据结构设计**:清晰的数据模型定义
- **工具函数**:复用性强的通用方法
- **使用说明**:集成步骤和注意事项
### 优化迭代阶段
基于反馈进行:
- **性能优化**:代码重构和性能提升
- **功能增强**:新需求的快速响应
- **bug修复**:问题定位和解决方案
- **体验改进**:用户体验的持续优化
## 沟通协议
### 输入格式期望
```
需求描述:[具体功能需求]
优先级:[高/中/低]
约束条件:[技术或业务限制]
参考示例:[如有相关参考]
```
### 输出格式承诺
```
方案概述:[实现思路和技术方案]
代码实现:[完整的可运行代码]
集成说明:[使用方法和注意事项]
后续建议:[优化方向和扩展可能]
```
## 价值承诺
我承诺提供的每一段代码都是:
- ✅ **即插即用**:无需修改即可在您的项目中运行
- ✅ **性能优化**:考虑了小程序环境的性能特点
- ✅ **用户友好**:符合宠物管理场景的用户习惯
- ✅ **可维护性**:代码结构清晰,便于后续扩展
让我们开始高效的开发协作吧!请告诉我您希望优先实现哪个功能模块。
+1
View File
@@ -77,3 +77,4 @@ $uni-font-size-paragraph:15px;
/* uView UI主题变量 */
@import "uview-ui-next/theme.scss";
@import '@/uni_modules/uview-next/theme.scss';
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 www.uviewui.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+76
View File
@@ -0,0 +1,76 @@
<div align="center">
<img alt="logo" src="http://uviewui.com/common/logo.png" width="120" height="120" style="margin-bottom: 10px;">
<h1 style="margin: 30px 0 30px;font-weight: bold;font-size:40px;">uView Next</h1>
<h3>企业级、高质量、更贴近业务场景的UI框架</h3>
</div>
[![stars](http://gitee.com/wakge/uview-next/badge/star.svg?theme=dark)](http://gitee.com/wakge/uview-next)
[![forks](http://gitee.com/wakge/uview-next/badge/fork.svg?theme=dark)](wakge/uview-next)
[![Stars](https://img.shields.io/github/stars/Yeloa/uview-next?logo=github&label=Stars&color=BA2127&labelColor=3E444F&style=flat&logoColor=white)](https://github.com/Yeloa/uview-next)
[![Forks](https://img.shields.io/github/forks/Yeloa/uview-next?logo=github&label=Forks&color=BA2127&labelColor=3E444F&style=flat&logoColor=white)](https://github.com/Yeloa/uview-next/fork)
[![Issues](https://img.shields.io/github/issues/Yeloa/uview-next?logo=github&label=Issues&color=BA2127&labelColor=3E444F&style=flat&logoColor=white)](https://github.com/Yeloa/uview-next/issues)
[![license](http://img.shields.io/badge/license-mit-red)](http://en.wikipedia.org/wiki/MIT_License)
## 📖 简介
uView Next 支持 Vue2 和 Vue3,支持国际化,是[uni-app](http://uniapp.dcloud.io/)全面兼容 nvue 的 uni-app 生态框架,提供了更丰富的组件和便捷的工具,让开发更得心应手。
## 📚 文档
- [🌐 **官方文档** https://uview-next.netlify.app](https://uview-next.netlify.app)
- [📄 **备用文档** https://uview.d3u.cn](https://uview.d3u.cn)
## 💬 交流反馈
欢迎加入交流群反馈:[点此跳转](https://uview.d3u.cn/components/addQQGroup.html)
## ✨ 特性
- 🌍 **全端兼容**:完美支持安卓、iOS、微信/QQ/百度/支付宝/头条小程序及H5
- 🚀 **原生渲染**:全面兼容nvue,性能媲美原生应用,流畅体验
- 🧩 **组件丰富**:80+ 高质量组件,覆盖各类业务场景,开箱即用
- 🔧 **工具集合**:实用JS工具库,简化开发流程,大幅提升开发效率
- 📖 **详尽文档**:清晰教程 + 交互演示,显著降低学习成本
- 📦 **按需引入**:智能按需加载,有效减小打包体积,优化性能
## 📱 预览
您可以通过**微信**扫码,查看最佳的演示效果。
<br>
<br>
<img width="200" height="200" src="https://uview.d3u.cn/common/h5_qrcode.png" alt=""/>
## 🔗 重要链接
- [🌐 官方文档](https://uview.d3u.cn/)
- [📝 更新日志](https://uview.d3u.cn/components/changelog.html)
- [👥 关于我们](https://uview.d3u.cn/cooperation/about.html)
## 🛠️ 关于 PR
> 我们非常乐意接受各位的优质 PR,但在此之前我希望您了解 uView Next是一个需要兼容多个平台的(小程序、h5、ios app、android app)包括 nvue 页面、vue 页面。
> 所以希望在您修复 bug 并提交之前尽可能的去这些平台测试一下兼容性。最好能携带测试截图以方便审核。非常感谢!
### 📦 快速安装
请通过[官网安装文档](https://uview.d3u.cn/components/downloadSetting.html)了解更详细的内容
### 🚀 快速上手
请通过[快速上手](https://uview.d3u.cn/components/quickstart.html)了解更详细的内容
## 💡 使用方法
配置 easycom 规则后,自动按需引入,无需`import`组件,直接引用即可。
```html
<template>
<u-button text="按钮"></u-button>
</template>
```
## 📄 版权信息
uView Next遵循[MIT](http://en.wikipedia.org/wiki/MIT_License)开源协议,意味着您无需支付任何费用,也无需授权,即可将 uView 应用到您的产品中。
+675
View File
@@ -0,0 +1,675 @@
## 3.4.8(2025-08-12)
1. 新增`subsection`组件`disabled`,`disabledBgColor `,`disabledColor `属性,优化组件
## 3.4.7(2025-08-11)
1. 修复一些已知问题
## 3.4.6(2025-08-11)
1. 修复`form`组件校验为空的问题
## 3.4.5(2025-08-11)
1. 新增`sidebar`侧边栏组件
## 3.4.4(2025-08-10)
1. 修复`form`组件`resetFields`方法vue2无效
## 3.4.3(2025-08-09)
1. 修复`barcode`组件异常报错
## 3.4.2(2025-08-09)
1. 新增`actionSheet`组件`height`属性,超出高度自动滚动
2. 新增`dateStrip`组件`padZero`,`disabledFun`属性,优化组件
## 3.4.1(2025-08-08)
1. 优化`dateStrip`日期横条组件
## 3.4.0(2025-08-08)
1. 新增`dateStrip`日期横条组件
2. 优化`dropdown`组件
3. 优化`table`组件
## 3.3.9(2025-08-07)
1. 新增`pdf`组件可预览pdf文件
2. 新增`table`组件`headerAlign`,`headerEllipsis`等属性
3. 新增`upload`组件`extension`属性
## 3.3.8(2025-08-05)
1. 新增`select`下拉列表组件`keyName`和`valueName`属性
## 3.3.7(2025-08-05)
1. 优化控制台输出
## 3.3.6(2025-08-04)
1. 全面适配鸿蒙系统
2. 其他一些细节优化
## 3.3.5(2025-08-04)
1. 新增`barcode`条码组件
2. 导出http等方法
## 3.3.4(2025-08-02)
1. 新增`select`下拉列表组件
## 3.3.3(2025-08-01)
1. 优化`signature`组件
## 3.3.2(2025-07-29)
1. 修复`icon`组件使用图片时支付宝显示不全
2. 修复`picker`组件支付宝传递this引发的报错
3. 修复一些组件由于`canvas`问题引起的支付宝报错
4. 修复`calendar`组件, `swipe-action-item`组件抖音小程序报错
5. 修复`swipe-action-item`组件不支持支付宝小程序的问题
6. 其他一些细节优化
## 3.3.1(2025-07-28)
1. 新增`u-divider` 组件插槽模式
2. 新增`form-item`组件`rules`属性
3. 修复`form`组件异步校验异常等问题
4. 优化`number-box`组件
5. 其他一些细节优化
## 3.3.0(2025-07-25)
1. 新增`button`组件新增`iconPosition`属性
## 3.2.9(2025-07-24)
1. 修复`I18n国际化`语言包
## 3.2.8(2025-07-24)
1. 优化`$u.os()`方法,去除小程序下的警告
2. 优化`areaPicker`组件input相关属性
3. 优化`datetimePicker`组件input相关属性,以及小程序new Date()再ios设备无效的警告
4. 优化一些组件在小程序下各种警告
5. 修复`overlay`由组件引发的一些异常
6. 修复`cropper`组件方法报错,以及缺失showGrid属性
7. 其他一些细节优化
## 3.2.7(2025-07-21)
1. 新增`draggable`拖拽排序组件
2. 修复`popover`组件边界计算不准确的问题
3. 修复几处语法错误导致的小程序无法上传
4. 优化`pagination`组件
5. 优化`card`组件
## 3.2.6(2025-07-18)
1. 新增`I18n国际化`功能,内置8种语言切换
2. 其他一些细节优化
## 3.2.5(2025-07-17)
1. 新增`datetimePicker`组件新增showInput属性
2. 新增`areaPicker`组件新增showInput属性
3. 修复`calendar`组件`readonly`属性错误
## 3.2.4(2025-07-16)
1. 新增`Ellipsis 文本省略`组件
2. 修复`Button`组件主题色问题
## 3.2.3(2025-07-15)
1. 新增`Fab 悬浮按钮`组件
2. 新增`upload`组件 `round` 属性,可设置圆角数值
3. 其他一些细节优化
## 3.2.2(2025-07-14)
更新文档
## 3.2.1(2025-07-14)
1. 新增`Cascader 级联选择器`组件
2. 新增`Tabs`组件使用`u-tab-item`子组件定义方式
3. 新增`Radio`组件value支持布尔值
## 3.2.0(2025-07-12)
1. 修复微信小程序 `{"es6" : false} ` 引发的报错
## 3.1.9(2025-07-11)
1. 优化示例
## 3.1.8(2025-07-11)
1. 新增`pagination`组件
## 3.1.7(2025-07-10)
1. 新增`popover`组件
## 3.1.6(2025-07-08)
1. 修复`scroll-list`组件微信小程序编译后会有警告
2. 修复`cell`组件required属性不生效的问题
3. 修复`tabs`组件index小于0时自动设置为0
4. 修复`keyboard`组件名称冲突的问题
5. 修复`datetime-picker`组件日期判断不准确的问题
6. 删除`theme.scss`中的flex
7. 其他一些修复
## 3.1.5(2025-07-07)
1. 新增`button`组件 `round` 属性,可设置圆角数值
2. 新增`modal`组件 `buttonModel` 属性,可以设置按钮样式
## 3.1.4(2025-07-06)
1. 修复vue2和vue3兼容性
2. 其他一些修复
## 3.1.3(2025-07-02)
1. 修复 vue2 引入报错
2. 新增`tag`组件增加默认 slot
3. 其他一些修复
## 3.1.2(2025-07-01)
1. 修复`tab`组件文字换行
2. 修复`readMore`组件兼容性
3. 其他一些修复
## 3.1.1(2025-07-01)
1. 新增`calendar`组件新增 shape 属性
## 3.1.0(2025-06-30)
1. 新增`calendar`组件新增日期是否被禁用的属性
2. 其他一些修复
## 3.0.9(2025-06-30)
1. 优化`$u.toast`支持关闭后回调
## 3.0.8(2025-06-30)
1. 优化`parse`组件支持解析音频
2. 新增`calendar`组件新增插入模式
3. 新增`markdown`组件用于 markdown 文档
4. 其他一些修复
## 3.0.7(2025-06-29)
1. 修改`keyboard`组件键盘自定义样式无效的问题
2. 修复`number-box`组件解决输入数字的问题,同时解决小数无法输入的问题
3. 修复`claendar`组件选择相同日期重新打开滚动定位错误问题
4. 修复`form-item`组件 marginBottom 取值问题
5. 修复`textarea`组件字数统计不限制时,会出现/-1
6. 修复`input`组件在 h5 平台设置 readonly 时 click 事件失效的问题
7. 新增`swiper-action-item`组件的 name 属性可以支持传递对象
8. 新增`input`组件增加 always-embed 属性
9. 新增`code-input`组件输入框新增 type 属性
10. 新增 test.image 函数支持判断 base64 格式的图片
## 3.0.6(2025-06-28)
1. 修复`search`在 h5 环境下的点击失效
2. 修复`number-box` focus 问题
3. 修复倒计时组件样例代码的运行错误
4. 修复`index-list`设置 customNavHeight 值问题
5. 修复`upload`组件视频无法预览问题
6. 修复`datetime-picker` 首次点击 confirm 回调无法获取最新值的问题,新增年,月选择
7. 修复`textarea`安卓微信实际输入字数超过限制问题,placeholderClass 和 placeholderStyle 不生效
## 3.0.5(2025-06-27)
1. search 组件新增右侧插槽
## 3.0.4(2025-06-27)
1. 优化 cell 组件示例
## 3.0.3(2025-06-26)
1. 优化懒加载
## 3.0.0
1. 兼容 vue3
2. 新增组件
3. 修改官方 github 大量 bug
## 2.0.38(2024-10-29)
# uView2.0 重磅发布,利剑出鞘,一统江湖
1. 修复`picker`组件和`datetime-picker`组件的头条小程序循环引用问题
2. 修复`image`组件的`bgColor`属性不生效问题
3. 修复`textarea`组件部分回调事件参数丢失问题
4. 修复`input`组件部分回调事件参数丢失问题
5. `datetime-picker`组件支持`immediateChange`属性
6. 修复`datetime-picker`组件`v-model`绑定异步设置无效问题
7. 修复`popup`组件设置`duration`属性为字符串值时,遮罩的打开、关闭很缓慢问题
8. 修复`radio`参数`iconColor`属性不生效问题
9. 修复`tooltip`组件`size`属性不生效的问题
10. 修复`image`组件当`src`为空时,loading 状态问题
11. 其他一些修复
## 2.0.37(2024-03-17)
# uView2.0 重磅发布,利剑出鞘,一统江湖
1. 修复表单校验`trigger`触发器参数无效问题
2. 修复`u-input`组件的`password`属性在动态切换为`false`时失效的问题
3. 添加微信小程序用户同意隐私协议事件回调
4. 修复支付宝小程序 picker 样式问题
5. `u-modal`添加`duration`字段控制动画过度时间
6. 修复`picker` `lastIndex`异常导致的`column`异常问题
7. `tabs`增加长按事件支持
8. 修复`u-avatar` `square`属性在小程序`open-data`下无效问题
9. 其他一些修复
## 2.0.36(2023-03-27)
# uView2.0 重磅发布,利剑出鞘,一统江湖
1. 重构`deepClone` & `deepMerge`方法
2. 其他优化
## 2.0.34(2022-09-24)
# uView2.0 重磅发布,利剑出鞘,一统江湖
1. `u-input`、`u-textarea`增加`ignoreCompositionEvent`属性
2. 修复`route`方法调用可能报错的问题
3. 修复`u-no-network`组件`z-index`无效的问题
4. 修复`textarea`组件在 h5 上 confirmType=""报错的问题
5. `u-rate`适配`nvue`
6. 优化验证手机号码的正则表达式(根据工信部发布的《电信网编号计划(2017 年版)》进行修改。)
7. `form-item`添加`labelPosition`属性
8. `u-calendar`修复`maxDate`设置为当前日期,并且当前时间大于 08:00 时无法显示日期列表的问题 (#724)
9. `u-radio`增加一个默认插槽用于自定义修改 label 内容 (#680)
10. 修复`timeFormat`函数在 safari 重的兼容性问题 (#664)
## 2.0.33(2022-06-17)
# uView2.0 重磅发布,利剑出鞘,一统江湖
1. 修复`loadmore`组件`lineColor`类型错误问题
2. 修复`u-parse`组件`imgtap`、`linktap`不生效问题
## 2.0.32(2022-06-16)
# uView2.0 重磅发布,利剑出鞘,一统江湖
1. `u-loadmore`新增自定义颜色、虚/实线
2. 修复`u-swiper-action`组件部分平台不能上下滑动的问题
3. 修复`u-list`回弹问题
4. 修复`notice-bar`组件动画在低端安卓机可能会抖动的问题
5. `u-loading-page`添加控制图标大小的属性`iconSize`
6. 修复`u-tooltip`组件`color`参数不生效的问题
7. 修复`u--input`组件使用`blur`事件输出为`undefined`的 bug
8. `u-code-input`组件新增键盘弹起时,是否自动上推页面参数`adjustPosition`
9. 修复`image`组件`load`事件无回调对象问题
10. 修复`button`组件`loadingSize`设置无效问题
11. 其他修复
## 2.0.31(2022-04-19)
# uView2.0 重磅发布,利剑出鞘,一统江湖
1. 修复`upload`在`vue`页面上传成功后没有成功标志的问题
2. 解决演示项目中微信小程序模拟上传图片一直出于上传中问题
3. 修复`u-code-input`组件在`nvue`页面编译到`app`平台上光标异常问题(`app`去除此功能)
4. 修复`actionSheet`组件标题关闭按钮点击事件名称错误的问题
5. 其他修复
## 2.0.30(2022-04-04)
# uView2.0 重磅发布,利剑出鞘,一统江湖
1. `u-rate`增加`readonly`属性
2. `tabs`滑块支持设置背景图片
3. 修复`u-subsection` `mode`为`subsection`时,滑块样式不正确的问题
4. `u-code-input`添加光标效果动画
5. 修复`popup`的`open`事件不触发
6. 修复`u-flex-column`无效的问题
7. 修复`u-datetime-picker`索引在特定场合异常问题
8. 修复`u-datetime-picker`最小时间字符串模板错误问题
9. `u-swiper`添加`m3u8`验证
10. `u-swiper`修改判断 image 和 video 逻辑
11. 修复`swiper`无法使用本地图片问题,增加`type`参数
12. 修复`u-row-notice`格式错误问题
13. 修复`u-switch`组件当`unit`为`rpx`时,`nodeStyle`消失的问题
14. 修复`datetime-picker`组件`showToolbar`与`visibleItemCount`属性无效的问题
15. 修复`upload`组件条件编译位置判断错误,导致`previewImage`属性设置为`false`时,整个组件都会被隐藏的问题
16. 修复`u-checkbox-group`设置`shape`属性无效的问题
17. 修复`u-upload`的`capture`传入字符串的时候不生效的问题
18. 修复`u-action-sheet`组件,关闭事件逻辑错误的问题
19. 修复`u-list`触顶事件的触发错误的问题
20. 修复`u-text`只有手机号可拨打的问题
21. 修复`u-textarea`不能换行的问题
22. 其他修复
## 2.0.29(2022-03-13)
# uView2.0 重磅发布,利剑出鞘,一统江湖
1. 修复`u--text`组件设置`decoration`属性未生效的问题
2. 修复`u-datetime-picker`使用`formatter`后返回值不正确
3. 修复`u-datetime-picker` `intercept` 可能为 undefined
4. 修复已设置单位 uni..config.unit = 'rpx'时,线型指示器 `transform` 的位置翻倍,导致指示器超出宽度
5. 修复 mixin 中 bem 方法生成的类名在支付宝和字节小程序中失效
6. 修复默认值传值为空的时候,打开`u-datetime-picker`报错,不能选中第一列时间的 bug
7. 修复`u-datetime-picker`使用`formatter`后返回值不正确
8. 修复`u-image`组件`loading`无效果的问题
9. 修复`config.unit`属性设为`rpx`时,导航栏占用高度不足导致塌陷的问题
10. 修复`u-datetime-picker`组件`itemHeight`无效问题
11. 其他修复
## 2.0.28(2022-02-22)
# uView2.0 重磅发布,利剑出鞘,一统江湖
1. search 组件新增 searchIconSize 属性
2. 兼容 Safari/Webkit 中传入时间格式如 2022-02-17 12:00:56
3. 修复 text value.js 判断日期出 format 错误问题
4. priceFormat 格式化金额出现精度错误
5. priceFormat 在部分情况下出现精度损失问题
6. 优化表单 rules 提示
7. 修复 avatar 组件 src 为空时,展示状态不对
8. 其他修复
## 2.0.27(2022-01-28)
# uView2.0 重磅发布,利剑出鞘,一统江湖
1.样式修复
## 2.0.26(2022-01-28)
# uView2.0 重磅发布,利剑出鞘,一统江湖
1.样式修复
## 2.0.25(2022-01-27)
# uView2.0 重磅发布,利剑出鞘,一统江湖
1. 修复 text 组件 mode=price 时,可能会导致精度错误的问题
2. 添加$u.setConfig()方法,可设置 uView 内置的 config, props, zIndex, color 属性,详见:[修改 uView 内置配置方案](https://uviewui.com/components/setting.html#%E9%BB%98%E8%AE%A4%E5%8D%95%E4%BD%8D%E9%85%8D%E7%BD%AE)
3. 优化 form 组件在 errorType=toast 时,如果输入错误页面会有抖动的问题
4. 修复$u.addUnit()对配置默认单位可能无效的问题
## 2.0.24(2022-01-25)
# uView2.0 重磅发布,利剑出鞘,一统江湖
1. 修复 swiper 在 current 指定非 0 时缩放有误
2. 修复 u-icon 添加 stop 属性的时候报错
3. 优化遗留的通过正则判断 rpx 单位的问题
4. 优化 Layout 布局 vue 使用 gutter 时,会超出固定区域
5. 优化 search 组件高度单位问题(rpx -> px)
6. 修复 u-image slot 加载和错误的图片失去了高度
7. 修复 u-index-list 中 footer 插槽与 header 插槽存在性判断错误
8. 修复部分机型下 u-popup 关闭时会闪烁
9. 修复 u-image 在 nvue-app 下失去宽高
10. 修复 u-popup 运行报错
11. 修复 u-tooltip 报错
12. 修复 box-sizing 在 app 下的警告
13. 修复 u-navbar 在小程序中报运行时错误
14. 其他修复
## 2.0.23(2022-01-24)
# uView2.0 重磅发布,利剑出鞘,一统江湖
1. 修复 image 组件在 hx3.3.9 的 nvue 下可能会显示异常的问题
2. 修复 col 组件 gutter 参数带 rpx 单位处理不正确的问题
3. 修复 text 组件单行时无法显示省略号的问题
4. navbar 添加 titleStyle 参数
5. 升级到 hx3.3.9 可消除 nvue 下控制台样式警告的问题
## 2.0.22(2022-01-19)
# uView2.0 重磅发布,利剑出鞘,一统江湖
1. $u.page()方法优化,避免在特殊场景可能报错的问题
2. picker 组件添加 immediateChange 参数
3. 新增$u.pages()方法
## 2.0.21(2022-01-19)
# uView2.0 重磅发布,利剑出鞘,一统江湖
1. 优化:form 组件在用户设置 rules 的时候提示用户 model 必传
2. 优化遗留的通过正则判断 rpx 单位的问题
3. 修复微信小程序环境中 tabbar 组件开启 safeAreaInsetBottom 属性后,placeholder 高度填充不正确
4. 修复 swiper 在 current 指定非 0 时缩放有误
5. 修复 u-icon 添加 stop 属性的时候报错
6. 修复 upload 组件在 accept=all 的时候没有作用
7. 修复在 text 组件 mode 为 phone 时 call 属性无效的问题
8. 处理 u-form clearValidate 方法
9. 其他修复
## 2.0.20(2022-01-14)
# uView2.0 重磅发布,利剑出鞘,一统江湖
1. 修复 calendar 默认会选择一个日期,如果直接点确定的话,无法取到值的问题
2. 修复 Slider 缺少 disabled props 还有注释
3. 修复 u-notice-bar 点击事件无法拿到 index 索引值的问题
4. 修复 u-collapse-item 在 vue 文件下,app 端自定义插槽不生效的问题
5. 优化头像为空时显示默认头像
6. 修复图片地址赋值后判断加载状态为完成问题
7. 修复日历滚动到默认日期月份区域
8. search 组件暴露点击左边 icon 事件
9. 修复 u-form clearValidate 方法不生效
10. upload h5 端增加返回文件参数(文件的 name 参数)
11. 处理 upload 选择文件后 url 为 blob 类型无法预览的问题
12. u-code-input 修复输入框没有往左移出一半屏幕
13. 修复 Upload 上传 disabled 为 true 时,控制台报 hoverClass 类型错误
14. 临时处理 ios app 下 grid 点击坍塌问题
15. 其他修复
## 2.0.19(2021-12-29)
# uView2.0 重磅发布,利剑出鞘,一统江湖
1. 优化微信小程序包体积可在微信中预览,请升级 HbuilderX3.3.4,同时在“运行->运行到小程序模拟器”中勾选“运行时是否压缩代码”
2. 优化微信小程序 setData 性能,处理某些方法如$u.route()无法在模板中使用的问题
3. navbar 添加 autoBack 参数
4. 允许 avatar 组件的事件冒泡
5. 修复 cell 组件报错问题
6. 其他修复
## 2.0.18(2021-12-28)
# uView2.0 重磅发布,利剑出鞘,一统江湖
1. 修复 app 端编译报错问题
2. 重新处理微信小程序端 setData 过大的性能问题
3. 修复边框问题
4. 修复最大最小月份不大于 0 则没有数据出现的问题
5. 修复 SwipeAction 微信小程序端无法上下滑动问题
6. 修复 input 的 placeholder 在小程序端默认显示为 true 问题
7. 修复 divider 组件 click 事件无效问题
8. 修复 u-code-input maxlength 属性值为 String 类型时显示异常
9. 修复当 grid 只有 1 到 2 时 在小程序端 algin 设置无效的问题
10. 处理 form-item 的 label 为 top 时,取消错误提示的左边距
11. 其他修复
## 2.0.17(2021-12-26)
## uView 正在参与开源中国的“年度最佳项目”评选,之前投过票的现在也可以投票,恳请同学们投一票,[点此帮助 uView](https://www.oschina.net/project/top_cn_2021/?id=583)
# uView2.0 重磅发布,利剑出鞘,一统江湖
1. 解决 HBuilderX3.3.3.20211225 版本导致的样式问题
2. calendar 日历添加 monthNum 参数
3. navbar 添加 center slot
## 2.0.16(2021-12-25)
## uView 正在参与开源中国的“年度最佳项目”评选,之前投过票的现在也可以投票,恳请同学们投一票,[点此帮助 uView](https://www.oschina.net/project/top_cn_2021/?id=583)
# uView2.0 重磅发布,利剑出鞘,一统江湖
1. 解决微信小程序 setData 性能问题
2. 修复 count-down 组件 change 事件不触发问题
## 2.0.15(2021-12-21)
## uView 正在参与开源中国的“年度最佳项目”评选,之前投过票的现在也可以投票,恳请同学们投一票,[点此帮助 uView](https://www.oschina.net/project/top_cn_2021/?id=583)
# uView2.0 重磅发布,利剑出鞘,一统江湖
1. 修复 Cell 单元格 titleWidth 无效
2. 修复 cheakbox 组件 ischecked 不更新
3. 修复 keyboard 是否显示"."按键默认值问题
4. 修复 number-keyboard 是否显示键盘的"."符号问题
5. 修复 Input 输入框 readonly 无效
6. 修复 u-avatar 导致打包 app、H5 时候报错问题
7. 修复 Upload 上传 deletable 无效
8. 修复 upload 当设置 maxSize 时无效的问题
9. 修复 tabs lineWidth 传入带单位的字符串的时候偏移量计算错误问题
10. 修复 rate 组件在有 padding 的 view 内,显示的星星位置和可触摸区域不匹配,无法正常选中星星
## 2.0.13(2021-12-14)
## [点击加群交流反馈:364463526](https://jq.qq.com/?_chanwv=1027&k=mCxS3TGY)
# uView2.0 重磅发布,利剑出鞘,一统江湖
1. 修复配置默认单位为 rpx 可能会导致自定义导航栏高度异常的问题
## 2.0.12(2021-12-14)
## [点击加群交流反馈:364463526](https://jq.qq.com/?_chanwv=1027&k=mCxS3TGY)
# uView2.0 重磅发布,利剑出鞘,一统江湖
1. 修复 tabs 组件在 vue 环境下划线消失的问题
2. 修复 upload 组件在安卓小程序无法选择视频的问题
3. 添加 uni.$u.config.unit 配置,用于配置参数默认单位,详见:[默认单位配置](https://uview.d3u.cn/components/setting.html#%E9%BB%98%E8%AE%A4%E5%8D%95%E4%BD%8D%E9%85%8D%E7%BD%AE)
4. 修复 textarea 组件在没绑定 v-model 时,字符统计不生效问题
5. 修复 nvue 下控制是否出现滚动条失效问题
## 2.0.11(2021-12-13)
## [点击加群交流反馈:364463526](https://jq.qq.com/?_chanwv=1027&k=mCxS3TGY)
# uView2.0 重磅发布,利剑出鞘,一统江湖
1. text 组件 align 参数无效的问题
2. subsection 组件添加 keyName 参数
3. upload 组件无法判断[Object file]类型的问题
4. 处理 notify 层级过低问题
5. codeInput 组件添加 disabledDot 参数
6. 处理 actionSheet 组件 round 参数无效的问题
7. calendar 组件添加 round 参数用于控制圆角值
8. 处理 swipeAction 组件在 vue 环境下默认被打开的问题
9. button 组件的 throttleTime 节流参数无效的问题
10. 解决 u-notify 手动关闭方法 close()无效的问题
11. input 组件 readonly 不生效问题
12. tag 组件 type 参数为 info 不生效问题
## 2.0.10(2021-12-08)
## [点击加群交流反馈:364463526](https://jq.qq.com/?_chanwv=1027&k=mCxS3TGY)
# uView2.0 重磅发布,利剑出鞘,一统江湖
1. 修复 button sendMessagePath 属性不生效
2. 修复 DatetimePicker 选择器 title 无效
3. 修复 u-toast 设置 loading=true 不生效
4. 修复 u-text 金额模式传 0 报错
5. 修复 u-toast 组件的 icon 属性配置不生效
6. button 的 icon 在特殊场景下的颜色优化
7. IndexList 优化,增加#
## 2.0.9(2021-12-01)
## [点击加群交流反馈:232041042](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
# uView2.0 重磅发布,利剑出鞘,一统江湖
1. 优化 swiper 的 height 支持 100%值(仅 vue 有效),修复嵌入视频时 click 事件无法触发的问题
2. 优化 tabs 组件对 list 值为空的判断,或者动态变化 list 时重新计算相关尺寸的问题
3. 优化 datetime-picker 组件逻辑,让其后续打开的默认值为上一次的选中值,需要通过 v-model 绑定值才有效
4. 修复 upload 内嵌在其他组件中,选择图片可能不会换行的问题
## 2.0.8(2021-12-01)
## [点击加群交流反馈:232041042](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
# uView2.0 重磅发布,利剑出鞘,一统江湖
1. 修复 toast 的 position 参数无效问题
2. 处理 input 在 ios nvue 上无法获得焦点的问题
3. avatar-group 组件添加 extraValue 参数,让剩余展示数量可手动控制
4. tabs 组件添加 keyName 参数用于配置从对象中读取的键名
5. 处理 text 组件名字脱敏默认配置无效的问题
6. 处理 picker 组件 item 文本太长换行问题
## 2.0.7(2021-11-30)
## [点击加群交流反馈:232041042](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
# uView2.0 重磅发布,利剑出鞘,一统江湖
1. 修复 radio 和 checkbox 动态改变 v-model 无效的问题。
2. 优化 form 规则 validator 在微信小程序用法
3. 修复 backtop 组件 mode 参数在微信小程序无效的问题
4. 处理 Album 的 previewFullImage 属性无效的问题
5. 处理 u-datetime-picker 组件 mode='time'在选择改变时间时,控制台报错的问题
## 2.0.6(2021-11-27)
## [点击加群交流反馈:232041042](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
# uView2.0 重磅发布,利剑出鞘,一统江湖
1. 处理 tag 组件在 vue 下边框无效的问题。
2. 处理 popup 组件圆角参数可能无效的问题。
3. 处理 tabs 组件 lineColor 参数可能无效的问题。
4. propgress 组件在值很小时,显示异常的问题。
## 2.0.5(2021-11-25)
## [点击加群交流反馈:232041042](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
# uView2.0 重磅发布,利剑出鞘,一统江湖
1. calendar 在 vue 下显示异常问题。
2. form 组件 labelPosition 和 errorType 参数无效的问题
3. input 组件 inputAlign 无效的问题
4. 其他一些修复
## 2.0.4(2021-11-23)
## [点击加群交流反馈:232041042](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
# uView2.0 重磅发布,利剑出鞘,一统江湖
0. input 组件缺失@confirm 事件,以及 subfix 和 prefix 无效问题
1. component.scss 文件样式在 vue 下干扰全局布局问题
2. 修复 subsection 在 vue 环境下表现异常的问题
3. tag 组件的 bgColor 等参数无效的问题
4. upload 组件不换行的问题
5. 其他的一些修复处理
## 2.0.3(2021-11-16)
## [点击加群交流反馈:1129077272](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
# uView2.0 重磅发布,利剑出鞘,一统江湖
1. uView2.0 已实现全面兼容 nvue
2. uView2.0 对 1.x 进行了架构重构,细节和性能都有极大提升
3. 目前 uView2.0 为公测阶段,相关细节可能会有变动
4. 我们写了一份与 1.x 的对比指南,详见[对比 1.x](https://uview.d3u.cn/components/diff1.x.html)
5. 处理 modal 的 confirm 回调事件拼写错误问题
6. 处理 input 组件@input 事件参数错误问题
7. 其他一些修复
## 2.0.2(2021-11-16)
## [点击加群交流反馈:1129077272](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
# uView2.0 重磅发布,利剑出鞘,一统江湖
1. uView2.0 已实现全面兼容 nvue
2. uView2.0 对 1.x 进行了架构重构,细节和性能都有极大提升
3. 目前 uView2.0 为公测阶段,相关细节可能会有变动
4. 我们写了一份与 1.x 的对比指南,详见[对比 1.x](https://uview.d3u.cn/components/diff1.x.html)
5. 修复 input 组件 formatter 参数缺失问题
6. 优化 loading-icon 组件的 scss 写法问题,防止不兼容新版本 scss
## 2.0.0(2020-11-15)
## [点击加群交流反馈:1129077272](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
# uView2.0 重磅发布,利剑出鞘,一统江湖
1. uView2.0 已实现全面兼容 nvue
2. uView2.0 对 1.x 进行了架构重构,细节和性能都有极大提升
3. 目前 uView2.0 为公测阶段,相关细节可能会有变动
4. 我们写了一份与 1.x 的对比指南,详见[对比 1.x](https://uview.d3u.cn/components/diff1.x.html)
5. 修复 input 组件 formatter 参数缺失问题
@@ -0,0 +1,95 @@
<template>
<uvForm
ref="uForm"
:model="model"
@update:model="$emit('update:model', $event)"
:rules="rules"
:errorType="errorType"
:borderBottom="borderBottom"
:labelPosition="labelPosition"
:labelWidth="labelWidth"
:labelAlign="labelAlign"
:labelStyle="labelStyle"
:customStyle="customStyle"
>
<slot />
</uvForm>
</template>
<script>
/**
* 此组件存在的理由是,在nvue下,u-form被uni-app官方占用了,u-form在nvue中相当于form组件
* 所以在nvue下,取名为u--form,内部其实还是u-form.vue,只不过做一层中转
*/
import uvForm from '../u-form/u-form.vue';
import props from '../u-form/props.js'
import mixin from '../../libs/mixin/mixin'
import mpMixin from '../../libs/mixin/mpMixin'
/**
* Form 表单
* @description 此组件一般用于表单场景,可以配置Input输入框,Select弹出框,进行表单验证等。
* @tutorial https://uview.d3u.cn/components/form.html
* @property {Object} model 当前form的需要验证字段的集合
* @property {Object | Function | Array} rules 验证规则
* @property {String} errorType 错误的提示方式,见上方说明 ( 默认 message )
* @property {Boolean} borderBottom 是否显示表单域的下划线边框 ( 默认 true )
* @property {String} labelPosition 表单域提示文字的位置,left-左侧,top-上方 ( 默认 'left' )
* @property {String | Number} labelWidth 提示文字的宽度,单位px ( 默认 45 )
* @property {String} labelAlign lable字体的对齐方式 ( 默认 ‘left' )
* @property {Object} labelStyle lable的样式,对象形式
* @example <u--formlabelPosition="left" :model="model1" :rules="rules" ref="form1"></u--form>
*/
export default {
// #ifdef MP-WEIXIN
name: 'u-form',
// #endif
// #ifndef MP-WEIXIN
name: 'u--form',
// #endif
mixins: [mpMixin, mixin, props],
components: {
uvForm
},
created() {
this.children = []
},
methods: {
// 手动设置校验的规则,如果规则中有函数的话,微信小程序中会过滤掉,所以只能手动调用设置规则
setRules(rules) {
this.$refs.uForm.setRules(rules)
},
validate() {
/**
* 在微信小程序中,通过this.$parent拿到的父组件是u--form,而不是其内嵌的u-form
* 导致在u-form组件中,拿不到对应的children数组,从而校验无效,所以这里每次调用u-form组件中的
* 对应方法的时候,在小程序中都先将u--form的children赋值给u-form中的children
*/
// #ifdef MP-WEIXIN
this.setMpData()
// #endif
return this.$refs.uForm.validate()
},
validateField(value, callback, event) {
// #ifdef MP-WEIXIN
this.setMpData()
// #endif
return this.$refs.uForm.validateField(value, callback, event)
},
resetFields() {
// #ifdef MP-WEIXIN
this.setMpData()
// #endif
return this.$refs.uForm.resetFields()
},
clearValidate(props) {
// #ifdef MP-WEIXIN
this.setMpData()
// #endif
return this.$refs.uForm.clearValidate(props)
},
setMpData() {
this.$refs.uForm.children = this.children
}
},
}
</script>
@@ -0,0 +1,75 @@
<template>
<uvImage
:src="src"
:mode="mode"
:width="width"
:height="height"
:shape="shape"
:round="round"
:lazyLoad="lazyLoad"
:showMenuByLongpress="showMenuByLongpress"
:loadingIcon="loadingIcon"
:errorIcon="errorIcon"
:showLoading="showLoading"
:showError="showError"
:fade="fade"
:webp="webp"
:duration="duration"
:bgColor="bgColor"
:customStyle="customStyle"
@click="$emit('click')"
@error="$emit('error')"
@load="$emit('load')"
>
<template v-slot:loading>
<slot name="loading"></slot>
</template>
<template v-slot:error>
<slot name="error"></slot>
</template>
</uvImage>
</template>
<script>
/**
* 此组件存在的理由是,在nvue下,u-image被uni-app官方占用了,u-image在nvue中相当于image组件
* 所以在nvue下,取名为u--image,内部其实还是u-iamge.vue,只不过做一层中转
*/
import uvImage from '../u-image/u-image.vue';
import props from '../u-image/props.js';
import mixin from '../../libs/mixin/mixin'
import mpMixin from '../../libs/mixin/mpMixin'
/**
* Image 图片
* @description 此组件为uni-app的image组件的加强版,在继承了原有功能外,还支持淡入动画、加载中、加载失败提示、圆角值和形状等。
* @tutorial https://uviewui.com/components/image.html
* @property {String} src 图片地址
* @property {String} mode 裁剪模式,见官网说明 (默认 'aspectFill' )
* @property {String | Number} width 宽度,单位任意,如果为数值,则为px单位 (默认 '300' )
* @property {String | Number} height 高度,单位任意,如果为数值,则为px单位 (默认 '225' )
* @property {String} shape 图片形状,circle-圆形,square-方形 (默认 'square' )
* @property {String | Number} round 圆角值,单位任意,如果为数值,则为px单位 (默认 0 )
* @property {Boolean} lazyLoad 是否懒加载,仅微信小程序、App、百度小程序、字节跳动小程序有效 (默认 true )
* @property {Boolean} showMenuByLongpress 是否开启长按图片显示识别小程序码菜单,仅微信小程序有效 (默认 true )
* @property {String} loadingIcon 加载中的图标,或者小图片 (默认 'photo' )
* @property {String} errorIcon 加载失败的图标,或者小图片 (默认 'error-circle' )
* @property {Boolean} showLoading 是否显示加载中的图标或者自定义的slot (默认 true )
* @property {Boolean} showError 是否显示加载错误的图标或者自定义的slot (默认 true )
* @property {Boolean} fade 是否需要淡入效果 (默认 true )
* @property {Boolean} webp 只支持网络资源,只对微信小程序有效 (默认 false )
* @property {String | Number} duration 搭配fade参数的过渡时间,单位ms (默认 500 )
* @property {String} bgColor 背景颜色,用于深色页面加载图片时,为了和背景色融合 (默认 '#f3f4f6' )
* @property {Object} customStyle 定义需要用到的外部样式
* @event {Function} click 点击图片时触发
* @event {Function} error 图片加载失败时触发
* @event {Function} load 图片加载成功时触发
* @example <u--image width="100%" height="300px" :src="src"></u--image>
*/
export default {
name: 'u--image',
mixins: [mpMixin, mixin, props],
components: {
uvImage
},
}
</script>
@@ -0,0 +1,125 @@
<template>
<uvInput
<!-- #ifdef VUE2 -->
:value="value"
@input="e => $emit('input', e)"
<!-- #endif -->
<!-- #ifdef VUE3 -->
:modelValue="modelValue"
@update:modelValue="e => $emit('update:modelValue', e)"
<!-- #endif -->
:type="type"
:fixed="fixed"
:disabled="disabled"
:disabledColor="disabledColor"
:clearable="clearable"
:password="password"
:maxlength="maxlength"
:placeholder="placeholder"
:placeholderClass="placeholderClass"
:placeholderStyle="placeholderStyle"
:confirmType="confirmType"
:confirmHold="confirmHold"
:holdKeyboard="holdKeyboard"
:focus="focus"
:autoBlur="autoBlur"
:disableDefaultPadding="disableDefaultPadding"
:alwaysEmbed="alwaysEmbed"
:cursor="cursor"
:cursorSpacing="cursorSpacing"
:selectionStart="selectionStart"
:selectionEnd="selectionEnd"
:adjustPosition="adjustPosition"
:inputAlign="inputAlign"
:fontSize="fontSize"
:color="color"
:backgroundColor="backgroundColor"
:round="round"
:borderColor="borderColor"
:prefixIcon="prefixIcon"
:suffixIcon="suffixIcon"
:suffixIconStyle="suffixIconStyle"
:prefixIconStyle="prefixIconStyle"
:border="border"
:readonly="readonly"
:shape="shape"
:customStyle="customStyle"
:formatter="formatter"
:ignoreCompositionEvent="ignoreCompositionEvent"
@focus="e => $emit('focus', e)"
@blur="e => $emit('blur', e)"
@keyboardheightchange="e => $emit('keyboardheightchange', e)"
@change="e => $emit('change', e)"
@confirm="e => $emit('confirm', e)"
@clear="$emit('clear')"
@click="$emit('click')"
>
<!-- #ifdef MP -->
<slot name="prefix"></slot>
<slot name="suffix"></slot>
<!-- #endif -->
<!-- #ifndef MP -->
<slot name="prefix" slot="prefix"></slot>
<slot name="suffix" slot="suffix"></slot>
<!-- #endif -->
</uvInput>
</template>
<script>
/**
* 此组件存在的理由是,在nvue下,u-input被uni-app官方占用了,u-input在nvue中相当于input组件
* 所以在nvue下,取名为u--input,内部其实还是u-input.vue,只不过做一层中转
*/
import uvInput from '../u-input/u-input.vue';
import props from '../u-input/props.js'
import mixin from '../../libs/mixin/mixin'
import mpMixin from '../../libs/mixin/mpMixin'
/**
* Input 输入框
* @description 此组件为一个输入框,默认没有边框和样式,是专门为配合表单组件u-form而设计的,利用它可以快速实现表单验证,输入内容,下拉选择等功能。
* @tutorial https://uviewui.com/components/input.html
* @property {String | Number} value 输入的值
* @property {String} type 输入框类型,见上方说明 ( 默认 'text' )
* @property {Boolean} fixed 如果 textarea 是在一个 position:fixed 的区域,需要显示指定属性 fixed 为 true,兼容性:微信小程序、百度小程序、字节跳动小程序、QQ小程序 ( 默认 false )
* @property {Boolean} disabled 是否禁用输入框 ( 默认 false )
* @property {String} disabledColor 禁用状态时的背景色( 默认 '#f5f7fa' )
* @property {Boolean} clearable 是否显示清除控件 ( 默认 false )
* @property {Boolean} password 是否密码类型 ( 默认 false )
* @property {String | Number} maxlength 最大输入长度,设置为 -1 的时候不限制最大长度 ( 默认 -1 )
* @property {String} placeholder 输入框为空时的占位符
* @property {String} placeholderClass 指定placeholder的样式类,注意页面或组件的style中写了scoped时,需要在类名前写/deep/ ( 默认 'input-placeholder' )
* @property {String | Object} placeholderStyle 指定placeholder的样式,字符串/对象形式,如"color: red;"
* @property {String} confirmType 设置右下角按钮的文字,兼容性详见uni-app文档 ( 默认 'done' )
* @property {Boolean} confirmHold 点击键盘右下角按钮时是否保持键盘不收起,H5无效 ( 默认 false )
* @property {Boolean} holdKeyboard focus时,点击页面的时候不收起键盘,微信小程序有效 ( 默认 false )
* @property {Boolean} focus 自动获取焦点,在 H5 平台能否聚焦以及软键盘是否跟随弹出,取决于当前浏览器本身的实现。nvue 页面不支持,需使用组件的 focus()、blur() 方法控制焦点 ( 默认 false )
* @property {Boolean} autoBlur 键盘收起时,是否自动失去焦点,目前仅App3.0.0+有效 ( 默认 false )
* @property {Boolean} disableDefaultPadding 是否去掉 iOS 下的默认内边距,仅微信小程序,且type=textarea时有效 ( 默认 false )
* @property {String | Number} cursor 指定focus时光标的位置( 默认 -1 )
* @property {String | Number} cursorSpacing 输入框聚焦时底部与键盘的距离 ( 默认 30 )
* @property {String | Number} selectionStart 光标起始位置,自动聚集时有效,需与selection-end搭配使用 ( 默认 -1 )
* @property {String | Number} selectionEnd 光标结束位置,自动聚集时有效,需与selection-start搭配使用 ( 默认 -1 )
* @property {Boolean} adjustPosition 键盘弹起时,是否自动上推页面 ( 默认 true )
* @property {String} inputAlign 输入框内容对齐方式( 默认 'left' )
* @property {String | Number} fontSize 输入框字体的大小 ( 默认 '15px' )
* @property {String} color 输入框字体颜色 ( 默认 '#303133' )
* @property {Function} formatter 内容式化函数
* @property {String} prefixIcon 输入框前置图标
* @property {String | Object} prefixIconStyle 前置图标样式,对象或字符串
* @property {String} suffixIcon 输入框后置图标
* @property {String | Object} suffixIconStyle 后置图标样式,对象或字符串
* @property {String} border 边框类型,surround-四周边框,bottom-底部边框,none-无边框 ( 默认 'surround' )
* @property {Boolean} readonly 是否只读,与disabled不同之处在于disabled会置灰组件,而readonly则不会 ( 默认 false )
* @property {String} shape 输入框形状,circle-圆形,square-方形 ( 默认 'square' )
* @property {Object} customStyle 定义需要用到的外部样式
* @property {Boolean} ignoreCompositionEvent 是否忽略组件内对文本合成系统事件的处理。
* @example <u--input v-model="value" :password="true" suffix-icon="lock-fill" />
*/
export default {
name: 'u--input',
mixins: [mpMixin, mixin, props],
components: {
uvInput
},
}
</script>
@@ -0,0 +1,74 @@
<template>
<uvText
:type="type"
:show="show"
:text="text"
:prefixIcon="prefixIcon"
:suffixIcon="suffixIcon"
:mode="mode"
:href="href"
:format="format"
:call="call"
:openType="openType"
:bold="bold"
:block="block"
:lines="lines"
:color="color"
:decoration="decoration"
:size="size"
:iconStyle="iconStyle"
:margin="margin"
:lineHeight="lineHeight"
:align="align"
:wordWrap="wordWrap"
:customStyle="customStyle"
@click="$emit('click')"
></uvText>
</template>
<script>
/**
* 此组件存在的理由是,在nvue下,u-text被uni-app官方占用了,u-text在nvue中相当于input组件
* 所以在nvue下,取名为u--input,内部其实还是u-text.vue,只不过做一层中转
* 不使用v-bind="$attrs",而是分开独立写传参,是因为微信小程序不支持此写法
*/
import uvText from "../u-text/u-text.vue";
import props from "../u-text/props.js";
import mixin from '../../libs/mixin/mixin'
import mpMixin from '../../libs/mixin/mpMixin'
/**
* Text 文本
* @description 此组件集成了文本类在项目中的常用功能,包括状态,拨打电话,格式化日期,*替换,超链接...等功能。 您大可不必在使用特殊文本时自己定义,text组件几乎涵盖您能使用的大部分场景。
* @tutorial https://uview.d3u.cn/components/loading.html
* @property {String} type 主题颜色
* @property {Boolean} show 是否显示(默认 true )
* @property {String | Number} text 显示的值
* @property {String} prefixIcon 前置图标
* @property {String} suffixIcon 后置图标
* @property {String} mode 文本处理的匹配模式 text-普通文本,price-价格,phone-手机号,name-姓名,date-日期,link-超链接
* @property {String} href mode=link下,配置的链接
* @property {String | Function} format 格式化规则
* @property {Boolean} call mode=phone时,点击文本是否拨打电话(默认 false )
* @property {String} openType 小程序的打开方式
* @property {Boolean} bold 是否粗体,默认normal(默认 false )
* @property {Boolean} block 是否块状(默认 false )
* @property {String | Number} lines 文本显示的行数,如果设置,超出此行数,将会显示省略号
* @property {String} color 文本颜色(默认 '#303133' )
* @property {String | Number} size 字体大小(默认 15 )
* @property {Object | String} iconStyle 图标的样式 (默认 {fontSize: '15px'} )
* @property {String} decoration 文字装饰,下划线,中划线等,可选值 none|underline|line-through(默认 'none' )
* @property {Object | String | Number} margin 外边距,对象、字符串,数值形式均可(默认 0 )
* @property {String | Number} lineHeight 文本行高
* @property {String} align 文本对齐方式,可选值left|center|right(默认 'left' )
* @property {String} wordWrap 文字换行,可选值break-word|normal|anywhere(默认 'normal' )
* @event {Function} click 点击触发事件
* @example <u--text text="我用十年青春,赴你最后之约"></u--text>
*/
export default {
name: "u--text",
mixins: [mpMixin, mixin, props],
components: {
uvText,
},
};
</script>
@@ -0,0 +1,102 @@
<template>
<uvTextarea
<!-- #ifdef VUE2 -->
:value="value"
@input="e => $emit('input', e)"
<!-- #endif -->
<!-- #ifdef VUE3 -->
:modelValue="modelValue"
@update:modelValue="e => $emit('update:modelValue', e)"
<!-- #endif -->
:placeholder="placeholder"
:placeholderStyle="placeholderStyle"
:placeholderClass="placeholderClass"
:height="height"
:confirmType="confirmType"
:disabled="disabled"
:count="count"
:focus="focus"
:autoHeight="autoHeight"
:fixed="fixed"
:cursorSpacing="cursorSpacing"
:cursor="cursor"
:showConfirmBar="showConfirmBar"
:selectionStart="selectionStart"
:selectionEnd="selectionEnd"
:adjustPosition="adjustPosition"
:disableDefaultPadding="disableDefaultPadding"
:holdKeyboard="holdKeyboard"
:maxlength="maxlength"
:border="border"
:round="round"
:borderColor="borderColor"
:backgroundColor="backgroundColor"
:customStyle="customStyle"
:formatter="formatter"
:ignoreCompositionEvent="ignoreCompositionEvent"
@focus="e => $emit('focus', e)"
@blur="e => $emit('blur', e)"
@linechange="e => $emit('linechange', e)"
@confirm="e => $emit('confirm', e)"
@keyboardheightchange="e => $emit('keyboardheightchange', e)"
></uvTextarea>
</template>
<script>
/**
* 此组件存在的理由是,在nvue下,u--textarea被uni-app官方占用了,u-textarea在nvue中相当于textarea组件
* 所以在nvue下,取名为u--textarea,内部其实还是u-textarea.vue,只不过做一层中转
*/
import uvTextarea from '../u-textarea/u-textarea.vue';
import props from '../u-textarea/props.js'
import mixin from '../../libs/mixin/mixin'
import mpMixin from '../../libs/mixin/mpMixin'
/**
* Textarea 文本域
* @description 文本域此组件满足了可能出现的表单信息补充,编辑等实际逻辑的功能,内置了字数校验等
* @tutorial https://uview.d3u.cn/components/textarea.html
*
* @property {String | Number} value 输入框的内容
* @property {String | Number} placeholder 输入框为空时占位符
* @property {String} placeholderClass 指定placeholder的样式类,注意页面或组件的style中写了scoped时,需要在类名前写/deep/ ( 默认 'input-placeholder' )
* @property {String | Object} placeholderStyle 指定placeholder的样式,字符串/对象形式,如"color: red;"
* @property {String | Number} height 输入框高度(默认 70 )
* @property {String} confirmType 设置键盘右下角按钮的文字,仅微信小程序,App-vue和H5有效(默认 'done' )
* @property {Boolean} disabled 是否禁用(默认 false )
* @property {Boolean} count 是否显示统计字数(默认 false )
* @property {Boolean} focus 是否自动获取焦点,nvue不支持,H5取决于浏览器的实现(默认 false )
* @property {Boolean | Function} autoHeight 是否自动增加高度(默认 false )
* @property {Boolean} fixed 如果textarea是在一个position:fixed的区域,需要显示指定属性fixed为true(默认 false )
* @property {Number} cursorSpacing 指定光标与键盘的距离(默认 0 )
* @property {String | Number} cursor 指定focus时的光标位置
* @property {Function} formatter 内容式化函数
* @property {Boolean} showConfirmBar 是否显示键盘上方带有”完成“按钮那一栏,(默认 true )
* @property {Number} selectionStart 光标起始位置,自动聚焦时有效,需与selection-end搭配使用,(默认 -1 )
* @property {Number | Number} selectionEnd 光标结束位置,自动聚焦时有效,需与selection-start搭配使用(默认 -1 )
* @property {Boolean} adjustPosition 键盘弹起时,是否自动上推页面(默认 true )
* @property {Boolean | Number} disableDefaultPadding 是否去掉 iOS 下的默认内边距,只微信小程序有效(默认 false )
* @property {Boolean} holdKeyboard focus时,点击页面的时候不收起键盘,只微信小程序有效(默认 false )
* @property {String | Number} maxlength 最大输入长度,设置为 -1 的时候不限制最大长度(默认 140 )
* @property {String} border 边框类型,surround-四周边框,none-无边框,bottom-底部边框(默认 'surround' )
* @property {Boolean} ignoreCompositionEvent 是否忽略组件内对文本合成系统事件的处理
* @property {String} backgroundColor 背景颜色
*@property {String} round 边框圆角
* @property {String} borderColor 边框颜色
*
*
* @event {Function(e)} focus 输入框聚焦时触发,event.detail = { value, height },height 为键盘高度
* @event {Function(e)} blur 输入框失去焦点时触发,event.detail = {value, cursor}
* @event {Function(e)} linechange 输入框行数变化时调用,event.detail = {height: 0, heightRpx: 0, lineCount: 0}
* @event {Function(e)} input 当键盘输入时,触发 input 事件
* @event {Function(e)} confirm 点击完成时, 触发 confirm 事件
* @event {Function(e)} keyboardheightchange 键盘高度发生变化的时候触发此事件
* @example <u--textarea v-model="value1" placeholder="请输入内容" ></u--textarea>
*/
export default {
name: 'u--textarea',
mixins: [mpMixin, mixin, props],
components: {
uvTextarea
},
}
</script>
@@ -0,0 +1,59 @@
export default {
props: {
// 操作菜单是否展示 (默认false)
show: {
type: Boolean,
default: ()=> uni.$u.props.actionSheet.show
},
// 标题
title: {
type: String,
default: ()=> uni.$u.props.actionSheet.title
},
// 选项上方的描述信息
description: {
type: String,
default: ()=> uni.$u.props.actionSheet.description
},
// 数据
actions: {
type: Array,
default: ()=> uni.$u.props.actionSheet.actions
},
// 取消按钮的文字,不为空时显示按钮
cancelText: {
type: String,
default: ()=> uni.$u.props.actionSheet.cancelText
},
// 点击某个菜单项时是否关闭弹窗
closeOnClickAction: {
type: Boolean,
default: ()=> uni.$u.props.actionSheet.closeOnClickAction
},
// 处理底部安全区(默认true)
safeAreaInsetBottom: {
type: Boolean,
default: ()=> uni.$u.props.actionSheet.safeAreaInsetBottom
},
// 小程序的打开方式
openType: {
type: String,
default: ()=> uni.$u.props.actionSheet.openType
},
// 点击遮罩是否允许关闭 (默认true)
closeOnClickOverlay: {
type: Boolean,
default: ()=> uni.$u.props.actionSheet.closeOnClickOverlay
},
// 圆角值
round: {
type: [String, Number],
default: ()=> uni.$u.props.actionSheet.round
},
// 高度
height: {
type: [String, Number],
default: ()=> uni.$u.props.actionSheet.height
}
}
}
@@ -0,0 +1,248 @@
<template>
<u-popup :show="show" mode="bottom" @close="closeHandler" :safeAreaInsetBottom="safeAreaInsetBottom" :round="round">
<view class="u-action-sheet">
<view class="u-action-sheet__header" v-if="title">
<text class="u-action-sheet__header__title u-line-1">{{ title }}</text>
<view class="u-action-sheet__header__icon-wrap" @tap.stop="cancel">
<u-icon name="close" size="17" color="#c8c9cc" bold></u-icon>
</view>
</view>
<text class="u-action-sheet__description" :style="[{
marginTop: `${title && description ? 0 : '18px'}`
}]" v-if="description">{{ description }}</text>
<slot>
<u-line v-if="description"></u-line>
<scroll-view class="u-action-sheet__item-wrap" :scroll-y="height !='' || height > 0" :style="[{ maxHeight: $u.addUnit(height) }]">
<view v-for="(item, index) in actions" :key="index">
<!-- #ifdef MP -->
<button
class="u-reset-button"
:key="index"
:openType="item.openType"
:lang="lang"
:session-from="sessionFrom"
:send-message-title="sendMessageTitle"
:send-message-path="sendMessagePath"
:send-message-img="sendMessageImg"
:show-message-card="showMessageCard"
:app-parameter="appParameter"
@tap="selectHandler(index)"
@getuserinfo="onGetUserInfo"
@contact="onContact"
@getphonenumber="onGetPhoneNumber"
@error="onError"
@launchapp="onLaunchApp"
@opensetting="onOpenSetting"
@chooseavatar="onChooseAvatar"
:hover-class="!item.disabled && !item.loading ? 'u-action-sheet--hover' : ''"
>
<!-- #endif -->
<view class="u-action-sheet__item-wrap__item" @tap.stop="selectHandler(index)"
:hover-class="!item.disabled && !item.loading ? 'u-action-sheet--hover' : ''"
:hover-stay-time="150">
<template v-if="!item.loading">
<text class="u-action-sheet__item-wrap__item__name" :style="[itemStyle(index)]">{{
item.name }}</text>
<text v-if="item.subname" class="u-action-sheet__item-wrap__item__subname">{{
item.subname }}</text>
</template>
<u-loading-icon v-else custom-class="van-action-sheet__loading" size="18" mode="circle" />
</view>
<!-- #ifdef MP -->
</button>
<!-- #endif -->
<u-line v-if="index !== actions.length - 1"></u-line>
</view>
</scroll-view>
</slot>
<u-gap bgColor="#eaeaec" height="6" v-if="cancelText"></u-gap>
<view hover-class="u-action-sheet--hover" v-if="cancelText" @tap="cancel">
<text
@touchmove.stop.prevent
:hover-stay-time="150"
class="u-action-sheet__cancel-text"
>{{ cancelText }}</text>
</view>
</view>
</u-popup>
</template>
<script>
import openType from '../../libs/mixin/openType';
import button from '../../libs/mixin/button';
import props from './props.js';
import mixin from '../../libs/mixin/mixin'
import mpMixin from '../../libs/mixin/mpMixin';
/**
* ActionSheet 操作菜单
* @description 本组件用于从底部弹出一个操作菜单,供用户选择并返回结果。本组件功能类似于uni的uni.showActionSheetAPI,配置更加灵活,所有平台都表现一致。
* @tutorial https://uview.d3u.cn/components/actionSheet.html
*
* @property {Boolean} show 操作菜单是否展示 (默认 false )
* @property {String} title 操作菜单标题
* @property {String} description 选项上方的描述信息
* @property {Array<Object>} actions 按钮的文字数组,见官方文档示例
* @property {String} cancelText 取消按钮的提示文字,不为空时显示按钮
* @property {Boolean} closeOnClickAction 点击某个菜单项时是否关闭弹窗 (默认 true )
* @property {Boolean} safeAreaInsetBottom 处理底部安全区 (默认 true )
* @property {String} openType 小程序的打开方式 (contact | launchApp | getUserInfo | openSetting |getPhoneNumber |error )
* @property {Boolean} closeOnClickOverlay 点击遮罩是否允许关闭 (默认 true )
* @property {Number|String} round 圆角值,默认无圆角 (默认 0 )
* @property {Number|String} height 高度,默认不限制 (默认 '' )
* @property {String} lang 指定返回用户信息的语言,zh_CN 简体中文,zh_TW 繁体中文,en 英文
* @property {String} sessionFrom 会话来源,openType="contact"时有效
* @property {String} sendMessageTitle 会话内消息卡片标题,openType="contact"时有效
* @property {String} sendMessagePath 会话内消息卡片点击跳转小程序路径,openType="contact"时有效
* @property {String} sendMessageImg 会话内消息卡片图片,openType="contact"时有效
* @property {Boolean} showMessageCard 是否显示会话内消息卡片,设置此参数为 true,用户进入客服会话会在右下角显示"可能要发送的小程序"提示,用户点击后可以快速发送小程序消息,openType="contact"时有效 (默认 false )
* @property {String} appParameter 打开 APP 时,向 APP 传递的参数,openType=launchApp 时有效
*
* @event {Function} select 点击ActionSheet列表项时触发
* @event {Function} close 点击取消按钮时触发
* @event {Function} getuserinfo 用户点击该按钮时,会返回获取到的用户信息,回调的 detail 数据与 wx.getUserInfo 返回的一致,openType="getUserInfo"时有效
* @event {Function} contact 客服消息回调,openType="contact"时有效
* @event {Function} getphonenumber 获取用户手机号回调,openType="getPhoneNumber"时有效
* @event {Function} chooseavatar 选择头像回调,openType="chooseAvatar"时有效
* @event {Function} error 当使用开放能力时,发生错误的回调,openType="error"时有效
* @event {Function} launchapp 打开 APP 成功的回调,openType="launchApp"时有效
* @event {Function} opensetting 在打开授权设置页后回调,openType="openSetting"时有效
* @example <u-action-sheet :actions="list" :title="title" :show="show"></u-action-sheet>
*/
export default {
name: "u-action-sheet",
// 一些props参数和methods方法,通过mixin混入,因为其他文件也会用到
mixins: [openType, button, mixin,mpMixin, props],
data() {
return {
};
},
computed: {
// 操作项目的样式
itemStyle() {
return (index) => {
let style = {};
if (this.actions[index].color) style.color = this.actions[index].color;
if (this.actions[index].fontSize) style.fontSize = uni.$u.addUnit(this.actions[index].fontSize);
// 选项被禁用的样式
if (this.actions[index].disabled) style.color = '#c0c4cc';
return style;
};
},
},
// #ifdef VUE3
emits: ["select", "close"],
// #endif
methods: {
closeHandler() {
// 允许点击遮罩关闭时,才发出close事件
if (this.closeOnClickOverlay) {
this.$emit('close');
}
},
// 点击取消按钮
cancel() {
this.$emit('close');
},
selectHandler(index) {
const item = this.actions[index];
if (item && !item.disabled && !item.loading) {
this.$emit('select', item);
if (this.closeOnClickAction) {
this.$emit('close');
}
}
},
}
};
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
$u-action-sheet-reset-button-width: 100% !default;
$u-action-sheet-title-font-size: 16px !default;
$u-action-sheet-title-padding: 12px 30px !default;
$u-action-sheet-title-color: $u-main-color !default;
$u-action-sheet-header-icon-wrap-right: 15px !default;
$u-action-sheet-header-icon-wrap-top: 15px !default;
$u-action-sheet-description-font-size: 13px !default;
$u-action-sheet-description-color: 14px !default;
$u-action-sheet-description-margin: 18px 15px !default;
$u-action-sheet-item-wrap-item-padding: 15px !default;
$u-action-sheet-item-wrap-name-font-size: 16px !default;
$u-action-sheet-item-wrap-subname-font-size: 13px !default;
$u-action-sheet-item-wrap-subname-color: #c0c4cc !default;
$u-action-sheet-item-wrap-subname-margin-top: 10px !default;
$u-action-sheet-cancel-text-font-size: 16px !default;
$u-action-sheet-cancel-text-color: $u-content-color !default;
$u-action-sheet-cancel-text-font-size: 15px !default;
$u-action-sheet-cancel-text-hover-background-color: rgb(242, 243, 245) !default;
.u-reset-button {
width: $u-action-sheet-reset-button-width;
}
.u-action-sheet {
text-align: center;
&__header {
position: relative;
padding: $u-action-sheet-title-padding;
&__title {
font-size: $u-action-sheet-title-font-size;
color: $u-action-sheet-title-color;
font-weight: bold;
text-align: center;
}
&__icon-wrap {
position: absolute;
right: $u-action-sheet-header-icon-wrap-right;
top: $u-action-sheet-header-icon-wrap-top;
}
}
&__description {
font-size: $u-action-sheet-description-font-size;
color: $u-tips-color;
margin: $u-action-sheet-description-margin;
text-align: center;
}
&__item-wrap {
&__item {
padding: $u-action-sheet-item-wrap-item-padding;
@include flex;
align-items: center;
justify-content: center;
flex-direction: column;
&__name {
font-size: $u-action-sheet-item-wrap-name-font-size;
color: $u-main-color;
text-align: center;
}
&__subname {
font-size: $u-action-sheet-item-wrap-subname-font-size;
color: $u-action-sheet-item-wrap-subname-color;
margin-top: $u-action-sheet-item-wrap-subname-margin-top;
text-align: center;
}
}
}
&__cancel-text {
font-size: $u-action-sheet-cancel-text-font-size;
color: $u-action-sheet-cancel-text-color;
text-align: center;
padding: $u-action-sheet-cancel-text-font-size;
}
&--hover {
background-color: $u-action-sheet-cancel-text-hover-background-color;
}
}
</style>
@@ -0,0 +1,59 @@
export default {
props: {
// 图片地址,Array<String>|Array<Object>形式
urls: {
type: Array,
default: ()=> uni.$u.props.album.urls
},
// 指定从数组的对象元素中读取哪个属性作为图片地址
keyName: {
type: String,
default: ()=> uni.$u.props.album.keyName
},
// 单图时,图片长边的长度
singleSize: {
type: [String, Number],
default: ()=> uni.$u.props.album.singleSize
},
// 多图时,图片边长
multipleSize: {
type: [String, Number],
default: ()=> uni.$u.props.album.multipleSize
},
// 多图时,图片水平和垂直之间的间隔
space: {
type: [String, Number],
default: ()=> uni.$u.props.album.space
},
// 单图时,图片缩放裁剪的模式
singleMode: {
type: String,
default: ()=> uni.$u.props.album.singleMode
},
// 多图时,图片缩放裁剪的模式
multipleMode: {
type: String,
default: ()=> uni.$u.props.album.multipleMode
},
// 最多展示的图片数量,超出时最后一个位置将会显示剩余图片数量
maxCount: {
type: [String, Number],
default: ()=> uni.$u.props.album.maxCount
},
// 是否可以预览图片
previewFullImage: {
type: Boolean,
default: ()=> uni.$u.props.album.previewFullImage
},
// 每行展示图片数量,如设置,singleSize和multipleSize将会无效
rowCount: {
type: [String, Number],
default: ()=> uni.$u.props.album.rowCount
},
// 超出maxCount时是否显示查看更多的提示
showMore: {
type: Boolean,
default: ()=> uni.$u.props.album.showMore
}
}
}
@@ -0,0 +1,270 @@
<template>
<view class="u-album">
<view
class="u-album__row"
ref="u-album__row"
v-for="(arr, index) in showUrls"
:forComputedUse="albumWidth"
:key="index"
>
<view
class="u-album__row__wrapper"
v-for="(item, index1) in arr"
:key="index1"
:style="[imageStyle(index + 1, index1 + 1)]"
@tap="previewFullImage ? onPreviewTap(getSrc(item)) : ''"
>
<image
:src="getSrc(item)"
:mode="
urls.length === 1
? imageHeight > 0
? singleMode
: 'widthFix'
: multipleMode
"
:style="[
{
width: imageWidth,
height: imageHeight
}
]"
></image>
<view
v-if="
showMore &&
urls.length > rowCount * showUrls.length &&
index === showUrls.length - 1 &&
index1 === showUrls[showUrls.length - 1].length - 1
"
class="u-album__row__wrapper__text"
>
<u--text
:text="`+${urls.length - maxCount}`"
color="#fff"
:size="multipleSize * 0.3"
align="center"
customStyle="justify-content: center"
></u--text>
</view>
</view>
</view>
</view>
</template>
<script>
import props from './props.js';
import mixin from '../../libs/mixin/mixin';
import mpMixin from '../../libs/mixin/mpMixin';
// #ifdef APP-NVUE
// 由于weex为阿里的KPI业绩考核的产物,所以不支持百分比单位,这里需要通过dom查询组件的宽度
const dom = uni.requireNativePlugin('dom');
// #endif
/**
* Album 相册
* @description 本组件提供一个类似相册的功能,让开发者开发起来更加得心应手。减少重复的模板代码
* @tutorial https://uview.d3u.cn/components/album.html
*
* @property {Array} urls 图片地址列表 Array<String>|Array<Object>形式
* @property {String} keyName 指定从数组的对象元素中读取哪个属性作为图片地址
* @property {String | Number} singleSize 单图时,图片长边的长度 (默认 180 )
* @property {String | Number} multipleSize 多图时,图片边长 (默认 70 )
* @property {String | Number} space 多图时,图片水平和垂直之间的间隔 (默认 6 )
* @property {String} singleMode 单图时,图片缩放裁剪的模式 (默认 'scaleToFill' )
* @property {String} multipleMode 多图时,图片缩放裁剪的模式 (默认 'aspectFill' )
* @property {String | Number} maxCount 取消按钮的提示文字 (默认 9 )
* @property {Boolean} previewFullImage 是否可以预览图片 (默认 true )
* @property {String | Number} rowCount 每行展示图片数量,如设置,singleSize和multipleSize将会无效 (默认 3 )
* @property {Boolean} showMore 超出maxCount时是否显示查看更多的提示 (默认 true )
*
* @event {Function} albumWidth 某些特殊的情况下,需要让文字与相册的宽度相等,这里事件的形式对外发送 (回调参数 width )
* @example <u-album :urls="urls2" @albumWidth="width => albumWidth = width" multipleSize="68" ></u-album>
*/
export default {
name: 'u-album',
mixins: [mpMixin, mixin, props],
data() {
return {
// 单图的宽度
singleWidth: 0,
// 单图的高度
singleHeight: 0,
// 单图时,如果无法获取图片的尺寸信息,让图片宽度默认为容器的一定百分比
singlePercent: 0.6,
};
},
watch: {
urls: {
immediate: true,
handler(newVal) {
if (newVal.length === 1) {
this.getImageRect();
}
},
},
},
computed: {
imageStyle() {
return (index1, index2) => {
const { space, rowCount, multipleSize, urls } = this,
{ addUnit, addStyle } = uni.$u,
rowLen = this.showUrls.length,
allLen = this.urls.length;
const style = {
marginRight: addUnit(space),
marginBottom: addUnit(space),
};
// 如果为最后一行,则每个图片都无需下边框
if (index1 === rowLen) style.marginBottom = 0;
// 每行的最右边一张和总长度的最后一张无需右边框
if (
index2 === rowCount ||
(index1 === rowLen &&
index2 === this.showUrls[index1 - 1].length)
)
style.marginRight = 0;
return style;
};
},
// 将数组划分为二维数组
showUrls() {
const arr = [];
this.urls.map((item, index) => {
// 限制最大展示数量
if (index + 1 <= this.maxCount) {
// 计算该元素为第几个素组内
const itemIndex = Math.floor(index / this.rowCount);
// 判断对应的索引是否存在
if (!arr[itemIndex]) {
arr[itemIndex] = [];
}
arr[itemIndex].push(item);
}
});
return arr;
},
imageWidth() {
return uni.$u.addUnit(
this.urls.length === 1
? this.singleWidth
: this.multipleSize,
);
},
imageHeight() {
return uni.$u.addUnit(
this.urls.length === 1
? this.singleHeight
: this.multipleSize,
);
},
// 此变量无实际用途,仅仅是为了利用computed特性,让其在urls长度等变化时,重新计算图片的宽度
// 因为用户在某些特殊的情况下,需要让文字与相册的宽度相等,所以这里事件的形式对外发送
albumWidth() {
let width = 0;
if (this.urls.length === 1) {
width = this.singleWidth;
} else {
width =
this.showUrls[0].length * this.multipleSize +
this.space * (this.showUrls[0].length - 1);
}
this.$emit('albumWidth', width);
return width;
},
},
// #ifdef VUE3
emits: ['albumWidth'],
// #endif
methods: {
// 预览图片
onPreviewTap(url) {
const urls = this.urls.map((item) => {
return this.getSrc(item);
});
uni.previewImage({
current: url,
urls,
});
},
// 获取图片的路径
getSrc(item) {
return uni.$u.test.object(item)
? (this.keyName && item[this.keyName]) || item.src
: item;
},
// 单图时,获取图片的尺寸
// 在小程序中,需要将网络图片的的域名添加到小程序的download域名才可能获取尺寸
// 在没有添加的情况下,让单图宽度默认为盒子的一定宽度(singlePercent)
getImageRect() {
const src = this.getSrc(this.urls[0]);
uni.getImageInfo({
src,
success: (res) => {
let singleSize = uni.$u.getPx(this.singleSize);
// 判断图片横向还是竖向展示方式
const isHorizotal = res.width >= res.height;
this.singleWidth = isHorizotal
? singleSize
: (res.width / res.height) * singleSize;
this.singleHeight = !isHorizotal
? singleSize
: (res.height / res.width) * this.singleWidth;
},
fail: () => {
this.getComponentWidth();
},
});
},
// 获取组件的宽度
async getComponentWidth() {
// 延时一定时间,以获取dom尺寸
await uni.$u.sleep(30);
// #ifndef APP-NVUE
this.$uGetRect('.u-album__row').then((size) => {
this.singleWidth = size.width * this.singlePercent;
});
// #endif
// #ifdef APP-NVUE
// 这里ref="u-album__row"所在的标签为通过for循环出来,导致this.$refs['u-album__row']是一个数组
const ref = this.$refs['u-album__row'][0];
ref &&
dom.getComponentRect(ref, (res) => {
this.singleWidth = res.size.width * this.singlePercent;
});
// #endif
},
},
};
</script>
<style lang="scss" scoped>
@import '../../libs/css/components.scss';
.u-album {
@include flex(column);
&__row {
@include flex(row);
flex-wrap: nowrap;
&__wrapper {
position: relative;
&__text {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.3);
@include flex(row);
justify-content: center;
align-items: center;
}
}
}
}
</style>
@@ -0,0 +1,44 @@
export default {
props: {
// 显示文字
title: {
type: String,
default: ()=> uni.$u.props.alert.title
},
// 主题,success/warning/info/error
type: {
type: String,
default: ()=> uni.$u.props.alert.type
},
// 辅助性文字
description: {
type: String,
default: ()=> uni.$u.props.alert.description
},
// 是否可关闭
closable: {
type: Boolean,
default: ()=> uni.$u.props.alert.closable
},
// 是否显示图标
showIcon: {
type: Boolean,
default: ()=> uni.$u.props.alert.showIcon
},
// 浅或深色调,light-浅色,dark-深色
effect: {
type: String,
default: ()=> uni.$u.props.alert.effect
},
// 文字是否居中
center: {
type: Boolean,
default: ()=> uni.$u.props.alert.center
},
// 字体大小
fontSize: {
type: [String, Number],
default: ()=> uni.$u.props.alert.fontSize
}
}
}
@@ -0,0 +1,265 @@
<template>
<u-transition
mode="fade"
:show="show"
>
<view
class="u-alert"
:class="[`u-alert--${type}--${effect}`]"
@tap.stop="clickHandler"
:style="[$u.addStyle(customStyle)]"
>
<view
class="u-alert__icon"
v-if="showIcon"
>
<u-icon
:name="iconName"
size="18"
:color="iconColor"
></u-icon>
</view>
<view
class="u-alert__content"
:style="[{
paddingRight: closable ? '20px' : 0
}]"
>
<text
class="u-alert__content__title"
v-if="title"
:style="[{
fontSize: $u.addUnit(fontSize),
textAlign: center ? 'center' : 'left'
}]"
:class="[effect === 'dark' ? 'u-alert__text--dark' : `u-alert__text--${type}--light`]"
>{{ title }}</text>
<text
class="u-alert__content__desc"
v-if="description"
:style="[{
fontSize: $u.addUnit(fontSize),
textAlign: center ? 'center' : 'left'
}]"
:class="[effect === 'dark' ? 'u-alert__text--dark' : `u-alert__text--${type}--light`]"
>{{ description }}</text>
</view>
<view
class="u-alert__close"
v-if="closable"
@tap.stop="closeHandler"
>
<u-icon
name="close"
:color="iconColor"
size="15"
></u-icon>
</view>
</view>
</u-transition>
</template>
<script>
import props from './props.js';
import mixin from '../../libs/mixin/mixin'
import mpMixin from '../../libs/mixin/mpMixin';
/**
* Alert 警告提示
* @description 警告提示,展现需要关注的信息。
* @tutorial https://uview.d3u.cn/components/alertTips.html
*
* @property {String} title 显示的文字
* @property {String} type 使用预设的颜色 (默认 'warning' )
* @property {String} description 辅助性文字,颜色比title浅一点,字号也小一点,可选
* @property {Boolean} closable 关闭按钮(默认为叉号icon图标) (默认 false )
* @property {Boolean} showIcon 是否显示左边的辅助图标 ( 默认 false )
* @property {String} effect 多图时,图片缩放裁剪的模式 (默认 'light' )
* @property {Boolean} center 文字是否居中 (默认 false )
* @property {String | Number} fontSize 字体大小 (默认 14 )
* @property {Object} customStyle 定义需要用到的外部样式
* @event {Function} click 点击组件时触发
* @event {Function} close 点击关闭按钮时触发
* @example <u-alert :title="title" type = "warning" :closable="closable" :description = "description"></u-alert>
*/
export default {
name: 'u-alert',
mixins: [mpMixin, mixin, props],
data() {
return {
show: true,
iconColor:'',
iconName:'',
}
},
watch: {
effect: {
immediate: true,
handler(newVal) {
this.getIconColor()
}
},
type: {
immediate: true,
handler(newVal) {
this.getIconName()
}
}
},
// #ifdef VUE3
emits: ["click", "close"],
// #endif
methods: {
getIconColor() {
this.iconColor = this.effect === 'light' ? this.type : '#fff'
},
// 不同主题对应不同的图标
getIconName() {
switch (this.type) {
case 'success':
this.iconName = 'checkmark-circle-fill';
break;
case 'error':
this.iconName = 'close-circle-fill';
break;
case 'warning':
this.iconName = 'error-circle-fill';
break;
case 'info':
this.iconName = 'info-circle-fill';
break;
case 'primary':
this.iconName = 'more-circle-fill';
break;
default:
this.iconName = 'error-circle-fill';
}
},
// 点击内容
clickHandler(e) {
this.$emit('click', e)
},
// 点击关闭按钮
closeHandler() {
this.show = false
this.$emit('close')
}
}
}
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
.u-alert {
position: relative;
background-color: $u-primary;
padding: 8px 10px;
@include flex(row);
align-items: center;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
&--primary--dark {
background-color: $u-primary;
}
&--primary--light {
background-color: #ecf5ff;
}
&--error--dark {
background-color: $u-error;
}
&--error--light {
background-color: #FEF0F0;
}
&--success--dark {
background-color: $u-success;
}
&--success--light {
background-color: #f5fff0;
}
&--warning--dark {
background-color: $u-warning;
}
&--warning--light {
background-color: #FDF6EC;
}
&--info--dark {
background-color: $u-info;
}
&--info--light {
background-color: #f4f4f5;
}
&__icon {
margin-right: 5px;
}
&__content {
@include flex(column);
flex: 1;
&__title {
color: $u-main-color;
font-size: 14px;
font-weight: bold;
color: #fff;
margin-bottom: 2px;
}
&__desc {
color: $u-main-color;
font-size: 14px;
flex-wrap: wrap;
color: #fff;
}
}
&__title--dark,
&__desc--dark {
color: #FFFFFF;
}
&__text--primary--light,
&__text--primary--light {
color: $u-primary;
}
&__text--success--light,
&__text--success--light {
color: $u-success;
}
&__text--warning--light,
&__text--warning--light {
color: $u-warning;
}
&__text--error--light,
&__text--error--light {
color: $u-error;
}
&__text--info--light,
&__text--info--light {
color: $u-info;
}
&__close {
position: absolute;
top: 11px;
right: 10px;
}
}
</style>
@@ -0,0 +1,100 @@
export default {
props: {
// 金额数值
value: {
type: Number,
default: ()=> uni.$u.props.amount.value
},
// #ifdef VUE3
modelValue: {
type: Number,
default: ()=> uni.$u.props.amount.value
},
// #endif
// 金融符号
symbol: {
type: String,
default: ()=> uni.$u.props.amount.symbol
},
// 是否置后金额符号位置
reverse: {
type: Boolean,
default: ()=> uni.$u.props.amount.reverse
},
// 数字精度,小数点后保留几位
precision: {
type: Number,
default: ()=> uni.$u.props.amount.precision
},
// 数字精度取舍是否四舍五入
roundUp: {
type: Boolean,
default: ()=> uni.$u.props.amount.roundUp
},
// 数字变化是否使用动画
transition: {
type: Boolean,
default: ()=> uni.$u.props.amount.transition
},
// 数字变化动画时长
duration: {
type: Number,
default: ()=> uni.$u.props.amount.duration
},
// 分隔符位置 (3为千分位,4为万分位)
separatorDigits: {
type: Number,
default: ()=> uni.$u.props.amount.separatorDigits
},
// 分隔符
separator: {
type: String,
default: ()=> uni.$u.props.amount.separator
},
// 是否显示金融符号
showSymbol: {
type: Boolean,
default: ()=> uni.$u.props.amount.showSymbol
},
// 是否显示小数
showDecimal: {
type: Boolean,
default: ()=> uni.$u.props.amount.showDecimal
},
// 是否显示分隔符
showSeparator: {
type: Boolean,
default: ()=> uni.$u.props.amount.showSeparator
},
// 数字是否转换为大写中文
capital: {
type: Boolean,
default: ()=> uni.$u.props.amount.capital
},
// 整数字体大小
fontSize: {
type: String,
default: ()=> uni.$u.props.amount.fontSize
},
// 金融符号小数与整数的比例或字体大小
fontSizeRatio: {
type: [String, Number],
default: ()=> uni.$u.props.amount.fontSizeRatio
},
// 颜色
color: {
type: String,
default: ()=> uni.$u.props.amount.color
},
// 金融符号与小数字体颜色
colorRatio: {
type: String,
default: ()=> uni.$u.props.amount.colorRatio
},
// 自定义样式
customStyle: {
type: Object,
default: ()=> uni.$u.props.amount.customStyle
}
}
}
@@ -0,0 +1,321 @@
<template>
<view class="u-amount" :style="[$u.addStyle(customStyle)]">
<!-- 插槽模式 -->
<slot v-if="$slots.default || $slots.$default" :integer="integerPart" :decimal="decimalPart" :capital="capitalText"></slot>
<!-- 默认显示模式 -->
<template v-else>
<!-- 大写中文模式 -->
<text v-if="capital" class="u-amount__capital" :style="[textStyle]">{{ capitalText }}</text>
<!-- 普通数字模式 -->
<template v-else>
<!-- 前置符号 -->
<text v-if="showSymbol && !reverse" class="u-amount__symbol" :style="[symbolStyle]">{{ symbol }}</text>
<!-- 整数部分 -->
<text class="u-amount__integer" :style="[integerStyle]">{{ formattedInteger }}</text>
<!-- 小数部分 -->
<text v-if="showDecimal && decimalPart" class="u-amount__decimal" :style="[decimalStyle]">.{{ decimalPart }}</text>
<!-- 后置符号 -->
<text v-if="showSymbol && reverse" class="u-amount__symbol" :style="[symbolStyle]">{{ symbol }}</text>
</template>
</template>
</view>
</template>
<script>
import props from './props.js';
import mixin from '../../libs/mixin/mixin'
import mpMixin from '../../libs/mixin/mpMixin';
/**
* amount 金融数字
* @description 用于展示金额、价格等,可以转换为人民币大写,支持动画效果
* @tutorial https://uviewui.com/components/amount.html
*
* @property {Number} v-model/modelValue 金额数值 (默认 0 )
* @property {String} symbol 金融符号 (默认 '¥' )
* @property {Boolean} reverse 是否置后金额符号位置 (默认 false )
* @property {Number} precision 数字精度,小数点后保留几位 (默认 2 )
* @property {Boolean} roundUp 数字精度取舍是否四舍五入 (默认 true )
* @property {Boolean} transition 数字变化是否使用动画 (默认 false )
* @property {Number} duration 数字变化动画时长 (默认 1000 )
* @property {Number} separatorDigits 分隔符位置 (默认 4 )
* @property {String} separator 分隔符 (默认 ',' )
* @property {Boolean} showSymbol 是否显示金融符号 (默认 true )
* @property {Boolean} showDecimal 是否显示小数 (默认 true )
* @property {Boolean} showSeparator 是否显示分隔符 (默认 false )
* @property {Boolean} capital 数字是否转换为大写中文 (默认 false )
* @property {String} fontSize 整数字体大小 (默认 '' )
* @property {String|Number} fontSizeRatio 金融符号小数字体大小 (默认 0.7 )
* @property {String} color 颜色 (默认 '' )
* @property {String} colorRatio 金融符号与小数字体颜色
* @property {Object} customStyle 定义需要用到的外部样式
* @example <u-amount v-model="123456.789" :show-separator="true" />
*/
export default {
name: 'u-amount',
mixins: [mpMixin, mixin, props],
data() {
return {
displayValue: 0,
animationTimer: null
}
},
computed: {
// 格式化后的数值
formattedValue() {
let num = this.displayValue;
if (this.roundUp) {
num = Math.round(num * Math.pow(10, this.precision)) / Math.pow(10, this.precision);
} else {
num = Math.floor(num * Math.pow(10, this.precision)) / Math.pow(10, this.precision);
}
return num.toFixed(this.precision);
},
// 整数部分
integerPart() {
const parts = this.formattedValue.split('.');
return parts[0];
},
// 小数部分
decimalPart() {
const parts = this.formattedValue.split('.');
return parts[1] || '';
},
// 格式化的整数(带分隔符)
formattedInteger() {
if (!this.showSeparator) return this.integerPart;
return this.addSeparator(this.integerPart);
},
// 中文大写
capitalText() {
if (!this.capital) return '';
return this.toChinese(this.displayValue);
},
// 文本通用样式
textStyle() {
const style = {};
if (this.color){
style.color = this.color;
}
if (this.fontSize){
style.fontSize = this.fontSize;
}
return style;
},
// 符号样式
symbolStyle() {
const style = {};
if (this.color){
style.color = this.color;
}
if (this.colorRatio){
style.color = this.colorRatio;
}
if (this.fontSize) {
if (this.fontSizeRatio) {
style.fontSize = this.fontSizeRatio;
} else {
const size = parseFloat(this.fontSize);
const ratio = parseFloat(this.fontSizeRatio);
style.fontSize = uni.$u.addUnit(size * ratio);
}
}
return style;
},
// 整数样式
integerStyle() {
const style = {};
if (this.color){
style.color = this.color;
}
if (this.fontSize){
style.fontSize = this.fontSize;
}
return style;
},
// 小数样式
decimalStyle() {
const style = {};
if (this.color){
style.color = this.color;
}
if (this.colorRatio){
style.color = this.colorRatio;
}
if (this.fontSize) {
if (this.fontSizeRatio) {
style.fontSize = this.fontSizeRatio;
} else {
const size = parseFloat(this.fontSize);
const ratio = parseFloat(this.fontSizeRatio);
style.fontSize = uni.$u.addUnit((size * ratio));
}
}
return style;
}
},
watch: {
value: {
handler(newVal) {
if(!newVal) return;
if (this.transition) {
this.startAnimation(newVal);
} else {
this.displayValue = newVal;
}
},
immediate: true
},
// #ifdef VUE3
modelValue: {
handler(newVal) {
if(!newVal) return;
if (this.transition) {
this.startAnimation(newVal);
} else {
this.displayValue = newVal;
}
},
immediate: true
}
// #endif
},
methods: {
// 添加分隔符
addSeparator(num) {
const str = num.toString();
const len = str.length;
let result = '';
for (let i = 0; i < len; i++) {
if (i > 0 && (len - i) % this.separatorDigits === 0) {
result += this.separator;
}
result += str[i];
}
return result;
},
// 启动动画
startAnimation(targetValue) {
if (this.animationTimer) {
clearInterval(this.animationTimer);
}
const startValue = this.displayValue;
const diff = targetValue - startValue;
const stepTime = 16; // 60fps
const steps = this.duration / stepTime;
const stepValue = diff / steps;
let currentStep = 0;
this.animationTimer = setInterval(() => {
currentStep++;
if (currentStep >= steps) {
this.displayValue = targetValue;
clearInterval(this.animationTimer);
this.animationTimer = null;
} else {
this.displayValue = startValue + (stepValue * currentStep);
}
}, stepTime);
},
// 转换为中文大写
toChinese(amount) {
// 小数单位:角、分、毫、厘
const fractionUnits = ['角', '分', '毫', '厘'];
// 数字对应的中文大写
const chineseDigits = ['零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖'];
// 整数单位
const integerUnits = [
['元', '万', '亿'],
['', '拾', '佰', '仟']
];
// 处理负数
const prefix = amount < 0 ? '欠' : '';
amount = Math.abs(amount);
let result = '';
// 处理小数部分(角、分、毫、厘)
for (let i = 0; i < fractionUnits.length; i++) {
const decimalDigit = Math.floor(amount * Math.pow(10, i + 1)) % 10;
if (decimalDigit > 0) {
result += chineseDigits[decimalDigit] + fractionUnits[i];
}
}
// 如果小数部分为空,则显示"整"
result = result || '整';
// 处理整数部分
let integerPart = Math.floor(amount);
for (let i = 0; i < integerUnits[0].length && integerPart > 0; i++) {
let sectionResult = '';
for (let j = 0; j < integerUnits[1].length && integerPart > 0; j++) {
sectionResult = chineseDigits[integerPart % 10] + integerUnits[1][j] + sectionResult;
integerPart = Math.floor(integerPart / 10);
}
result = sectionResult.replace(/(零.)*零$/, '').replace(/^$/, '零') + integerUnits[0][i] + result;
}
// 处理特殊情况和格式化
return prefix + result.replace(/(零.)*零元/, '元')
.replace(/(零.)+/g, '零')
.replace(/^整$/, '零元整')
.replace(/零角零分零毫零厘/, '整')
.replace(/零角零分零毫/, '')
.replace(/零角零分/, '')
.replace(/零角/, '');
}
},
// #ifdef VUE2
beforeDestroy() {
// #endif
// #ifdef VUE3
beforeUnmount() {
// #endif
if (this.animationTimer) {
clearInterval(this.animationTimer);
}
}
}
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
.u-amount {
@include flex(row);
align-items: baseline;
&__symbol {
font-weight: normal;
}
&__integer {
font-weight: bold;
}
&__decimal {
font-weight: normal;
}
&__capital {
font-weight: normal;
}
}
</style>
@@ -0,0 +1,105 @@
export default {
props: {
// 输入的值
// #ifdef VUE2
value: {
type: [Array, String, Number],
default: ()=> uni.$u.props.areaPicker.value
},
// #endif
// #ifdef VUE3
modelValue: {
type: [Array, String, Number],
default: ()=> uni.$u.props.areaPicker.value
},
// #endif
// 是否展示picker弹窗
show: {
type: Boolean,
default: ()=> uni.$u.props.areaPicker.show
},
// 是否显示省份
province: {
type: Boolean,
default: ()=> uni.$u.props.areaPicker.province
},
// 是否显示城市
city: {
type: Boolean,
default: ()=> uni.$u.props.areaPicker.city
},
// 是否显示区县
county: {
type: Boolean,
default: ()=> uni.$u.props.areaPicker.county
},
// 是否展示顶部的操作栏
showToolbar: {
type: Boolean,
default: ()=> uni.$u.props.areaPicker.showToolbar
},
// 顶部标题
title: {
type: String,
default: ()=> uni.$u.props.areaPicker.title
},
// 是否显示加载中状态
loading: {
type: Boolean,
default: ()=> uni.$u.props.areaPicker.loading
},
// 各列中,单个选项的高度
itemHeight: {
type: [String, Number],
default: ()=> uni.$u.props.areaPicker.itemHeight
},
// 取消按钮的文字
cancelText: {
type: String,
default: ()=> uni.$u.$t('uAreaPicker.cancelText')
},
// 确认按钮的文字
confirmText: {
type: String,
default: ()=> uni.$u.$t('uAreaPicker.confirmText')
},
// 取消按钮的颜色
cancelColor: {
type: String,
default: ()=> uni.$u.props.areaPicker.cancelColor
},
// 确认按钮的颜色
confirmColor: {
type: String,
default: ()=> uni.$u.props.areaPicker.confirmColor
},
// 每列中可见选项的数量
visibleItemCount: {
type: [String, Number],
default: ()=> uni.$u.props.areaPicker.visibleItemCount
},
// 是否允许点击遮罩关闭选择器
closeOnClickOverlay: {
type: Boolean,
default: ()=> uni.$u.props.areaPicker.closeOnClickOverlay
},
// 是否在手指松开时立即触发 change 事件。若不开启则会在滚动动画结束后触发 change 事件,只在微信2.21.1及以上有效
immediateChange: {
type: Boolean,
default: ()=> uni.$u.props.areaPicker.immediateChange
},
// 设置圆角值
round: {
type: String,
default: ()=> uni.$u.props.areaPicker.round
},
showInput: {
type: Boolean,
default: ()=> uni.$u.props.areaPicker.showInput
},
inputProps:{
type: Object,
default: ()=> uni.$u.props.areaPicker.inputProps
}
}
}
@@ -0,0 +1,281 @@
<template>
<u-picker
ref="areaPicker"
keyName="label"
valueName="value"
:show="show"
:columns="columns"
:showToolbar="showToolbar"
:title="title"
:loading="loading"
:itemHeight="itemHeight"
:cancelText="cancelText"
:confirmText="confirmText"
:cancelColor="cancelColor"
:confirmColor="confirmColor"
:visibleItemCount="visibleItemCount"
:closeOnClickOverlay="closeOnClickOverlay"
:defaultIndex="innerDefaultIndex"
:immediateChange="immediateChange"
:round="round"
@close="close"
@cancel="cancel"
@confirm="confirm"
@change="change"
>
<template v-slot:trigger>
<slot name="trigger">
<u-input
v-if="showInput"
:value="inputValue"
:clearable="inputProps.clearable"
:placeholder="inputProps.placeholder"
:disabled="inputProps.disabled"
:border="inputProps.border"
:round="inputProps.round"
:backgroundColor="inputProps.disabled ? '' : inputProps.backgroundColor"
:disabledColor="inputProps.disabled ? inputProps.backgroundColor : ''"
:placeholderClass="inputProps.placeholderClass"
:placeholderStyle="inputProps.placeholderStyle"
:confirmType="inputProps.confirmType"
:focus="inputProps.focus"
:inputAlign="inputProps.inputAlign"
:fontSize="inputProps.fontSize"
:color="inputProps.color"
:borderColor="inputProps.borderColor"
:prefixIcon="inputProps.prefixIcon"
:suffixIcon="inputProps.suffixIcon"
:suffixIconStyle="inputProps.suffixIconStyle"
:prefixIconStyle="inputProps.prefixIconStyle"
:shape="inputProps.shape"
:customStyle="inputProps.customStyle"
/>
</slot>
</template>
</u-picker>
</template>
<script>
import area from '../../libs/util/area.js'
import props from './props.js'
import mixin from '../../libs/mixin/mixin'
import mpMixin from '../../libs/mixin/mpMixin'
export default {
name: 'u-region-picker',
mixins: [mpMixin, mixin, props],
data() {
return {
inputValue: '',
columns: [],
innerDefaultIndex: [],
selectedValue:[]
}
},
watch: {
// #ifdef VUE2
value(newValue) {
if(newValue){
this.init()
}
},
// #endif
// #ifdef VUE3
modelValue(newValue) {
if(newValue){
this.init()
}
},
// #endif
},
mounted() {
this.init();
},
// #ifdef VUE3
emits: ['update:modelValue','init', 'confirm', 'change', 'close', 'cancel'],
// #endif
methods: {
// 初始化数据
init() {
const columns = [];
const innerDefaultIndex = [];
const innerDefaultLabel = []
// 获取当前值
const currentValue = this.getCurrentValue();
// 处理省份列
let selectedProvince;
if (this.province) {
const province = this.getProvinceList();
columns.push(province);
selectedProvince = this.getSelectedValue(0, province);
let provinceIndex = province.findIndex(item => item.value == selectedProvince);
innerDefaultIndex.push(provinceIndex >= 0 ? provinceIndex : 0);
innerDefaultLabel.push(province[provinceIndex].label)
}
// 处理城市列
let selectedCity;
if (this.city) {
const city = this.getCityList(selectedProvince);
columns.push(city);
const cityIndex = this.province ? 1 : 0;
selectedCity = this.getSelectedValue(cityIndex, city);
let cityIndexValue = city.findIndex(item => item.value == selectedCity);
innerDefaultIndex.push(cityIndexValue >= 0 ? cityIndexValue : 0);
innerDefaultLabel.push(city[cityIndexValue].label)
}
// 处理县区列
if (this.county && selectedCity) {
const county = this.getCountyList(selectedCity);
columns.push(county);
const countyIndex = (this.province ? 1 : 0) + (this.city ? 1 : 0);
const selectedCounty = this.getSelectedValue(countyIndex, county);
let countyIndexValue = county.findIndex(item => item.value == selectedCounty);
innerDefaultIndex.push(countyIndexValue >= 0 ? countyIndexValue : 0);
innerDefaultLabel.push(county[countyIndexValue].label)
}
// 设置列数据
this.columns = columns;
this.innerDefaultIndex = innerDefaultIndex;
if(this.showInput && currentValue.length > 0){
this.inputValue = innerDefaultLabel.join(this.inputProps.separator)
}
},
// 获取选中值
getSelectedValue(index, list) {
const currentValue = this.getCurrentValue();
// 如果有默认值且列表不为空,则使用默认值,否则使用第一个值
return (currentValue && currentValue[index] && list.length > 0)
? currentValue[index]
: list[0]?.value;
},
// 获取当前值,兼容 Vue2 和 Vue3
getCurrentValue() {
// #ifdef VUE2
return this.value;
// #endif
// #ifdef VUE3
return this.modelValue;
// #endif
},
// 获取省份数据
getProvinceList() {
let list = [];
Object.entries(area.province_list).forEach(([value, label],idx) => {
list.push({ value, label });
});
return list;
},
// 获取城市数据
getCityList(cityCode) {
let list = [];
Object.entries(area.city_list).forEach(([value, label],idx) => {
if(cityCode){
if (value.startsWith(String(cityCode).substring(0, 2))) {
list.push({ value, label });
}
}else{
list.push({ value, label });
}
});
return list;
},
// 获取区县数据
getCountyList(countyCode) {
let list = [];
Object.entries(area.county_list).forEach(([value, label],idx) => {
if(countyCode){
if (value.startsWith(String(countyCode).substring(0, 4))) {
list.push({ value, label });
}
}else{
list.push({ value, label });
}
});
return list;
},
// 选择器变化事件
change(e) {
const { columnIndex, value } = e;
// 计算实际的列索引(考虑可能跳过的列)
let actualColumnIndex = columnIndex;
let valueIndex = columnIndex;
// 省份列变化
if ((this.province && actualColumnIndex === 0) && this.city) {
// 选择省份时更新城市列表
const cityList = this.getCityList(value[valueIndex].value);
const cityColumnIndex = this.province ? 1 : 0;
this.$refs.areaPicker.setColumnValues(cityColumnIndex, cityList);
// 如果显示区县列,同时更新区县列表
if (this.county && cityList.length > 0) {
const countyList = this.getCountyList(cityList[0].value);
const countyColumnIndex = (this.province ? 1 : 0) + (this.city ? 1 : 0);
this.$refs.areaPicker.setColumnValues(countyColumnIndex, countyList);
}
}
// 城市列变化
else if (this.city &&
((this.province && actualColumnIndex === 1) ||
(!this.province && actualColumnIndex === 0)) &&
this.county) {
// 选择城市时更新区县列表
const cityValueIndex = this.province ? 1 : 0;
let countyList = this.getCountyList(value[cityValueIndex].value);
const countyColumnIndex = (this.province ? 1 : 0) + (this.city ? 1 : 0) - 1;
this.$refs.areaPicker.setColumnValues(countyColumnIndex + 1, countyList);
}
this.$emit('change', e);
},
// 关闭选择器
close() {
if (this.closeOnClickOverlay) {
this.$emit('close')
}
},
// 确认选择
confirm(e) {
this.$emit('confirm', e);
let value = e.value.map(item => item.value)
if(this.showInput){
let label = e.value.map(item => item.label)
this.inputValue = label.join(this.inputProps.separator)
}
// #ifdef VUE2
this.$emit('input', value)
// #endif
// #ifdef VUE3
this.$emit('update:modelValue', value)
// #endif
},
// 取消选择
cancel() {
this.$emit('cancel');
}
},
}
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
</style>
@@ -0,0 +1,52 @@
export default {
props: {
// 头像图片组
urls: {
type: Array,
default: ()=> uni.$u.props.avatarGroup.urls
},
// 最多展示的头像数量
maxCount: {
type: [String, Number],
default: ()=> uni.$u.props.avatarGroup.maxCount
},
// 头像形状
shape: {
type: String,
default: ()=> uni.$u.props.avatarGroup.shape
},
// 图片裁剪模式
mode: {
type: String,
default: ()=> uni.$u.props.avatarGroup.mode
},
// 超出maxCount时是否显示查看更多的提示
showMore: {
type: Boolean,
default: ()=> uni.$u.props.avatarGroup.showMore
},
// 头像大小
size: {
type: [String, Number],
default: ()=> uni.$u.props.avatarGroup.size
},
// 指定从数组的对象元素中读取哪个属性作为图片地址
keyName: {
type: String,
default: ()=> uni.$u.props.avatarGroup.keyName
},
// 头像之间的遮挡比例
gap: {
type: [String, Number],
validator(value) {
return value >= 0 && value <= 1
},
default: ()=> uni.$u.props.avatarGroup.gap
},
// 需额外显示的值
extraValue: {
type: [Number, String],
default: ()=> uni.$u.props.avatarGroup.extraValue
}
}
}
@@ -0,0 +1,108 @@
<template>
<view class="u-avatar-group">
<view
class="u-avatar-group__item"
v-for="(item, index) in showUrl"
:key="index"
:style="{
marginLeft: index === 0 ? 0 : $u.addUnit(-size * gap)
}"
>
<u-avatar
:size="size"
:shape="shape"
:mode="mode"
:src="$u.test.object(item) ? keyName && item[keyName] || item.url : item"
></u-avatar>
<view
class="u-avatar-group__item__show-more"
v-if="showMore && index === showUrl.length - 1 && (urls.length > maxCount || extraValue > 0)"
@tap="clickHandler"
>
<u--text
color="#ffffff"
:size="size * 0.4"
:text="`+${extraValue || urls.length - showUrl.length}`"
align="center"
customStyle="justify-content: center"
></u--text>
</view>
</view>
</view>
</template>
<script>
import props from './props.js';
import mixin from '../../libs/mixin/mixin'
import mpMixin from '../../libs/mixin/mpMixin';
/**
* AvatarGroup 头像组
* @description 本组件一般用于展示头像的地方,如个人中心,或者评论列表页的用户头像展示等场所。
* @tutorial https://uview.d3u.cn/components/avatar.html
*
* @property {Array} urls 头像图片组 (默认 [] )
* @property {String | Number} maxCount 最多展示的头像数量 ( 默认 5 )
* @property {String} shape 头像形状( 'circle' (默认) | 'square' )
* @property {String} mode 图片裁剪模式(默认 'scaleToFill' )
* @property {Boolean} showMore 超出maxCount时是否显示查看更多的提示 (默认 true )
* @property {String | Number} size 头像大小 (默认 40 )
* @property {String} keyName 指定从数组的对象元素中读取哪个属性作为图片地址
* @property {String | Number} gap 头像之间的遮挡比例(0.4代表遮挡40%) (默认 0.5 )
* @property {String | Number} extraValue 需额外显示的值
* @event {Function} showMore 头像组更多点击
* @example <u-avatar-group:urls="urls" size="35" gap="0.4" ></u-avatar-group:urls=>
*/
export default {
name: 'u-avatar-group',
mixins: [mpMixin, mixin, props],
data() {
return {
}
},
computed: {
showUrl() {
return this.urls.slice(0, this.maxCount)
}
},
// #ifdef VUE3
emits: ["showMore"],
// #endif
methods: {
clickHandler() {
this.$emit('showMore')
}
},
}
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
.u-avatar-group {
@include flex;
&__item {
margin-left: -10px;
position: relative;
&--no-indent {
// 如果你想质疑作者不会使用:first-child,说明你太年轻,因为nvue不支持
margin-left: 0;
}
&__show-more {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
background-color: rgba(0, 0, 0, 0.3);
@include flex;
align-items: center;
justify-content: center;
border-radius: 100px;
}
}
}
</style>
@@ -0,0 +1,78 @@
export default {
props: {
// 头像图片路径(不能为相对路径)
src: {
type: String,
default: ()=> uni.$u.props.avatar.src
},
// 头像形状,circle-圆形,square-方形
shape: {
type: String,
default: ()=> uni.$u.props.avatar.shape
},
// 头像尺寸
size: {
type: [String, Number],
default: ()=> uni.$u.props.avatar.size
},
// 裁剪模式
mode: {
type: String,
default: ()=> uni.$u.props.avatar.mode
},
// 显示的文字
text: {
type: String,
default: ()=> uni.$u.props.avatar.text
},
// 背景色
bgColor: {
type: String,
default: ()=> uni.$u.props.avatar.bgColor
},
// 文字颜色
color: {
type: String,
default: ()=> uni.$u.props.avatar.color
},
// 文字大小
fontSize: {
type: [String, Number],
default: ()=> uni.$u.props.avatar.fontSize
},
// 显示的图标
icon: {
type: String,
default: ()=> uni.$u.props.avatar.icon
},
// 显示小程序头像,只对百度,微信,QQ小程序有效
mpAvatar: {
type: Boolean,
default: ()=> uni.$u.props.avatar.mpAvatar
},
// 是否使用随机背景色
randomBgColor: {
type: Boolean,
default: ()=> uni.$u.props.avatar.randomBgColor
},
// 加载失败的默认头像(组件有内置默认图片)
defaultUrl: {
type: String,
default: ()=> uni.$u.props.avatar.defaultUrl
},
// 如果配置了randomBgColor为true,且配置了此值,则从默认的背景色数组中取出对应索引的颜色值,取值0-19之间
colorIndex: {
type: [String, Number],
// 校验参数规则,索引在0-19之间
validator(n) {
return uni.$u.test.range(n, [0, 19]) || n === ''
},
default: ()=> uni.$u.props.avatar.colorIndex
},
// 组件标识符
name: {
type: String,
default: ()=> uni.$u.props.avatar.name
}
}
}
@@ -0,0 +1,178 @@
<template>
<view
class="u-avatar"
:class="[`u-avatar--${shape}`]"
:style="[{
backgroundColor: (text || icon) ? (randomBgColor ? colors[colorIndex !== '' ? colorIndex : $u.random(0, 19)] : bgColor) : 'transparent',
width: $u.addUnit(size),
height: $u.addUnit(size),
}, $u.addStyle(customStyle)]"
@tap="clickHandler"
>
<slot>
<!-- #ifdef MP-WEIXIN || MP-QQ || MP-BAIDU -->
<open-data
v-if="mpAvatar && allowMp"
type="userAvatarUrl"
:style="[{
width: $u.addUnit(size),
height: $u.addUnit(size)
}]"
/>
<!-- #endif -->
<!-- #ifndef MP-WEIXIN && MP-QQ && MP-BAIDU -->
<template v-if="mpAvatar && allowMp"></template>
<!-- #endif -->
<u-icon
v-else-if="icon"
:name="icon"
:size="fontSize"
:color="color"
></u-icon>
<u--text
v-else-if="text"
:text="text"
:size="fontSize"
:color="color"
align="center"
customStyle="justify-content: center"
></u--text>
<image
class="u-avatar__image"
v-else
:class="[`u-avatar__image--${shape}`]"
:src="avatarUrl || defaultUrl"
:mode="mode"
@error="errorHandler"
:style="[{
width: $u.addUnit(size),
height: $u.addUnit(size)
}]"
></image>
</slot>
</view>
</template>
<script>
import props from './props.js';
import mixin from '../../libs/mixin/mixin'
import mpMixin from '../../libs/mixin/mpMixin';
const base64Avatar =
"data:image/jpg;base64,/9j/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/sABFEdWNreQABAAQAAAA8AAD/4QMraHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLwA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/PiA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjMtYzAxMSA2Ni4xNDU2NjEsIDIwMTIvMDIvMDYtMTQ6NTY6MjcgICAgICAgICI+IDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+IDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bXA6Q3JlYXRvclRvb2w9IkFkb2JlIFBob3Rvc2hvcCBDUzYgKFdpbmRvd3MpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjREMEQwRkY0RjgwNDExRUE5OTY2RDgxODY3NkJFODMxIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjREMEQwRkY1RjgwNDExRUE5OTY2RDgxODY3NkJFODMxIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6NEQwRDBGRjJGODA0MTFFQTk5NjZEODE4Njc2QkU4MzEiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6NEQwRDBGRjNGODA0MTFFQTk5NjZEODE4Njc2QkU4MzEiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7/7gAOQWRvYmUAZMAAAAAB/9sAhAAGBAQEBQQGBQUGCQYFBgkLCAYGCAsMCgoLCgoMEAwMDAwMDBAMDg8QDw4MExMUFBMTHBsbGxwfHx8fHx8fHx8fAQcHBw0MDRgQEBgaFREVGh8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx//wAARCADIAMgDAREAAhEBAxEB/8QAcQABAQEAAwEBAAAAAAAAAAAAAAUEAQMGAgcBAQAAAAAAAAAAAAAAAAAAAAAQAAIBAwICBgkDBQAAAAAAAAABAhEDBCEFMVFBYXGREiKBscHRMkJSEyOh4XLxYjNDFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8A/fAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHbHFyZ/Dam+yLA+Z2L0Pjtyj2poD4AAAAAAAAAAAAAAAAAAAAAAAAKWFs9y6lcvvwQeqj8z9wFaziY1n/HbUX9XF97A7QAGXI23EvJ1goyfzR0YEfN269jeZ+a03pNe0DIAAAAAAAAAAAAAAAAAAAACvtO3RcVkXlWutuL9YFYAAAAAOJRjKLjJVi9GmB5/csH/mu1h/in8PU+QGMAAAAAAAAAAAAAAAAAAaMDG/6MmMH8C80+xAelSSVFolwQAAAAAAAHVlWI37ErUulaPk+hgeYnCUJuElSUXRrrQHAAAAAAAAAAAAAAAAABa2Oz4bM7r4zdF2ICmAAAAAAAAAg7zZ8GX41wuJP0rRgYAAAAAAAAAAAAAAAAAD0m2R8ODaXU33tsDSAAAAAAAAAlb9HyWZcnJd9PcBHAAAAAAAAAAAAAAAAAPS7e64Vn+KA0AAAAAAAAAJm+v8Ftf3ewCKAAAAAAAAAAAAAAAAAX9muqeGo9NttP06+0DcAAAAAAAAAjb7dTu2ra+VOT9P8AQCWAAAAAAAAAAAAAAAAAUNmyPt5Ltv4bui/kuAF0AAAAAAADiUlGLlJ0SVW+oDzOXfd/Ind6JPRdS0QHSAAAAAAAAAAAAAAAAAE2nVaNcGB6Lbs6OTao9LsF51z60BrAAAAAABJ3jOVHjW3r/sa9QEgAAAAAAAAAAAAAAAAAAAPu1duWriuW34ZR4MC9hbnZyEoy8l36XwfYBsAAADaSq9EuLAlZ+7xSdrGdW9Hc5dgEdtt1erfFgAAAAAAAAAAAAAAAAADVjbblX6NR8MH80tEBRs7HYivyzlN8lovaBPzduvY0m6eK10TXtAyAarO55lpJK54orolr+4GqO/Xaea1FvqbXvA+Z77kNeW3GPbV+4DJfzcm/pcm3H6Vou5AdAFLC2ed2Pjv1txa8sV8T6wOL+yZEKu1JXFy4MDBOE4ScZxcZLinoB8gAAAAAAAAAAAB242LeyJ+C3GvN9C7QLmJtePYpKS+5c+p8F2IDYAANJqj1T4oCfk7Nj3G5Wn9qXJax7gJ93Z82D8sVNc4v30A6Xg5i42Z+iLfqARwcyT0sz9MWvWBps7LlTf5Grce9/oBTxdtxseklHxT+uWr9AGoAB138ezfj4bsFJdD6V2MCPm7RdtJzs1uW1xXzL3gTgAAAAAAAAADRhYc8q74I6RWs5ckB6GxYtWLat21SK731sDsAAAAAAAAAAAAAAAASt021NO/YjrxuQXT1oCOAAAAAAABzGLlJRSq26JAelwsWONYjbXxcZvmwO8AAAAAAAAAAAAAAAAAAef3TEWPkVivx3NY9T6UBiAAAAAABo2+VmGXblddIJ8eivRUD0oAAAAAAAAAAAAAAAAAAAYt4tKeFKVNYNSXfRgefAAAAAAAAr7VuSSWPedKaW5v1MCsAAAAAAAAAAAAAAAAAAIe6bj96Ts2n+JPzSXzP3ATgAAAAAAAAFbbt1UUrOQ9FpC4/UwK6aaqtU+DAAAAAAAAAAAAAAA4lKMIuUmoxWrb4ARNx3R3q2rLpa4Sl0y/YCcAAAAAAAAAAANmFud7G8r89r6X0dgFvGzLGRGtuWvTF6NAdwAAAAAAAAAAAy5W442PVN+K59EePp5ARMvOv5MvO6QXCC4AZwAAAAAAAAAAAAAcxlKLUotprg1owN+PvORborq+7Hnwl3gUbO74VzRydt8pKn68ANcJwmqwkpLmnUDkAAAAfNy9atqtyagut0AxXt5xIV8Fbj6lRd7Am5G65V6qUvtwfyx94GMAAAAAAAAAAAAAAAAAAAOU2nVOj5gdsc3LiqRvTpyqwOxbnnrhdfpSfrQB7pnv/AGvuS9gHXPMy5/Fem1yq0v0A6W29XqwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf//Z";
/**
* Avatar 头像
* @description 本组件一般用于展示头像的地方,如个人中心,或者评论列表页的用户头像展示等场所。
* @tutorial https://uview.d3u.cn/components/avatar.html
*
* @property {String} src 头像路径,如加载失败,将会显示默认头像(不能为相对路径)
* @property {String} shape 头像形状 ( circle (默认) | square)
* @property {String | Number} size 头像尺寸,可以为指定字符串(large, default, mini),或者数值 (默认 40 )
* @property {String} mode 头像图片的裁剪类型,与uni的image组件的mode参数一致,如效果达不到需求,可尝试传widthFix值 (默认 'scaleToFill' )
* @property {String} text 用文字替代图片,级别优先于src
* @property {String} bgColor 背景颜色,一般显示文字时用 (默认 '#c0c4cc' )
* @property {String} color 文字颜色 (默认 '#ffffff' )
* @property {String | Number} fontSize 文字大小 (默认 18 )
* @property {String} icon 显示的图标
* @property {Boolean} mpAvatar 显示小程序头像,只对百度,微信,QQ小程序有效 (默认 false )
* @property {Boolean} randomBgColor 是否使用随机背景色 (默认 false )
* @property {String} defaultUrl 加载失败的默认头像(组件有内置默认图片)
* @property {String | Number} colorIndex 如果配置了randomBgColor为true,且配置了此值,则从默认的背景色数组中取出对应索引的颜色值,取值0-19之间
* @property {String} name 组件标识符 (默认 'level' )
* @property {Object} customStyle 定义需要用到的外部样式
*
* @event {Function} click 点击组件时触发 index: 用户传递的标识符
* @example <u-avatar :src="src" mode="square"></u-avatar>
*/
export default {
name: 'u-avatar',
mixins: [mpMixin, mixin, props],
data() {
return {
// 如果配置randomBgColor参数为true,在图标或者文字的模式下,会随机从中取出一个颜色值当做背景色
colors: ['#ffb34b', '#f2bba9', '#f7a196', '#f18080', '#88a867', '#bfbf39', '#89c152', '#94d554', '#f19ec2',
'#afaae4', '#e1b0df', '#c38cc1', '#72dcdc', '#9acdcb', '#77b1cc', '#448aca', '#86cefa', '#98d1ee',
'#73d1f1',
'#80a7dc'
],
avatarUrl: this.src,
allowMp: false
}
},
watch: {
// 监听头像src的变化,赋值给内部的avatarUrl变量,因为图片加载失败时,需要修改图片的src为默认值
// 而组件内部不能直接修改props的值,所以需要一个中间变量
src: {
immediate: true,
handler(newVal) {
this.avatarUrl = newVal
// 如果没有传src,则主动触发error事件,用于显示默认的头像,否则src为''空字符等的时候,会无内容展示
if(!newVal) {
this.errorHandler()
}
}
}
},
computed: {
imageStyle() {
const style = {}
return style
}
},
created() {
this.init()
},
// #ifdef VUE3
emits: ["click"],
// #endif
methods: {
init() {
// 目前只有这几个小程序平台具有open-data标签
// 其他平台可以通过uni.getUserInfo类似接口获取信息,但是需要弹窗授权(首次),不合符组件逻辑
// 故目前自动获取小程序头像只支持这几个平台
// #ifdef MP-WEIXIN || MP-QQ || MP-BAIDU
this.allowMp = true
// #endif
},
// 判断传入的name属性,是否图片路径,只要带有"/"均认为是图片形式
isImg() {
return this.src.indexOf('/') !== -1
},
// 图片加载时失败时触发
errorHandler() {
this.avatarUrl = this.defaultUrl || base64Avatar
},
clickHandler() {
this.$emit('click', this.name)
}
}
}
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
.u-avatar {
@include flex;
align-items: center;
justify-content: center;
&--circle {
border-radius: 100px;
}
&--square {
border-radius: 4px;
}
&__image {
&--circle {
border-radius: 100px;
overflow: hidden;
}
&--square {
border-radius: 4px;
}
}
}
</style>
@@ -0,0 +1,54 @@
export default {
props: {
// 返回顶部的形状,circle-圆形,square-方形
mode: {
type: String,
default: ()=> uni.$u.props.backtop.mode
},
// 自定义图标
icon: {
type: String,
default: ()=> uni.$u.props.backtop.icon
},
// 提示文字
text: {
type: String,
default: ()=> uni.$u.props.backtop.text
},
// 返回顶部滚动时间
duration: {
type: [String, Number],
default: ()=> uni.$u.props.backtop.duration
},
// 滚动距离
scrollTop: {
type: [String, Number],
default: ()=> uni.$u.props.backtop.scrollTop
},
// 距离顶部多少距离显示,单位px
top: {
type: [String, Number],
default: ()=> uni.$u.props.backtop.top
},
// 返回顶部按钮到底部的距离,单位px
bottom: {
type: [String, Number],
default: ()=> uni.$u.props.backtop.bottom
},
// 返回顶部按钮到右边的距离,单位px
right: {
type: [String, Number],
default: ()=> uni.$u.props.backtop.right
},
// 层级
zIndex: {
type: [String, Number],
default: ()=> uni.$u.props.backtop.zIndex
},
// 图标的样式,对象形式
iconStyle: {
type: Object,
default: ()=> uni.$u.props.backtop.iconStyle
}
}
}
@@ -0,0 +1,134 @@
<template>
<u-transition
mode="fade"
:customStyle="backTopStyle"
:show="show"
>
<view
class="u-back-top"
:style="[contentStyle]"
v-if="!$slots.default && !$slots.$default"
@click="backToTop"
>
<u-icon
:name="icon"
:custom-style="iconStyle"
></u-icon>
<text
v-if="text"
class="u-back-top__text"
>{{text}}</text>
</view>
<slot v-else />
</u-transition>
</template>
<script>
import props from './props.js';
import mixin from '../../libs/mixin/mixin'
import mpMixin from '../../libs/mixin/mpMixin';
// #ifdef APP-NVUE
const dom = weex.requireModule('dom')
// #endif
/**
* backTop 返回顶部
* @description 本组件一个用于长页面,滑动一定距离后,出现返回顶部按钮,方便快速返回顶部的场景。
* @tutorial https://uviewui.com/components/backTop.html
*
* @property {String} mode 返回顶部的形状,circle-圆形,square-方形 (默认 'circle' )
* @property {String} icon 自定义图标 (默认 'arrow-upward' ) 见官方文档示例
* @property {String} text 提示文字
* @property {String | Number} duration 返回顶部滚动时间 (默认 100)
* @property {String | Number} scrollTop 滚动距离 (默认 0 )
* @property {String | Number} top 距离顶部多少距离显示,单位px (默认 400 )
* @property {String | Number} bottom 返回顶部按钮到底部的距离,单位px (默认 100 )
* @property {String | Number} right 返回顶部按钮到右边的距离,单位px (默认 20 )
* @property {String | Number} zIndex 层级 (默认 9 )
* @property {Object<Object>} iconStyle 图标的样式,对象形式 (默认 {color: '#909399',fontSize: '19px'})
* @property {Object} customStyle 定义需要用到的外部样式
*
* @example <u-back-top :scrollTop="scrollTop"></u-back-top>
*/
export default {
name: 'u-back-top',
mixins: [mpMixin, mixin, props],
computed: {
backTopStyle() {
// 动画组件样式
const style = {
bottom: uni.$u.addUnit(this.bottom),
right: uni.$u.addUnit(this.right),
width: '40px',
height: '40px',
position: 'fixed',
zIndex: 10,
}
return style
},
show() {
return uni.$u.getPx(this.scrollTop) > uni.$u.getPx(this.top)
},
contentStyle() {
const style = {}
let radius = 0
// 是否圆形
if(this.mode === 'circle') {
radius = '100px'
} else {
radius = '4px'
}
// 为了兼容安卓nvue,只能这么分开写
style.borderTopLeftRadius = radius
style.borderTopRightRadius = radius
style.borderBottomLeftRadius = radius
style.borderBottomRightRadius = radius
return uni.$u.deepMerge(style, uni.$u.addStyle(this.customStyle))
}
},
// #ifdef VUE3
emits: ["click"],
// #endif
methods: {
backToTop() {
// #ifdef APP-NVUE
if (!this.$parent.$refs['u-back-top']) {
uni.$u.error(`nvue页面需要给页面最外层元素设置"ref='u-back-top'`)
}
dom.scrollToElement(this.$parent.$refs['u-back-top'], {
offset: 0
})
// #endif
// #ifndef APP-NVUE
uni.pageScrollTo({
scrollTop: 0,
duration: this.duration
});
// #endif
this.$emit('click')
}
}
}
</script>
<style lang="scss" scoped>
@import '../../libs/css/components.scss';
$u-back-top-flex:1 !default;
$u-back-top-height:100% !default;
$u-back-top-background-color:#E1E1E1 !default;
$u-back-top-tips-font-size:12px !default;
.u-back-top {
@include flex;
flex-direction: column;
align-items: center;
flex:$u-back-top-flex;
height: $u-back-top-height;
justify-content: center;
background-color: $u-back-top-background-color;
&__tips {
font-size:$u-back-top-tips-font-size;
transform: scale(0.8);
}
}
</style>
@@ -0,0 +1,78 @@
export default {
props: {
// 是否显示圆点
isDot: {
type: Boolean,
default: ()=> uni.$u.props.badge.isDot
},
// 显示的内容
value: {
type: [Number, String],
default: ()=> uni.$u.props.badge.value
},
// #ifdef VUE3
modelValue: {
type: [Number, String],
default: ()=> uni.$u.props.badge.value
},
// #endif
// 是否显示
show: {
type: Boolean,
default: ()=> uni.$u.props.badge.show
},
// 最大值,超过最大值会显示 '{max}+'
max: {
type: [Number, String],
default: ()=> uni.$u.props.badge.max
},
// 主题类型,error|warning|success|primary
type: {
type: String,
default: ()=> uni.$u.props.badge.type
},
// 当数值为 0 时,是否展示 Badge
showZero: {
type: Boolean,
default: ()=> uni.$u.props.badge.showZero
},
// 背景颜色,优先级比type高,如设置,type参数会失效
bgColor: {
type: String,
default: ()=> uni.$u.props.badge.bgColor
},
// 字体颜色
color: {
type: String,
default: ()=> uni.$u.props.badge.color
},
// 徽标形状,circle-四角均为圆角,horn-左下角为直角
shape: {
type: String,
default: ()=> uni.$u.props.badge.shape
},
// 设置数字的显示方式,overflow|ellipsis|limit
// overflow会根据max字段判断,超出显示`${max}+`
// ellipsis会根据max判断,超出显示`${max}...`
// limit会依据1000作为判断条件,超出1000,显示`${value/1000}K`,比如2.2k、3.34w,最多保留2位小数
numberType: {
type: String,
default: ()=> uni.$u.props.badge.numberType
},
// 设置badge的位置偏移,格式为 [x, y],也即设置的为top和right的值,absolute为true时有效
offset: {
type: Array,
default: ()=> uni.$u.props.badge.offset
},
// 是否反转背景和字体颜色
inverted: {
type: Boolean,
default: ()=> uni.$u.props.badge.inverted
},
// 是否绝对定位
absolute: {
type: Boolean,
default: ()=> uni.$u.props.badge.absolute
}
}
}
@@ -0,0 +1,181 @@
<template>
<text
v-if="show && ((currentValue === 0 ? showZero : true) || isDot)"
:class="[isDot ? 'u-badge--dot' : 'u-badge--not-dot', inverted && 'u-badge--inverted', shape === 'horn' && 'u-badge--horn', `u-badge--${type}${inverted ? '--inverted' : ''}`]"
:style="[$u.addStyle(customStyle), badgeStyle]"
class="u-badge"
>{{ isDot ? '' :showValue }}</text>
</template>
<script>
import props from './props.js';
import mixin from '../../libs/mixin/mixin'
import mpMixin from '../../libs/mixin/mpMixin';
/**
* badge 徽标数
* @description 该组件一般用于图标右上角显示未读的消息数量,提示用户点击,有圆点和圆包含文字两种形式。
* @tutorial https://uviewui.com/components/badge.html
*
* @property {Boolean} isDot 是否显示圆点 (默认 false )
* @property {String | Number} value 显示的内容
* @property {Boolean} show 是否显示 (默认 true )
* @property {String | Number} max 最大值,超过最大值会显示 '{max}+' (默认999)
* @property {String} type 主题类型,error|warning|success|primary (默认 'error' )
* @property {Boolean} showZero 当数值为 0 时,是否展示 Badge (默认 false )
* @property {String} bgColor 背景颜色,优先级比type高,如设置,type参数会失效
* @property {String} color 字体颜色 (默认 '#ffffff' )
* @property {String} shape 徽标形状,circle-四角均为圆角,horn-左下角为直角 (默认 'circle' )
* @property {String} numberType 设置数字的显示方式,overflow|ellipsis|limit (默认 'overflow' )
* @property {Array}} offset 设置badge的位置偏移,格式为 [x, y],也即设置的为top和right的值,absolute为true时有效
* @property {Boolean} inverted 是否反转背景和字体颜色(默认 false )
* @property {Boolean} absolute 是否绝对定位(默认 false )
* @property {Object} customStyle 定义需要用到的外部样式
* @example <u-badge :type="type" :count="count"></u-badge>
*/
export default {
name: 'u-badge',
mixins: [mpMixin, mixin, props],
computed: {
currentValue() {
// #ifdef VUE2
return Number(this.value);
// #endif
// #ifdef VUE3
return Number(this.modelValue || this.value);
// #endif
},
// 是否将badge中心与父组件右上角重合
boxStyle() {
let style = {};
return style;
},
// 整个组件的样式
badgeStyle() {
const style = {}
if(this.color) {
style.color = this.color
}
if (this.bgColor && !this.inverted) {
style.backgroundColor = this.bgColor
}
if (this.absolute) {
style.position = 'absolute'
// 如果有设置offset参数
if(this.offset.length) {
// top和right分为为offset的第一个和第二个值,如果没有第二个值,则right等于top
const top = this.offset[0]
const right = this.offset[1] || top
style.top = uni.$u.addUnit(top)
style.right = uni.$u.addUnit(right)
}
}
return style
},
showValue() {
switch (this.numberType) {
case "overflow":
return this.currentValue > Number(this.max) ? this.max + "+" : this.modelValue || this.value
break;
case "ellipsis":
return this.currentValue > Number(this.max) ? "..." : this.modelValue || this.value
break;
case "limit":
return this.currentValue > 999 ? this.currentValue >= 9999 ?
Math.floor(this.modelValue || this.value / 1e4 * 100) / 100 + "w" : Math.floor(this.modelValue || this.value /
1e3 * 100) / 100 + "k" : this.modelValue || this.value
break;
default:
return this.currentValue
}
},
}
}
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
$u-badge-primary: $u-primary !default;
$u-badge-error: $u-error !default;
$u-badge-success: $u-success !default;
$u-badge-info: $u-info !default;
$u-badge-warning: $u-warning !default;
$u-badge-dot-radius: 100px !default;
$u-badge-dot-size: 8px !default;
$u-badge-dot-right: 4px !default;
$u-badge-dot-top: 0 !default;
$u-badge-text-font-size: 11px !default;
$u-badge-text-right: 10px !default;
$u-badge-text-padding: 2px 5px !default;
$u-badge-text-align: center !default;
$u-badge-text-color: #FFFFFF !default;
.u-badge {
border-top-right-radius: $u-badge-dot-radius;
border-top-left-radius: $u-badge-dot-radius;
border-bottom-left-radius: $u-badge-dot-radius;
border-bottom-right-radius: $u-badge-dot-radius;
@include flex;
line-height: $u-badge-text-font-size;
text-align: $u-badge-text-align;
font-size: $u-badge-text-font-size;
color: $u-badge-text-color;
&--dot {
height: $u-badge-dot-size;
width: $u-badge-dot-size;
}
&--inverted {
font-size: 13px;
}
&--not-dot {
padding: $u-badge-text-padding;
}
&--horn {
border-bottom-left-radius: 0;
}
&--primary {
background-color: $u-badge-primary;
}
&--primary--inverted {
color: $u-badge-primary;
}
&--error {
background-color: $u-badge-error;
}
&--error--inverted {
color: $u-badge-error;
}
&--success {
background-color: $u-badge-success;
}
&--success--inverted {
color: $u-badge-success;
}
&--info {
background-color: $u-badge-info;
}
&--info--inverted {
color: $u-badge-info;
}
&--warning {
background-color: $u-badge-warning;
}
&--warning--inverted {
color: $u-badge-warning;
}
}
</style>
@@ -0,0 +1,208 @@
// Import all the barcodes
import barcodes from './barcodes/';
// Help functions
import merge from './help/merge.js';
import linearizeEncodings from './help/linearizeEncodings.js';
import fixOptions from './help/fixOptions.js';
import getRenderProperties from './help/getRenderProperties.js';
import optionsFromStrings from './help/optionsFromStrings.js';
// Exceptions
import ErrorHandler from './exceptions/ErrorHandler.js';
import {InvalidInputException, NoElementException} from './exceptions/exceptions.js';
// Default values
import defaults from './options/defaults.js';
// The protype of the object returned from the JsBarcode() call
let API = function(){};
// The first call of the library API
// Will return an object with all barcodes calls and the data that is used
// by the renderers
let JsBarcode = function(element, text, options){
var api = new API();
if(typeof element === "undefined"){
throw Error("No element to render on was provided.");
}
// Variables that will be pased through the API calls
api._renderProperties = getRenderProperties(element);
api._encodings = [];
api._options = defaults;
api._errorHandler = new ErrorHandler(api);
// If text is set, use the simple syntax (render the barcode directly)
if(typeof text !== "undefined"){
options = options || {};
if(!options.format){
options.format = autoSelectBarcode();
}
api.options(options)[options.format](text, options).render();
}
return api;
};
// To make tests work TODO: remove
JsBarcode.getModule = function(name){
return barcodes[name];
};
// Register all barcodes
for(var name in barcodes){
if(barcodes.hasOwnProperty(name)){ // Security check if the propery is a prototype property
registerBarcode(barcodes, name);
}
}
function registerBarcode(barcodes, name){
API.prototype[name] =
API.prototype[name.toUpperCase()] =
API.prototype[name.toLowerCase()] =
function(text, options){
var api = this;
return api._errorHandler.wrapBarcodeCall(function(){
// Ensure text is options.text
options.text = typeof options.text === 'undefined' ? undefined : '' + options.text;
var newOptions = merge(api._options, options);
newOptions = optionsFromStrings(newOptions);
var Encoder = barcodes[name];
var encoded = encode(text, Encoder, newOptions);
api._encodings.push(encoded);
return api;
});
};
}
// encode() handles the Encoder call and builds the binary string to be rendered
function encode(text, Encoder, options){
// Ensure that text is a string
text = "" + text;
var encoder = new Encoder(text, options);
// If the input is not valid for the encoder, throw error.
// If the valid callback option is set, call it instead of throwing error
if(!encoder.valid()){
throw new InvalidInputException(encoder.constructor.name, text);
}
// Make a request for the binary data (and other infromation) that should be rendered
var encoded = encoder.encode();
// Encodings can be nestled like [[1-1, 1-2], 2, [3-1, 3-2]
// Convert to [1-1, 1-2, 2, 3-1, 3-2]
encoded = linearizeEncodings(encoded);
// Merge
for(let i = 0; i < encoded.length; i++){
encoded[i].options = merge(options, encoded[i].options);
}
return encoded;
}
function autoSelectBarcode(){
// If CODE128 exists. Use it
if(barcodes["CODE128"]){
return "CODE128";
}
// Else, take the first (probably only) barcode
return Object.keys(barcodes)[0];
}
// Sets global encoder options
// Added to the api by the JsBarcode function
API.prototype.options = function(options){
this._options = merge(this._options, options);
return this;
};
// Will create a blank space (usually in between barcodes)
API.prototype.blank = function(size){
const zeroes = new Array(size + 1).join("0");
this._encodings.push({data: zeroes});
return this;
};
// Initialize JsBarcode on all HTML elements defined.
API.prototype.init = function(){
// Should do nothing if no elements where found
if(!this._renderProperties){
return;
}
// Make sure renderProperies is an array
if(!Array.isArray(this._renderProperties)){
this._renderProperties = [this._renderProperties];
}
var renderProperty;
for(let i in this._renderProperties){
renderProperty = this._renderProperties[i];
var options = merge(this._options, renderProperty.options);
if(options.format == "auto"){
options.format = autoSelectBarcode();
}
this._errorHandler.wrapBarcodeCall(function(){
var text = options.value;
var Encoder = barcodes[options.format.toUpperCase()];
var encoded = encode(text, Encoder, options);
render(renderProperty, encoded, options);
});
}
};
// The render API call. Calls the real render function.
API.prototype.render = function(){
if(!this._renderProperties){
throw new NoElementException();
}
if(Array.isArray(this._renderProperties)){
for(var i = 0; i < this._renderProperties.length; i++){
render(this._renderProperties[i], this._encodings, this._options);
}
}
else{
render(this._renderProperties, this._encodings, this._options);
}
return this;
};
API.prototype._defaults = defaults;
// Prepares the encodings and calls the renderer
function render(renderProperties, encodings, options){
encodings = linearizeEncodings(encodings);
for(let i = 0; i < encodings.length; i++){
encodings[i].options = merge(options, encodings[i].options);
fixOptions(encodings[i].options);
}
fixOptions(options);
var Renderer = renderProperties.renderer;
var renderer = new Renderer(renderProperties.element, encodings, options);
renderer.render();
if(renderProperties.afterRender){
renderProperties.afterRender();
}
}
// Export to commonJS
export default JsBarcode;
@@ -0,0 +1,9 @@
class Barcode{
constructor(data, options){
this.data = data;
this.text = options.text || data;
this.options = options;
}
}
export default Barcode;
@@ -0,0 +1,127 @@
import Barcode from "../Barcode.js";
import { SHIFT, SET_A, SET_B, MODULO, STOP, FNC1, SET_BY_CODE, SWAP, BARS } from './constants';
// This is the master class,
// it does require the start code to be included in the string
class CODE128 extends Barcode {
constructor(data, options) {
super(data.substring(1), options);
// Get array of ascii codes from data
this.bytes = data.split('')
.map(char => char.charCodeAt(0));
}
valid() {
// ASCII value ranges 0-127, 200-211
return /^[\x00-\x7F\xC8-\xD3]+$/.test(this.data);
}
// The public encoding function
encode() {
const bytes = this.bytes;
// Remove the start code from the bytes and set its index
const startIndex = bytes.shift() - 105;
// Get start set by index
const startSet = SET_BY_CODE[startIndex];
if (startSet === undefined) {
throw new RangeError('The encoding does not start with a start character.');
}
if (this.shouldEncodeAsEan128() === true) {
bytes.unshift(FNC1);
}
// Start encode with the right type
const encodingResult = CODE128.next(bytes, 1, startSet);
return {
text:
this.text === this.data
? this.text.replace(/[^\x20-\x7E]/g, '')
: this.text,
data:
// Add the start bits
CODE128.getBar(startIndex) +
// Add the encoded bits
encodingResult.result +
// Add the checksum
CODE128.getBar((encodingResult.checksum + startIndex) % MODULO) +
// Add the end bits
CODE128.getBar(STOP)
};
}
// GS1-128/EAN-128
shouldEncodeAsEan128() {
let isEAN128 = this.options.ean128 || false;
if (typeof isEAN128 === 'string') {
isEAN128 = isEAN128.toLowerCase() === 'true';
}
return isEAN128;
}
// Get a bar symbol by index
static getBar(index) {
return BARS[index] ? BARS[index].toString() : '';
}
// Correct an index by a set and shift it from the bytes array
static correctIndex(bytes, set) {
if (set === SET_A) {
const charCode = bytes.shift();
return charCode < 32 ? charCode + 64 : charCode - 32;
} else if (set === SET_B) {
return bytes.shift() - 32;
} else {
return (bytes.shift() - 48) * 10 + bytes.shift() - 48;
}
}
static next(bytes, pos, set) {
if (!bytes.length) {
return { result: '', checksum: 0 };
}
let nextCode, index;
// Special characters
if (bytes[0] >= 200){
index = bytes.shift() - 105;
const nextSet = SWAP[index];
// Swap to other set
if (nextSet !== undefined) {
nextCode = CODE128.next(bytes, pos + 1, nextSet);
}
// Continue on current set but encode a special character
else {
// Shift
if ((set === SET_A || set === SET_B) && index === SHIFT) {
// Convert the next character so that is encoded correctly
bytes[0] = (set === SET_A)
? bytes[0] > 95 ? bytes[0] - 96 : bytes[0]
: bytes[0] < 32 ? bytes[0] + 96 : bytes[0];
}
nextCode = CODE128.next(bytes, pos + 1, set);
}
}
// Continue encoding
else {
index = CODE128.correctIndex(bytes, set);
nextCode = CODE128.next(bytes, pos + 1, set);
}
// Get the correct binary encoding and calculate the weight
const enc = CODE128.getBar(index);
const weight = index * pos;
return {
result: enc + nextCode.result,
checksum: weight + nextCode.checksum
};
}
}
export default CODE128;
@@ -0,0 +1,14 @@
import CODE128 from './CODE128.js';
import { A_START_CHAR, A_CHARS } from './constants';
class CODE128A extends CODE128 {
constructor(string, options) {
super(A_START_CHAR + string, options);
}
valid() {
return (new RegExp(`^${A_CHARS}+$`)).test(this.data);
}
}
export default CODE128A;
@@ -0,0 +1,14 @@
import CODE128 from './CODE128.js';
import { B_START_CHAR, B_CHARS } from './constants';
class CODE128B extends CODE128 {
constructor(string, options) {
super(B_START_CHAR + string, options);
}
valid() {
return (new RegExp(`^${B_CHARS}+$`)).test(this.data);
}
}
export default CODE128B;
@@ -0,0 +1,14 @@
import CODE128 from './CODE128.js';
import { C_START_CHAR, C_CHARS } from './constants';
class CODE128C extends CODE128 {
constructor(string, options) {
super(C_START_CHAR + string, options);
}
valid() {
return (new RegExp(`^${C_CHARS}+$`)).test(this.data);
}
}
export default CODE128C;
@@ -0,0 +1,15 @@
import CODE128 from './CODE128';
import autoSelectModes from './auto';
class CODE128AUTO extends CODE128{
constructor(data, options){
// ASCII value ranges 0-127, 200-211
if (/^[\x00-\x7F\xC8-\xD3]+$/.test(data)) {
super(autoSelectModes(data), options);
} else{
super(data, options);
}
}
}
export default CODE128AUTO;
@@ -0,0 +1,68 @@
import { A_START_CHAR, B_START_CHAR, C_START_CHAR, A_CHARS, B_CHARS, C_CHARS } from './constants';
// Match Set functions
const matchSetALength = (string) => string.match(new RegExp(`^${A_CHARS}*`))[0].length;
const matchSetBLength = (string) => string.match(new RegExp(`^${B_CHARS}*`))[0].length;
const matchSetC = (string) => string.match(new RegExp(`^${C_CHARS}*`))[0];
// CODE128A or CODE128B
function autoSelectFromAB(string, isA){
const ranges = isA ? A_CHARS : B_CHARS;
const untilC = string.match(new RegExp(`^(${ranges}+?)(([0-9]{2}){2,})([^0-9]|$)`));
if (untilC) {
return (
untilC[1] +
String.fromCharCode(204) +
autoSelectFromC(string.substring(untilC[1].length))
);
}
const chars = string.match(new RegExp(`^${ranges}+`))[0];
if (chars.length === string.length) {
return string;
}
return (
chars +
String.fromCharCode(isA ? 205 : 206) +
autoSelectFromAB(string.substring(chars.length), !isA)
);
}
// CODE128C
function autoSelectFromC(string) {
const cMatch = matchSetC(string);
const length = cMatch.length;
if (length === string.length) {
return string;
}
string = string.substring(length);
// Select A/B depending on the longest match
const isA = matchSetALength(string) >= matchSetBLength(string);
return cMatch + String.fromCharCode(isA ? 206 : 205) + autoSelectFromAB(string, isA);
}
// Detect Code Set (A, B or C) and format the string
export default (string) => {
let newString;
const cLength = matchSetC(string).length;
// Select 128C if the string start with enough digits
if (cLength >= 2) {
newString = C_START_CHAR + autoSelectFromC(string);
} else {
// Select A/B depending on the longest match
const isA = matchSetALength(string) > matchSetBLength(string);
newString = (isA ? A_START_CHAR : B_START_CHAR) + autoSelectFromAB(string, isA);
}
return newString.replace(
/[\xCD\xCE]([^])[\xCD\xCE]/, // Any sequence between 205 and 206 characters
(match, char) => String.fromCharCode(203) + char
);
};
@@ -0,0 +1,71 @@
// constants for internal usage
export const SET_A = 0;
export const SET_B = 1;
export const SET_C = 2;
// Special characters
export const SHIFT = 98;
export const START_A = 103;
export const START_B = 104;
export const START_C = 105;
export const MODULO = 103;
export const STOP = 106;
export const FNC1 = 207;
// Get set by start code
export const SET_BY_CODE = {
[START_A]: SET_A,
[START_B]: SET_B,
[START_C]: SET_C,
};
// Get next set by code
export const SWAP = {
101: SET_A,
100: SET_B,
99: SET_C,
};
export const A_START_CHAR = String.fromCharCode(208); // START_A + 105
export const B_START_CHAR = String.fromCharCode(209); // START_B + 105
export const C_START_CHAR = String.fromCharCode(210); // START_C + 105
// 128A (Code Set A)
// ASCII characters 00 to 95 (0–9, A–Z and control codes), special characters, and FNC 1–4
export const A_CHARS = "[\x00-\x5F\xC8-\xCF]";
// 128B (Code Set B)
// ASCII characters 32 to 127 (0–9, A–Z, a–z), special characters, and FNC 1–4
export const B_CHARS = "[\x20-\x7F\xC8-\xCF]";
// 128C (Code Set C)
// 00–99 (encodes two digits with a single code point) and FNC1
export const C_CHARS = "(\xCF*[0-9]{2}\xCF*)";
// CODE128 includes 107 symbols:
// 103 data symbols, 3 start symbols (A, B and C), and 1 stop symbol (the last one)
// Each symbol consist of three black bars (1) and three white spaces (0).
export const BARS = [
11011001100, 11001101100, 11001100110, 10010011000, 10010001100,
10001001100, 10011001000, 10011000100, 10001100100, 11001001000,
11001000100, 11000100100, 10110011100, 10011011100, 10011001110,
10111001100, 10011101100, 10011100110, 11001110010, 11001011100,
11001001110, 11011100100, 11001110100, 11101101110, 11101001100,
11100101100, 11100100110, 11101100100, 11100110100, 11100110010,
11011011000, 11011000110, 11000110110, 10100011000, 10001011000,
10001000110, 10110001000, 10001101000, 10001100010, 11010001000,
11000101000, 11000100010, 10110111000, 10110001110, 10001101110,
10111011000, 10111000110, 10001110110, 11101110110, 11010001110,
11000101110, 11011101000, 11011100010, 11011101110, 11101011000,
11101000110, 11100010110, 11101101000, 11101100010, 11100011010,
11101111010, 11001000010, 11110001010, 10100110000, 10100001100,
10010110000, 10010000110, 10000101100, 10000100110, 10110010000,
10110000100, 10011010000, 10011000010, 10000110100, 10000110010,
11000010010, 11001010000, 11110111010, 11000010100, 10001111010,
10100111100, 10010111100, 10010011110, 10111100100, 10011110100,
10011110010, 11110100100, 11110010100, 11110010010, 11011011110,
11011110110, 11110110110, 10101111000, 10100011110, 10001011110,
10111101000, 10111100010, 11110101000, 11110100010, 10111011110,
10111101110, 11101011110, 11110101110, 11010000100, 11010010000,
11010011100, 1100011101011
];
@@ -0,0 +1,6 @@
import CODE128 from './CODE128_AUTO.js';
import CODE128A from './CODE128A.js';
import CODE128B from './CODE128B.js';
import CODE128C from './CODE128C.js';
export {CODE128, CODE128A, CODE128B, CODE128C};
@@ -0,0 +1,105 @@
// Encoding documentation:
// https://en.wikipedia.org/wiki/Code_39#Encoding
import Barcode from "../Barcode.js";
class CODE39 extends Barcode {
constructor(data, options){
data = data.toUpperCase();
// Calculate mod43 checksum if enabled
if(options.mod43){
data += getCharacter(mod43checksum(data));
}
super(data, options);
}
encode(){
// First character is always a *
var result = getEncoding("*");
// Take every character and add the binary representation to the result
for(let i = 0; i < this.data.length; i++){
result += getEncoding(this.data[i]) + "0";
}
// Last character is always a *
result += getEncoding("*");
return {
data: result,
text: this.text
};
}
valid(){
return this.data.search(/^[0-9A-Z\-\.\ \$\/\+\%]+$/) !== -1;
}
}
// All characters. The position in the array is the (checksum) value
var characters = [
"0", "1", "2", "3",
"4", "5", "6", "7",
"8", "9", "A", "B",
"C", "D", "E", "F",
"G", "H", "I", "J",
"K", "L", "M", "N",
"O", "P", "Q", "R",
"S", "T", "U", "V",
"W", "X", "Y", "Z",
"-", ".", " ", "$",
"/", "+", "%", "*"
];
// The decimal representation of the characters, is converted to the
// corresponding binary with the getEncoding function
var encodings = [
20957, 29783, 23639, 30485,
20951, 29813, 23669, 20855,
29789, 23645, 29975, 23831,
30533, 22295, 30149, 24005,
21623, 29981, 23837, 22301,
30023, 23879, 30545, 22343,
30161, 24017, 21959, 30065,
23921, 22385, 29015, 18263,
29141, 17879, 29045, 18293,
17783, 29021, 18269, 17477,
17489, 17681, 20753, 35770
];
// Get the binary representation of a character by converting the encodings
// from decimal to binary
function getEncoding(character){
return getBinary(characterValue(character));
}
function getBinary(characterValue){
return encodings[characterValue].toString(2);
}
function getCharacter(characterValue){
return characters[characterValue];
}
function characterValue(character){
return characters.indexOf(character);
}
function mod43checksum(data){
var checksum = 0;
for(let i = 0; i < data.length; i++){
checksum += characterValue(data[i]);
}
checksum = checksum % 43;
return checksum;
}
export {CODE39};
@@ -0,0 +1,73 @@
// Encoding documentation:
// https://en.wikipedia.org/wiki/Code_93#Detailed_outline
import { SYMBOLS, BINARIES, MULTI_SYMBOLS } from './constants';
import Barcode from "../Barcode.js";
class CODE93 extends Barcode {
constructor(data, options){
super(data, options);
}
valid(){
return /^[0-9A-Z\-. $/+%]+$/.test(this.data);
}
encode(){
const symbols = this.data
.split('')
.flatMap(c => MULTI_SYMBOLS[c] || c);
const encoded = symbols
.map(s => CODE93.getEncoding(s))
.join('');
// Compute checksum symbols
const csumC = CODE93.checksum(symbols, 20);
const csumK = CODE93.checksum(symbols.concat(csumC), 15);
return {
text: this.text,
data:
// Add the start bits
CODE93.getEncoding('\xff') +
// Add the encoded bits
encoded +
// Add the checksum
CODE93.getEncoding(csumC) + CODE93.getEncoding(csumK) +
// Add the stop bits
CODE93.getEncoding('\xff') +
// Add the termination bit
'1'
};
}
// Get the binary encoding of a symbol
static getEncoding(symbol) {
return BINARIES[CODE93.symbolValue(symbol)];
}
// Get the symbol for a symbol value
static getSymbol(symbolValue) {
return SYMBOLS[symbolValue];
}
// Get the symbol value of a symbol
static symbolValue(symbol) {
return SYMBOLS.indexOf(symbol);
}
// Calculate a checksum symbol
static checksum(symbols, maxWeight) {
const csum = symbols
.slice()
.reverse()
.reduce((sum, symbol, idx) => {
const weight = (idx % maxWeight) + 1;
return sum + (CODE93.symbolValue(symbol) * weight);
}, 0);
return CODE93.getSymbol(csum % 47);
}
}
export default CODE93;
@@ -0,0 +1,16 @@
// Encoding documentation
// https://en.wikipedia.org/wiki/Code_93#Full_ASCII_Code_93
import CODE93 from './CODE93.js';
class CODE93FullASCII extends CODE93 {
constructor(data, options) {
super(data, options);
}
valid() {
return /^[\x00-\x7f]+$/.test(this.data);
}
}
export default CODE93FullASCII;
@@ -0,0 +1,123 @@
// The position in the array is the (checksum) value
export const SYMBOLS = [
'0', '1', '2', '3',
'4', '5', '6', '7',
'8', '9', 'A', 'B',
'C', 'D', 'E', 'F',
'G', 'H', 'I', 'J',
'K', 'L', 'M', 'N',
'O', 'P', 'Q', 'R',
'S', 'T', 'U', 'V',
'W', 'X', 'Y', 'Z',
'-', '.', ' ', '$',
'/', '+', '%',
// Only used for csum and multi-symbols character encodings
'($)', '(%)', '(/)', '(+)',
// Start/Stop
'\xff',
];
// Order matches SYMBOLS array
export const BINARIES = [
'100010100', '101001000', '101000100', '101000010',
'100101000', '100100100', '100100010', '101010000',
'100010010', '100001010', '110101000', '110100100',
'110100010', '110010100', '110010010', '110001010',
'101101000', '101100100', '101100010', '100110100',
'100011010', '101011000', '101001100', '101000110',
'100101100', '100010110', '110110100', '110110010',
'110101100', '110100110', '110010110', '110011010',
'101101100', '101100110', '100110110', '100111010',
'100101110', '111010100', '111010010', '111001010',
'101101110', '101110110', '110101110', '100100110',
'111011010', '111010110', '100110010', '101011110',
];
// Multi-symbol characters (Full ASCII Code 93)
export const MULTI_SYMBOLS = {
'\x00': ['(%)', 'U'],
'\x01': ['($)', 'A'],
'\x02': ['($)', 'B'],
'\x03': ['($)', 'C'],
'\x04': ['($)', 'D'],
'\x05': ['($)', 'E'],
'\x06': ['($)', 'F'],
'\x07': ['($)', 'G'],
'\x08': ['($)', 'H'],
'\x09': ['($)', 'I'],
'\x0a': ['($)', 'J'],
'\x0b': ['($)', 'K'],
'\x0c': ['($)', 'L'],
'\x0d': ['($)', 'M'],
'\x0e': ['($)', 'N'],
'\x0f': ['($)', 'O'],
'\x10': ['($)', 'P'],
'\x11': ['($)', 'Q'],
'\x12': ['($)', 'R'],
'\x13': ['($)', 'S'],
'\x14': ['($)', 'T'],
'\x15': ['($)', 'U'],
'\x16': ['($)', 'V'],
'\x17': ['($)', 'W'],
'\x18': ['($)', 'X'],
'\x19': ['($)', 'Y'],
'\x1a': ['($)', 'Z'],
'\x1b': ['(%)', 'A'],
'\x1c': ['(%)', 'B'],
'\x1d': ['(%)', 'C'],
'\x1e': ['(%)', 'D'],
'\x1f': ['(%)', 'E'],
'!': ['(/)', 'A'],
'"': ['(/)', 'B'],
'#': ['(/)', 'C'],
'&': ['(/)', 'F'],
'\'': ['(/)', 'G'],
'(': ['(/)', 'H'],
')': ['(/)', 'I'],
'*': ['(/)', 'J'],
',': ['(/)', 'L'],
':': ['(/)', 'Z'],
';': ['(%)', 'F'],
'<': ['(%)', 'G'],
'=': ['(%)', 'H'],
'>': ['(%)', 'I'],
'?': ['(%)', 'J'],
'@': ['(%)', 'V'],
'[': ['(%)', 'K'],
'\\': ['(%)', 'L'],
']': ['(%)', 'M'],
'^': ['(%)', 'N'],
'_': ['(%)', 'O'],
'`': ['(%)', 'W'],
'a': ['(+)', 'A'],
'b': ['(+)', 'B'],
'c': ['(+)', 'C'],
'd': ['(+)', 'D'],
'e': ['(+)', 'E'],
'f': ['(+)', 'F'],
'g': ['(+)', 'G'],
'h': ['(+)', 'H'],
'i': ['(+)', 'I'],
'j': ['(+)', 'J'],
'k': ['(+)', 'K'],
'l': ['(+)', 'L'],
'm': ['(+)', 'M'],
'n': ['(+)', 'N'],
'o': ['(+)', 'O'],
'p': ['(+)', 'P'],
'q': ['(+)', 'Q'],
'r': ['(+)', 'R'],
's': ['(+)', 'S'],
't': ['(+)', 'T'],
'u': ['(+)', 'U'],
'v': ['(+)', 'V'],
'w': ['(+)', 'W'],
'x': ['(+)', 'X'],
'y': ['(+)', 'Y'],
'z': ['(+)', 'Z'],
'{': ['(%)', 'P'],
'|': ['(%)', 'Q'],
'}': ['(%)', 'R'],
'~': ['(%)', 'S'],
'\x7f': ['(%)', 'T'],
};
@@ -0,0 +1,4 @@
import CODE93 from './CODE93.js';
import CODE93FullASCII from './CODE93FullASCII.js';
export {CODE93, CODE93FullASCII};
@@ -0,0 +1,72 @@
import { SIDE_BIN, MIDDLE_BIN } from './constants';
import encode from './encoder';
import Barcode from '../Barcode';
// Base class for EAN8 & EAN13
class EAN extends Barcode {
constructor(data, options) {
super(data, options);
// Make sure the font is not bigger than the space between the guard bars
this.fontSize = !options.flat && options.fontSize > options.width * 10
? options.width * 10
: options.fontSize;
// Make the guard bars go down half the way of the text
this.guardHeight = options.height + this.fontSize / 2 + options.textMargin;
}
encode() {
return this.options.flat
? this.encodeFlat()
: this.encodeGuarded();
}
leftText(from, to) {
return this.text.substr(from, to);
}
leftEncode(data, structure) {
return encode(data, structure);
}
rightText(from, to) {
return this.text.substr(from, to);
}
rightEncode(data, structure) {
return encode(data, structure);
}
encodeGuarded() {
const textOptions = { fontSize: this.fontSize };
const guardOptions = { height: this.guardHeight };
return [
{ data: SIDE_BIN, options: guardOptions },
{ data: this.leftEncode(), text: this.leftText(), options: textOptions },
{ data: MIDDLE_BIN, options: guardOptions },
{ data: this.rightEncode(), text: this.rightText(), options: textOptions },
{ data: SIDE_BIN, options: guardOptions },
];
}
encodeFlat() {
const data = [
SIDE_BIN,
this.leftEncode(),
MIDDLE_BIN,
this.rightEncode(),
SIDE_BIN
];
return {
data: data.join(''),
text: this.text
};
}
}
export default EAN;
@@ -0,0 +1,90 @@
// Encoding documentation:
// https://en.wikipedia.org/wiki/International_Article_Number_(EAN)#Binary_encoding_of_data_digits_into_EAN-13_barcode
import { EAN13_STRUCTURE } from './constants';
import EAN from './EAN';
// Calculate the checksum digit
// https://en.wikipedia.org/wiki/International_Article_Number_(EAN)#Calculation_of_checksum_digit
const checksum = (number) => {
const res = number
.substr(0, 12)
.split('')
.map((n) => +n)
.reduce((sum, a, idx) => (
idx % 2 ? sum + a * 3 : sum + a
), 0);
return (10 - (res % 10)) % 10;
};
class EAN13 extends EAN {
constructor(data, options) {
// Add checksum if it does not exist
if (data.search(/^[0-9]{12}$/) !== -1) {
data += checksum(data);
}
super(data, options);
// Adds a last character to the end of the barcode
this.lastChar = options.lastChar;
}
valid() {
return (
this.data.search(/^[0-9]{13}$/) !== -1 &&
+this.data[12] === checksum(this.data)
);
}
leftText() {
return super.leftText(1, 6);
}
leftEncode() {
const data = this.data.substr(1, 6);
const structure = EAN13_STRUCTURE[this.data[0]];
return super.leftEncode(data, structure);
}
rightText() {
return super.rightText(7, 6);
}
rightEncode() {
const data = this.data.substr(7, 6);
return super.rightEncode(data, 'RRRRRR');
}
// The "standard" way of printing EAN13 barcodes with guard bars
encodeGuarded() {
const data = super.encodeGuarded();
// Extend data with left digit & last character
if (this.options.displayValue) {
data.unshift({
data: '000000000000',
text: this.text.substr(0, 1),
options: { textAlign: 'left', fontSize: this.fontSize }
});
if (this.options.lastChar) {
data.push({
data: '00'
});
data.push({
data: '00000',
text: this.options.lastChar,
options: { fontSize: this.fontSize }
});
}
}
return data;
}
}
export default EAN13;
@@ -0,0 +1,30 @@
// Encoding documentation:
// https://en.wikipedia.org/wiki/EAN_2#Encoding
import { EAN2_STRUCTURE } from './constants';
import encode from './encoder';
import Barcode from '../Barcode';
class EAN2 extends Barcode {
constructor(data, options) {
super(data, options);
}
valid() {
return this.data.search(/^[0-9]{2}$/) !== -1;
}
encode(){
// Choose the structure based on the number mod 4
const structure = EAN2_STRUCTURE[parseInt(this.data) % 4];
return {
// Start bits + Encode the two digits with 01 in between
data: '1011' + encode(this.data, structure, '01'),
text: this.text
};
}
}
export default EAN2;
@@ -0,0 +1,40 @@
// Encoding documentation:
// https://en.wikipedia.org/wiki/EAN_5#Encoding
import { EAN5_STRUCTURE } from './constants';
import encode from './encoder';
import Barcode from '../Barcode';
const checksum = (data) => {
const result = data
.split('')
.map(n => +n)
.reduce((sum, a, idx) => {
return idx % 2
? sum + a * 9
: sum + a * 3;
}, 0);
return result % 10;
};
class EAN5 extends Barcode {
constructor(data, options) {
super(data, options);
}
valid() {
return this.data.search(/^[0-9]{5}$/) !== -1;
}
encode() {
const structure = EAN5_STRUCTURE[checksum(this.data)];
return {
data: '1011' + encode(this.data, structure, '01'),
text: this.text
};
}
}
export default EAN5;
@@ -0,0 +1,57 @@
// Encoding documentation:
// http://www.barcodeisland.com/ean8.phtml
import EAN from './EAN';
// Calculate the checksum digit
const checksum = (number) => {
const res = number
.substr(0, 7)
.split('')
.map((n) => +n)
.reduce((sum, a, idx) => (
idx % 2 ? sum + a : sum + a * 3
), 0);
return (10 - (res % 10)) % 10;
};
class EAN8 extends EAN {
constructor(data, options) {
// Add checksum if it does not exist
if (data.search(/^[0-9]{7}$/) !== -1) {
data += checksum(data);
}
super(data, options);
}
valid() {
return (
this.data.search(/^[0-9]{8}$/) !== -1 &&
+this.data[7] === checksum(this.data)
);
}
leftText() {
return super.leftText(0, 4);
}
leftEncode() {
const data = this.data.substr(0, 4);
return super.leftEncode(data, 'LLLL');
}
rightText() {
return super.rightText(4, 4);
}
rightEncode() {
const data = this.data.substr(4, 4);
return super.rightEncode(data, 'RRRR');
}
}
export default EAN8;
@@ -0,0 +1,132 @@
// Encoding documentation:
// https://en.wikipedia.org/wiki/Universal_Product_Code#Encoding
import encode from './encoder';
import Barcode from "../Barcode.js";
class UPC extends Barcode{
constructor(data, options){
// Add checksum if it does not exist
if(data.search(/^[0-9]{11}$/) !== -1){
data += checksum(data);
}
super(data, options);
this.displayValue = options.displayValue;
// Make sure the font is not bigger than the space between the guard bars
if(options.fontSize > options.width * 10){
this.fontSize = options.width * 10;
}
else{
this.fontSize = options.fontSize;
}
// Make the guard bars go down half the way of the text
this.guardHeight = options.height + this.fontSize / 2 + options.textMargin;
}
valid(){
return this.data.search(/^[0-9]{12}$/) !== -1 &&
this.data[11] == checksum(this.data);
}
encode(){
if(this.options.flat){
return this.flatEncoding();
}
else{
return this.guardedEncoding();
}
}
flatEncoding(){
var result = "";
result += "101";
result += encode(this.data.substr(0, 6), "LLLLLL");
result += "01010";
result += encode(this.data.substr(6, 6), "RRRRRR");
result += "101";
return {
data: result,
text: this.text
};
}
guardedEncoding(){
var result = [];
// Add the first digit
if(this.displayValue){
result.push({
data: "00000000",
text: this.text.substr(0, 1),
options: {textAlign: "left", fontSize: this.fontSize}
});
}
// Add the guard bars
result.push({
data: "101" + encode(this.data[0], "L"),
options: {height: this.guardHeight}
});
// Add the left side
result.push({
data: encode(this.data.substr(1, 5), "LLLLL"),
text: this.text.substr(1, 5),
options: {fontSize: this.fontSize}
});
// Add the middle bits
result.push({
data: "01010",
options: {height: this.guardHeight}
});
// Add the right side
result.push({
data: encode(this.data.substr(6, 5), "RRRRR"),
text: this.text.substr(6, 5),
options: {fontSize: this.fontSize}
});
// Add the end bits
result.push({
data: encode(this.data[11], "R") + "101",
options: {height: this.guardHeight}
});
// Add the last digit
if(this.displayValue){
result.push({
data: "00000000",
text: this.text.substr(11, 1),
options: {textAlign: "right", fontSize: this.fontSize}
});
}
return result;
}
}
// Calulate the checksum digit
// https://en.wikipedia.org/wiki/International_Article_Number_(EAN)#Calculation_of_checksum_digit
export function checksum(number){
var result = 0;
var i;
for(i = 1; i < 11; i += 2){
result += parseInt(number[i]);
}
for(i = 0; i < 11; i += 2){
result += parseInt(number[i]) * 3;
}
return (10 - (result % 10)) % 10;
}
export default UPC;
@@ -0,0 +1,177 @@
// Encoding documentation:
// https://en.wikipedia.org/wiki/Universal_Product_Code#Encoding
//
// UPC-E documentation:
// https://en.wikipedia.org/wiki/Universal_Product_Code#UPC-E
import encode from './encoder';
import Barcode from "../Barcode.js";
import { checksum } from './UPC.js';
const EXPANSIONS = [
"XX00000XXX",
"XX10000XXX",
"XX20000XXX",
"XXX00000XX",
"XXXX00000X",
"XXXXX00005",
"XXXXX00006",
"XXXXX00007",
"XXXXX00008",
"XXXXX00009"
];
const PARITIES = [
["EEEOOO", "OOOEEE"],
["EEOEOO", "OOEOEE"],
["EEOOEO", "OOEEOE"],
["EEOOOE", "OOEEEO"],
["EOEEOO", "OEOOEE"],
["EOOEEO", "OEEOOE"],
["EOOOEE", "OEEEOO"],
["EOEOEO", "OEOEOE"],
["EOEOOE", "OEOEEO"],
["EOOEOE", "OEEOEO"]
];
class UPCE extends Barcode{
constructor(data, options){
// Code may be 6 or 8 digits;
// A 7 digit code is ambiguous as to whether the extra digit
// is a UPC-A check or number system digit.
super(data, options);
this.isValid = false;
if(data.search(/^[0-9]{6}$/) !== -1){
this.middleDigits = data;
this.upcA = expandToUPCA(data, "0");
this.text = options.text ||
`${this.upcA[0]}${data}${this.upcA[this.upcA.length - 1]}`;
this.isValid = true;
}
else if(data.search(/^[01][0-9]{7}$/) !== -1){
this.middleDigits = data.substring(1, data.length - 1);
this.upcA = expandToUPCA(this.middleDigits, data[0]);
if(this.upcA[this.upcA.length - 1] === data[data.length - 1]){
this.isValid = true;
}
else{
// checksum mismatch
return;
}
}
else{
return;
}
this.displayValue = options.displayValue;
// Make sure the font is not bigger than the space between the guard bars
if(options.fontSize > options.width * 10){
this.fontSize = options.width * 10;
}
else{
this.fontSize = options.fontSize;
}
// Make the guard bars go down half the way of the text
this.guardHeight = options.height + this.fontSize / 2 + options.textMargin;
}
valid(){
return this.isValid;
}
encode(){
if(this.options.flat){
return this.flatEncoding();
}
else{
return this.guardedEncoding();
}
}
flatEncoding(){
var result = "";
result += "101";
result += this.encodeMiddleDigits();
result += "010101";
return {
data: result,
text: this.text
};
}
guardedEncoding(){
var result = [];
// Add the UPC-A number system digit beneath the quiet zone
if(this.displayValue){
result.push({
data: "00000000",
text: this.text[0],
options: {textAlign: "left", fontSize: this.fontSize}
});
}
// Add the guard bars
result.push({
data: "101",
options: {height: this.guardHeight}
});
// Add the 6 UPC-E digits
result.push({
data: this.encodeMiddleDigits(),
text: this.text.substring(1, 7),
options: {fontSize: this.fontSize}
});
// Add the end bits
result.push({
data: "010101",
options: {height: this.guardHeight}
});
// Add the UPC-A check digit beneath the quiet zone
if(this.displayValue){
result.push({
data: "00000000",
text: this.text[7],
options: {textAlign: "right", fontSize: this.fontSize}
});
}
return result;
}
encodeMiddleDigits() {
const numberSystem = this.upcA[0];
const checkDigit = this.upcA[this.upcA.length - 1];
const parity = PARITIES[parseInt(checkDigit)][parseInt(numberSystem)];
return encode(this.middleDigits, parity);
}
}
function expandToUPCA(middleDigits, numberSystem) {
const lastUpcE = parseInt(middleDigits[middleDigits.length - 1]);
const expansion = EXPANSIONS[lastUpcE];
let result = "";
let digitIndex = 0;
for(let i = 0; i < expansion.length; i++) {
let c = expansion[i];
if (c === 'X') {
result += middleDigits[digitIndex++];
} else {
result += c;
}
}
result = `${numberSystem}${result}`;
return `${result}${checksum(result)}`;
}
export default UPCE;
@@ -0,0 +1,41 @@
// Standard start end and middle bits
export const SIDE_BIN = '101';
export const MIDDLE_BIN = '01010';
export const BINARIES = {
'L': [ // The L (left) type of encoding
'0001101', '0011001', '0010011', '0111101', '0100011',
'0110001', '0101111', '0111011', '0110111', '0001011'
],
'G': [ // The G type of encoding
'0100111', '0110011', '0011011', '0100001', '0011101',
'0111001', '0000101', '0010001', '0001001', '0010111'
],
'R': [ // The R (right) type of encoding
'1110010', '1100110', '1101100', '1000010', '1011100',
'1001110', '1010000', '1000100', '1001000', '1110100'
],
'O': [ // The O (odd) encoding for UPC-E
'0001101', '0011001', '0010011', '0111101', '0100011',
'0110001', '0101111', '0111011', '0110111', '0001011'
],
'E': [ // The E (even) encoding for UPC-E
'0100111', '0110011', '0011011', '0100001', '0011101',
'0111001', '0000101', '0010001', '0001001', '0010111'
]
};
// Define the EAN-2 structure
export const EAN2_STRUCTURE = ['LL', 'LG', 'GL', 'GG'];
// Define the EAN-5 structure
export const EAN5_STRUCTURE = [
'GGLLL', 'GLGLL', 'GLLGL', 'GLLLG', 'LGGLL',
'LLGGL', 'LLLGG', 'LGLGL', 'LGLLG', 'LLGLG'
];
// Define the EAN-13 structure
export const EAN13_STRUCTURE = [
'LLLLLL', 'LLGLGG', 'LLGGLG', 'LLGGGL', 'LGLLGG',
'LGGLLG', 'LGGGLL', 'LGLGLG', 'LGLGGL', 'LGGLGL'
];
@@ -0,0 +1,20 @@
import { BINARIES } from './constants';
// Encode data string
const encode = (data, structure, separator) => {
let encoded = data
.split('')
.map((val, idx) => BINARIES[structure[idx]])
.map((val, idx) => val ? val[data[idx]] : '');
if (separator) {
const last = data.length - 1;
encoded = encoded.map((val, idx) => (
idx < last ? val + separator : val
));
}
return encoded.join('');
};
export default encode;
@@ -0,0 +1,8 @@
import EAN13 from './EAN13.js';
import EAN8 from './EAN8.js';
import EAN5 from './EAN5.js';
import EAN2 from './EAN2.js';
import UPC from './UPC.js';
import UPCE from './UPCE.js';
export {EAN13, EAN8, EAN5, EAN2, UPC, UPCE};
@@ -0,0 +1,22 @@
import Barcode from "../Barcode.js";
class GenericBarcode extends Barcode{
constructor(data, options){
super(data, options); // Sets this.data and this.text
}
// Return the corresponding binary numbers for the data provided
encode(){
return {
data: "10101010101010101010101010101010101010101",
text: this.text
};
}
// Resturn true/false if the string provided is valid for this encoder
valid(){
return true;
}
}
export {GenericBarcode};
@@ -0,0 +1,37 @@
import { START_BIN, END_BIN, BINARIES } from './constants';
import Barcode from '../Barcode';
class ITF extends Barcode {
valid() {
return this.data.search(/^([0-9]{2})+$/) !== -1;
}
encode() {
// Calculate all the digit pairs
const encoded = this.data
.match(/.{2}/g)
.map(pair => this.encodePair(pair))
.join('');
return {
data: START_BIN + encoded + END_BIN,
text: this.text
};
}
// Calculate the data of a number pair
encodePair(pair) {
const second = BINARIES[pair[1]];
return BINARIES[pair[0]]
.split('')
.map((first, idx) => (
(first === '1' ? '111' : '1') +
(second[idx] === '1' ? '000' : '0')
))
.join('');
}
}
export default ITF;
@@ -0,0 +1,33 @@
import ITF from './ITF';
// Calculate the checksum digit
const checksum = (data) => {
const res = data
.substr(0, 13)
.split('')
.map(num => parseInt(num, 10))
.reduce((sum, n, idx) => sum + (n * (3 - (idx % 2) * 2)), 0);
return Math.ceil(res / 10) * 10 - res;
};
class ITF14 extends ITF {
constructor(data, options) {
// Add checksum if it does not exist
if (data.search(/^[0-9]{13}$/) !== -1) {
data += checksum(data);
}
super(data, options);
}
valid() {
return (
this.data.search(/^[0-9]{14}$/) !== -1 &&
+this.data[13] === checksum(this.data)
);
}
}
export default ITF14;
@@ -0,0 +1,7 @@
export const START_BIN = '1010';
export const END_BIN = '11101';
export const BINARIES = [
'00110', '10001', '01001', '11000', '00101',
'10100', '01100', '00011', '10010', '01010',
];
@@ -0,0 +1,4 @@
import ITF from './ITF';
import ITF14 from './ITF14';
export { ITF, ITF14 };
@@ -0,0 +1,48 @@
// Encoding documentation
// https://en.wikipedia.org/wiki/MSI_Barcode#Character_set_and_binary_lookup
import Barcode from "../Barcode.js";
class MSI extends Barcode{
constructor(data, options){
super(data, options);
}
encode(){
// Start bits
var ret = "110";
for(var i = 0; i < this.data.length; i++){
// Convert the character to binary (always 4 binary digits)
var digit = parseInt(this.data[i]);
var bin = digit.toString(2);
bin = addZeroes(bin, 4 - bin.length);
// Add 100 for every zero and 110 for every 1
for(var b = 0; b < bin.length; b++){
ret += bin[b] == "0" ? "100" : "110";
}
}
// End bits
ret += "1001";
return {
data: ret,
text: this.text
};
}
valid(){
return this.data.search(/^[0-9]+$/) !== -1;
}
}
function addZeroes(number, n){
for(var i = 0; i < n; i++){
number = "0" + number;
}
return number;
}
export default MSI;
@@ -0,0 +1,10 @@
import MSI from './MSI.js';
import {mod10} from './checksums.js';
class MSI10 extends MSI{
constructor(data, options){
super(data + mod10(data), options);
}
}
export default MSI10;
@@ -0,0 +1,12 @@
import MSI from './MSI.js';
import {mod10} from './checksums.js';
class MSI1010 extends MSI{
constructor(data, options){
data += mod10(data);
data += mod10(data);
super(data, options);
}
}
export default MSI1010;
@@ -0,0 +1,10 @@
import MSI from './MSI.js';
import {mod11} from './checksums.js';
class MSI11 extends MSI{
constructor(data, options){
super(data + mod11(data), options);
}
}
export default MSI11;
@@ -0,0 +1,12 @@
import MSI from './MSI.js';
import {mod10, mod11} from './checksums.js';
class MSI1110 extends MSI{
constructor(data, options){
data += mod11(data);
data += mod10(data);
super(data, options);
}
}
export default MSI1110;
@@ -0,0 +1,23 @@
export function mod10(number){
var sum = 0;
for(var i = 0; i < number.length; i++){
var n = parseInt(number[i]);
if((i + number.length) % 2 === 0){
sum += n;
}
else{
sum += (n * 2) % 10 + Math.floor((n * 2) / 10);
}
}
return (10 - (sum % 10)) % 10;
}
export function mod11(number){
var sum = 0;
var weights = [2, 3, 4, 5, 6, 7];
for(var i = 0; i < number.length; i++){
var n = parseInt(number[number.length - 1 - i]);
sum += weights[i % weights.length] * n;
}
return (11 - (sum % 11)) % 11;
}
@@ -0,0 +1,7 @@
import MSI from './MSI.js';
import MSI10 from './MSI10.js';
import MSI11 from './MSI11.js';
import MSI1010 from './MSI1010.js';
import MSI1110 from './MSI1110.js';
export {MSI, MSI10, MSI11, MSI1010, MSI1110};
@@ -0,0 +1,63 @@
// Encoding specification:
// http://www.barcodeisland.com/codabar.phtml
import Barcode from "../Barcode.js";
class codabar extends Barcode{
constructor(data, options){
if (data.search(/^[0-9\-\$\:\.\+\/]+$/) === 0) {
data = "A" + data + "A";
}
super(data.toUpperCase(), options);
this.text = this.options.text || this.text.replace(/[A-D]/g, '');
}
valid(){
return this.data.search(/^[A-D][0-9\-\$\:\.\+\/]+[A-D]$/) !== -1;
}
encode(){
var result = [];
var encodings = this.getEncodings();
for(var i = 0; i < this.data.length; i++){
result.push(encodings[this.data.charAt(i)]);
// for all characters except the last, append a narrow-space ("0")
if (i !== this.data.length - 1) {
result.push("0");
}
}
return {
text: this.text,
data: result.join('')
};
}
getEncodings(){
return {
"0": "101010011",
"1": "101011001",
"2": "101001011",
"3": "110010101",
"4": "101101001",
"5": "110101001",
"6": "100101011",
"7": "100101101",
"8": "100110101",
"9": "110100101",
"-": "101001101",
"$": "101100101",
":": "1101011011",
"/": "1101101011",
".": "1101101101",
"+": "1011011011",
"A": "1011001001",
"B": "1001001011",
"C": "1010010011",
"D": "1010011001"
};
}
}
export {codabar};
@@ -0,0 +1,22 @@
import {CODE39} from './CODE39/';
import {CODE128, CODE128A, CODE128B, CODE128C} from './CODE128/';
import {EAN13, EAN8, EAN5, EAN2, UPC, UPCE} from './EAN_UPC/';
import {ITF, ITF14} from './ITF/';
import {MSI, MSI10, MSI11, MSI1010, MSI1110} from './MSI/';
import {pharmacode} from './pharmacode/';
import {codabar} from './codabar';
import {CODE93, CODE93FullASCII} from './CODE93/';
import {GenericBarcode} from './GenericBarcode/';
export default {
CODE39,
CODE128, CODE128A, CODE128B, CODE128C,
EAN13, EAN8, EAN5, EAN2, UPC, UPCE,
ITF14,
ITF,
MSI, MSI10, MSI11, MSI1010, MSI1110,
pharmacode,
codabar,
CODE93, CODE93FullASCII,
GenericBarcode
};
@@ -0,0 +1,43 @@
// Encoding documentation
// http://www.gomaro.ch/ftproot/Laetus_PHARMA-CODE.pdf
import Barcode from "../Barcode.js";
class pharmacode extends Barcode{
constructor(data, options){
super(data, options);
this.number = parseInt(data, 10);
}
encode(){
var z = this.number;
var result = "";
// http://i.imgur.com/RMm4UDJ.png
// (source: http://www.gomaro.ch/ftproot/Laetus_PHARMA-CODE.pdf, page: 34)
while(!isNaN(z) && z != 0){
if(z % 2 === 0){ // Even
result = "11100" + result;
z = (z - 2) / 2;
}
else{ // Odd
result = "100" + result;
z = (z - 1) / 2;
}
}
// Remove the two last zeroes
result = result.slice(0, -2);
return {
data: result,
text: this.text
};
}
valid(){
return this.number >= 3 && this.number <= 131070;
}
}
export {pharmacode};
@@ -0,0 +1,39 @@
/*eslint no-console: 0 */
class ErrorHandler{
constructor(api){
this.api = api;
}
handleCatch(e){
// If babel supported extending of Error in a correct way instanceof would be used here
if(e.name === "InvalidInputException"){
if(this.api._options.valid !== this.api._defaults.valid){
this.api._options.valid(false);
}
else{
throw e.message;
}
}
else{
throw e;
}
this.api.render = function(){};
}
wrapBarcodeCall(func){
try{
var result = func(...arguments);
this.api._options.valid(true);
return result;
}
catch(e){
this.handleCatch(e);
return this.api;
}
}
}
export default ErrorHandler;
@@ -0,0 +1,29 @@
class InvalidInputException extends Error{
constructor(symbology, input) {
super();
this.name = "InvalidInputException";
this.symbology = symbology;
this.input = input;
this.message = '"' + this.input + '" is not a valid input for ' + this.symbology;
}
}
class InvalidElementException extends Error{
constructor() {
super();
this.name = "InvalidElementException";
this.message = "Not supported type to render on";
}
}
class NoElementException extends Error{
constructor() {
super();
this.name = "NoElementException";
this.message = "No element to render on.";
}
}
export {InvalidInputException, InvalidElementException, NoElementException};
@@ -0,0 +1,11 @@
export default fixOptions;
function fixOptions(options){
// Fix the margins
options.marginTop = options.marginTop || options.margin;
options.marginBottom = options.marginBottom || options.margin;
options.marginRight = options.marginRight || options.margin;
options.marginLeft = options.marginLeft || options.margin;
return options;
}
@@ -0,0 +1,28 @@
import optionsFromStrings from "./optionsFromStrings.js";
import defaults from "../options/defaults.js";
function getOptionsFromElement(element){
var options = {};
for(var property in defaults){
if(defaults.hasOwnProperty(property)){
// jsbarcode-*
if(element.hasAttribute("jsbarcode-" + property.toLowerCase())){
options[property] = element.getAttribute("jsbarcode-" + property.toLowerCase());
}
// data-*
if(element.hasAttribute("data-" + property.toLowerCase())){
options[property] = element.getAttribute("data-" + property.toLowerCase());
}
}
}
options["value"] = element.getAttribute("jsbarcode-value") || element.getAttribute("data-value");
// Since all atributes are string they need to be converted to integers
options = optionsFromStrings(options);
return options;
}
export default getOptionsFromElement;
@@ -0,0 +1,103 @@
/* global HTMLImageElement */
/* global HTMLCanvasElement */
/* global SVGElement */
import getOptionsFromElement from "./getOptionsFromElement.js";
import renderers from "../renderers";
import {InvalidElementException} from "../exceptions/exceptions.js";
// Takes an element and returns an object with information about how
// it should be rendered
// This could also return an array with these objects
// {
// element: The element that the renderer should draw on
// renderer: The name of the renderer
// afterRender (optional): If something has to done after the renderer
// completed, calls afterRender (function)
// options (optional): Options that can be defined in the element
// }
function getRenderProperties(element){
// If the element is a string, query select call again
if(typeof element === "string"){
return querySelectedRenderProperties(element);
}
// If element is array. Recursivly call with every object in the array
else if(Array.isArray(element)){
var returnArray = [];
for(let i = 0; i < element.length; i++){
returnArray.push(getRenderProperties(element[i]));
}
return returnArray;
}
// If element, render on canvas and set the uri as src
else if(typeof HTMLCanvasElement !== 'undefined' && element instanceof HTMLImageElement){
return newCanvasRenderProperties(element);
}
// If SVG
else if(
(element && element.nodeName && element.nodeName.toLowerCase() === 'svg') ||
(typeof SVGElement !== 'undefined' && element instanceof SVGElement)
){
return {
element: element,
options: getOptionsFromElement(element),
renderer: renderers.SVGRenderer
};
}
// If canvas (in browser)
else if(typeof HTMLCanvasElement !== 'undefined' && element instanceof HTMLCanvasElement){
return {
element: element,
options: getOptionsFromElement(element),
renderer: renderers.CanvasRenderer
};
}
// If canvas (in node)
else if(element && element.getContext){
return {
element: element,
renderer: renderers.CanvasRenderer
};
}
else if(element && typeof element === 'object' && !element.nodeName) {
return {
element: element,
renderer: renderers.ObjectRenderer
};
}
else{
throw new InvalidElementException();
}
}
function querySelectedRenderProperties(string){
var selector = document.querySelectorAll(string);
if(selector.length === 0){
return undefined;
}
else{
let returnArray = [];
for(let i = 0; i < selector.length; i++){
returnArray.push(getRenderProperties(selector[i]));
}
return returnArray;
}
}
function newCanvasRenderProperties(imgElement){
var canvas = document.createElement('canvas');
return {
element: canvas,
options: getOptionsFromElement(imgElement),
renderer: renderers.CanvasRenderer,
afterRender: function(){
imgElement.setAttribute("src", canvas.toDataURL());
}
};
}
export default getRenderProperties;
@@ -0,0 +1,22 @@
export default linearizeEncodings;
// Encodings can be nestled like [[1-1, 1-2], 2, [3-1, 3-2]
// Convert to [1-1, 1-2, 2, 3-1, 3-2]
function linearizeEncodings(encodings){
var linearEncodings = [];
function nextLevel(encoded){
if(Array.isArray(encoded)){
for(let i = 0; i < encoded.length; i++){
nextLevel(encoded[i]);
}
}
else{
encoded.text = encoded.text || "";
encoded.data = encoded.data || "";
linearEncodings.push(encoded);
}
}
nextLevel(encodings);
return linearEncodings;
}
@@ -0,0 +1 @@
export default (old, replaceObj) => ({ ...old, ...replaceObj });
@@ -0,0 +1,31 @@
export default optionsFromStrings;
// Convert string to integers/booleans where it should be
function optionsFromStrings(options){
var intOptions = [
"width",
"height",
"textMargin",
"fontSize",
"margin",
"marginTop",
"marginBottom",
"marginLeft",
"marginRight"
];
for(var intOption in intOptions){
if(intOptions.hasOwnProperty(intOption)){
intOption = intOptions[intOption];
if(typeof options[intOption] === "string"){
options[intOption] = parseInt(options[intOption], 10);
}
}
}
if(typeof options["displayValue"] === "string"){
options["displayValue"] = (options["displayValue"] != "false");
}
return options;
}
@@ -0,0 +1,23 @@
var defaults = {
width: 2,
height: 100,
format: "auto",
displayValue: true,
fontOptions: "",
font: "monospace",
text: undefined,
textAlign: "center",
textPosition: "bottom",
textMargin: 2,
fontSize: 20,
background: "#ffffff",
lineColor: "#000000",
margin: 10,
marginTop: undefined,
marginBottom: undefined,
marginLeft: undefined,
marginRight: undefined,
valid: function(){}
};
export default defaults;
@@ -0,0 +1,137 @@
import merge from "../help/merge.js";
import {calculateEncodingAttributes, getTotalWidthOfEncodings, getMaximumHeightOfEncodings} from "./shared.js";
class CanvasRenderer{
constructor(canvas, encodings, options){
this.canvas = canvas;
this.encodings = encodings;
this.options = options;
}
render(){
// Abort if the browser does not support HTML5 canvas
if (!this.canvas.getContext) {
throw new Error('The browser does not support canvas.');
}
this.prepareCanvas();
for(let i = 0; i < this.encodings.length; i++){
var encodingOptions = merge(this.options, this.encodings[i].options);
this.drawCanvasBarcode(encodingOptions, this.encodings[i]);
this.drawCanvasText(encodingOptions, this.encodings[i]);
this.moveCanvasDrawing(this.encodings[i]);
}
this.restoreCanvas();
}
prepareCanvas(){
// Get the canvas context
var ctx = this.canvas.getContext("2d");
ctx.save();
calculateEncodingAttributes(this.encodings, this.options, ctx);
var totalWidth = getTotalWidthOfEncodings(this.encodings);
var maxHeight = getMaximumHeightOfEncodings(this.encodings);
this.canvas.width = totalWidth + this.options.marginLeft + this.options.marginRight;
this.canvas.height = maxHeight;
// Paint the canvas
ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
if(this.options.background){
ctx.fillStyle = this.options.background;
ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
}
ctx.translate(this.options.marginLeft, 0);
}
drawCanvasBarcode(options, encoding){
// Get the canvas context
var ctx = this.canvas.getContext("2d");
var binary = encoding.data;
// Creates the barcode out of the encoded binary
var yFrom;
if(options.textPosition == "top"){
yFrom = options.marginTop + options.fontSize + options.textMargin;
}
else{
yFrom = options.marginTop;
}
ctx.fillStyle = options.lineColor;
for(var b = 0; b < binary.length; b++){
var x = b * options.width + encoding.barcodePadding;
if(binary[b] === "1"){
ctx.fillRect(x, yFrom, options.width, options.height);
}
else if(binary[b]){
ctx.fillRect(x, yFrom, options.width, options.height * binary[b]);
}
}
}
drawCanvasText(options, encoding){
// Get the canvas context
var ctx = this.canvas.getContext("2d");
var font = options.fontOptions + " " + options.fontSize + "px " + options.font;
// Draw the text if displayValue is set
if(options.displayValue){
var x, y;
if(options.textPosition == "top"){
y = options.marginTop + options.fontSize - options.textMargin;
}
else{
y = options.height + options.textMargin + options.marginTop + options.fontSize;
}
ctx.font = font;
// Draw the text in the correct X depending on the textAlign option
if(options.textAlign == "left" || encoding.barcodePadding > 0){
x = 0;
ctx.textAlign = 'left';
}
else if(options.textAlign == "right"){
x = encoding.width - 1;
ctx.textAlign = 'right';
}
// In all other cases, center the text
else{
x = encoding.width / 2;
ctx.textAlign = 'center';
}
ctx.fillText(encoding.text, x, y);
}
}
moveCanvasDrawing(encoding){
var ctx = this.canvas.getContext("2d");
ctx.translate(encoding.width, 0);
}
restoreCanvas(){
// Get the canvas context
var ctx = this.canvas.getContext("2d");
ctx.restore();
}
}
export default CanvasRenderer;
@@ -0,0 +1,5 @@
import CanvasRenderer from './canvas.js';
import SVGRenderer from './svg.js';
import ObjectRenderer from './object.js';
export default {CanvasRenderer, SVGRenderer, ObjectRenderer};
@@ -0,0 +1,14 @@
class ObjectRenderer {
constructor(object, encodings, options) {
this.object = object;
this.encodings = encodings;
this.options = options;
}
render() {
this.object.encodings = this.encodings;
}
}
export default ObjectRenderer;
@@ -0,0 +1,94 @@
import merge from "../help/merge.js";
function getEncodingHeight(encoding, options){
return options.height +
((options.displayValue && encoding.text.length > 0) ? options.fontSize + options.textMargin : 0) +
options.marginTop +
options.marginBottom;
}
function getBarcodePadding(textWidth, barcodeWidth, options){
if(options.displayValue && barcodeWidth < textWidth){
if(options.textAlign == "center"){
return Math.floor((textWidth - barcodeWidth) / 2);
}
else if(options.textAlign == "left"){
return 0;
}
else if(options.textAlign == "right"){
return Math.floor(textWidth - barcodeWidth);
}
}
return 0;
}
function calculateEncodingAttributes(encodings, barcodeOptions, context){
for(let i = 0; i < encodings.length; i++){
var encoding = encodings[i];
var options = merge(barcodeOptions, encoding.options);
// Calculate the width of the encoding
var textWidth;
if(options.displayValue){
textWidth = messureText(encoding.text, options, context);
}
else{
textWidth = 0;
}
var barcodeWidth = encoding.data.length * options.width;
encoding.width = Math.ceil(Math.max(textWidth, barcodeWidth));
encoding.height = getEncodingHeight(encoding, options);
encoding.barcodePadding = getBarcodePadding(textWidth, barcodeWidth, options);
}
}
function getTotalWidthOfEncodings(encodings){
var totalWidth = 0;
for(let i = 0; i < encodings.length; i++){
totalWidth += encodings[i].width;
}
return totalWidth;
}
function getMaximumHeightOfEncodings(encodings){
var maxHeight = 0;
for(let i = 0; i < encodings.length; i++){
if(encodings[i].height > maxHeight){
maxHeight = encodings[i].height;
}
}
return maxHeight;
}
function messureText(string, options, context){
var ctx;
if(context){
ctx = context;
}
else if(typeof document !== "undefined"){
ctx = document.createElement("canvas").getContext("2d");
}
else{
// If the text cannot be messured we will return 0.
// This will make some barcode with big text render incorrectly
return 0;
}
ctx.font = options.fontOptions + " " + options.fontSize + "px " + options.font;
// Calculate the width of the encoding
var measureTextResult = ctx.measureText(string);
if (!measureTextResult) {
// Some implementations don't implement measureText and return undefined.
// If the text cannot be measured we will return 0.
// This will make some barcode with big text render incorrectly
return 0;
}
var size = measureTextResult.width;
return size;
}
export {getMaximumHeightOfEncodings, getEncodingHeight, getBarcodePadding, calculateEncodingAttributes, getTotalWidthOfEncodings};
@@ -0,0 +1,171 @@
import merge from "../help/merge.js";
import {calculateEncodingAttributes, getTotalWidthOfEncodings, getMaximumHeightOfEncodings} from "./shared.js";
var svgns = "http://www.w3.org/2000/svg";
class SVGRenderer{
constructor(svg, encodings, options){
this.svg = svg;
this.encodings = encodings;
this.options = options;
this.document = options.xmlDocument || document;
}
render(){
var currentX = this.options.marginLeft;
this.prepareSVG();
for(let i = 0; i < this.encodings.length; i++){
var encoding = this.encodings[i];
var encodingOptions = merge(this.options, encoding.options);
var group = this.createGroup(currentX, encodingOptions.marginTop, this.svg);
this.setGroupOptions(group, encodingOptions);
this.drawSvgBarcode(group, encodingOptions, encoding);
this.drawSVGText(group, encodingOptions, encoding);
currentX += encoding.width;
}
}
prepareSVG(){
// Clear the SVG
while (this.svg.firstChild) {
this.svg.removeChild(this.svg.firstChild);
}
calculateEncodingAttributes(this.encodings, this.options);
var totalWidth = getTotalWidthOfEncodings(this.encodings);
var maxHeight = getMaximumHeightOfEncodings(this.encodings);
var width = totalWidth + this.options.marginLeft + this.options.marginRight;
this.setSvgAttributes(width, maxHeight);
if(this.options.background){
this.drawRect(0, 0, width, maxHeight, this.svg).setAttribute(
"style", "fill:" + this.options.background + ";"
);
}
}
drawSvgBarcode(parent, options, encoding){
var binary = encoding.data;
// Creates the barcode out of the encoded binary
var yFrom;
if(options.textPosition == "top"){
yFrom = options.fontSize + options.textMargin;
}
else{
yFrom = 0;
}
var barWidth = 0;
var x = 0;
for(var b = 0; b < binary.length; b++){
x = b * options.width + encoding.barcodePadding;
if(binary[b] === "1"){
barWidth++;
}
else if(barWidth > 0){
this.drawRect(x - options.width * barWidth, yFrom, options.width * barWidth, options.height, parent);
barWidth = 0;
}
}
// Last draw is needed since the barcode ends with 1
if(barWidth > 0){
this.drawRect(x - options.width * (barWidth - 1), yFrom, options.width * barWidth, options.height, parent);
}
}
drawSVGText(parent, options, encoding){
var textElem = this.document.createElementNS(svgns, 'text');
// Draw the text if displayValue is set
if(options.displayValue){
var x, y;
textElem.setAttribute("style",
"font:" + options.fontOptions + " " + options.fontSize + "px " + options.font
);
if(options.textPosition == "top"){
y = options.fontSize - options.textMargin;
}
else{
y = options.height + options.textMargin + options.fontSize;
}
// Draw the text in the correct X depending on the textAlign option
if(options.textAlign == "left" || encoding.barcodePadding > 0){
x = 0;
textElem.setAttribute("text-anchor", "start");
}
else if(options.textAlign == "right"){
x = encoding.width - 1;
textElem.setAttribute("text-anchor", "end");
}
// In all other cases, center the text
else{
x = encoding.width / 2;
textElem.setAttribute("text-anchor", "middle");
}
textElem.setAttribute("x", x);
textElem.setAttribute("y", y);
textElem.appendChild(this.document.createTextNode(encoding.text));
parent.appendChild(textElem);
}
}
setSvgAttributes(width, height){
var svg = this.svg;
svg.setAttribute("width", width + "px");
svg.setAttribute("height", height + "px");
svg.setAttribute("x", "0px");
svg.setAttribute("y", "0px");
svg.setAttribute("viewBox", "0 0 " + width + " " + height);
svg.setAttribute("xmlns", svgns);
svg.setAttribute("version", "1.1");
svg.setAttribute("style", "transform: translate(0,0)");
}
createGroup(x, y, parent){
var group = this.document.createElementNS(svgns, 'g');
group.setAttribute("transform", "translate(" + x + ", " + y + ")");
parent.appendChild(group);
return group;
}
setGroupOptions(group, options){
group.setAttribute("style",
"fill:" + options.lineColor + ";"
);
}
drawRect(x, y, width, height, parent){
var rect = this.document.createElementNS(svgns, 'rect');
rect.setAttribute("x", x);
rect.setAttribute("y", y);
rect.setAttribute("width", width);
rect.setAttribute("height", height);
parent.appendChild(rect);
return rect;
}
}
export default SVGRenderer;
@@ -0,0 +1,74 @@
export default {
props: {
// 条形码内容
value: {
type: String,
default: () => uni.$u.props.barcode.value
},
// 条形码类型
format: {
type: String,
default: () => uni.$u.props.barcode.format
},
// 条形码宽度
width: {
type: [String, Number],
default: () => uni.$u.props.barcode.width
},
// 条形码高度
height: {
type: [String, Number],
default: () => uni.$u.props.barcode.height
},
// 条形码颜色
color: {
type: String,
default: () => uni.$u.props.barcode.color
},
// 背景颜色
backgroundColor: {
type: String,
default: () => uni.$u.props.barcode.backgroundColor
},
// 是否显示文本
displayValue: {
type: Boolean,
default: () => uni.$u.props.barcode.displayValue
},
// 文本位置
textPosition: {
type: String,
default: () => uni.$u.props.barcode.textPosition
},
// 画布类型
canvasType: {
type: String,
default: () => uni.$u.props.barcode.canvasType
},
// 字体选项
fontOptions: {
type: String,
default: () => uni.$u.props.barcode.fontOptions
},
// 字体
font: {
type: String,
default: () => uni.$u.props.barcode.font
},
// 文本对齐方式
textAlign: {
type: String,
default: () => uni.$u.props.barcode.textAlign
},
// 文本边距
textMargin: {
type: Number,
default: () => uni.$u.props.barcode.textMargin
},
// 字体大小
fontSize: {
type: Number,
default: () => uni.$u.props.barcode.fontSize
}
}
}
@@ -0,0 +1,249 @@
<template>
<view class="u-barcode" :style="[{
width: $u.addUnit(barcodeWidth),
height: $u.addUnit(barcodeHeight)
},$u.addStyle(customStyle)]">
<canvas
class="u-barcode__canvas"
:type="canvasType"
:canvas-id="canvasId"
:id="canvasId"
></canvas>
</view>
</template>
<script>
import props from './props.js';
import JsBarcode from './JsBarcode/JsBarcode.js';
import mixin from '../../libs/mixin/mixin'
import mpMixin from '../../libs/mixin/mpMixin';
/**
* Barcode 条形码组件
* @description 支持生成条形码和二维码的组件,基于JsBarcode.js库
* @tutorial https://uviewui.com/components/barcode.html
*
* @property {String} value 条形码内容
* @property {String} type 条形码类型,支持code128、code39、ean13、qr等
* @property {String|Number} width 条形码宽度
* @property {String|Number} height 条形码高度
* @property {String} color 条形码颜色
* @property {String} backgroundColor 背景颜色
* @property {Boolean} displayValue 是否显示文本
* @property {String} textPosition 文本位置,top/bottom
* @property {Object|String} customStyle 自定义样式
* @property {String} fontOptions 字体选项
* @property {String} font 字体
* @property {String} textAlign 文本对齐方式,left/center/right
* @property {Number} textMargin 文本边距
* @property {Number} fontSize 字体大小
* @example <u-barcode value="123456789" type="code128"></u-barcode>
*/
export default {
name: "u-barcode",
mixins: [mpMixin, mixin, props],
data() {
return {
canvasId: 'barcodeId_' + uni.$u.guid(),
ctx: null,
barcodeWidth: null,
barcodeHeight: null,
textHeight: 30
}
},
computed: {
is2d() {
// #ifdef MP-WEIXIN
return this.canvasType == '2d';
// #endif
// #ifndef MP-WEIXIN
return false;
// #endif
}
},
watch: {
value: {
handler(newVal) {
if (newVal) {
this.generateBarcode()
}
}
}
},
mounted() {
this.$nextTick(() => {
this.generateBarcode()
})
},
methods: {
// 生成条形码
async generateBarcode() {
const data = {};
JsBarcode(data, this.value, {
format: this.format,
width: this.width,
height: this.height,
displayValue: false,
});
// 获取编码数据
const encodings = data.encodings[0];
if (!encodings) {
return;
}
const { data: binaryData } = encodings;
this.barcodeWidth = binaryData.length * this.width;
this.barcodeHeight = this.height + (this.displayValue ? this.textHeight : 0);
await uni.$u.sleep(100);
await this.initCanvas(binaryData);
},
// 初始化canvas
async initCanvas(binaryData) {
const query = uni
.createSelectorQuery()
.in(this)
.select(`#${this.canvasId}`);
if (this.is2d) {
let canvas = await new Promise((resolve) => {
query
.fields({
node: true,
size: true,
})
.exec((res) => {
resolve(res[0].node);
});
});
canvas.width = this.barcodeWidth;
canvas.height = this.barcodeHeight;
this.ctx = canvas.getContext('2d', { willReadFrequently: true });
} else {
// #ifdef MP-ALIPAY
this.ctx = uni.createCanvasContext(this.canvasId);
// #endif
// #ifndef MP-ALIPAY
this.ctx = uni.createCanvasContext(this.canvasId, this);
// #endif
}
// 清空画布并设置背景色
if (this.is2d) {
this.ctx.fillStyle = this.backgroundColor;
this.ctx.fillRect(0, 0, this.barcodeWidth, this.barcodeHeight);
} else {
this.ctx.setFillStyle(this.backgroundColor);
this.ctx.fillRect(0, 0, this.barcodeWidth, this.barcodeHeight);
}
this.drawBarcode(binaryData);
},
// 绘制条形码
drawBarcode(binaryData) {
const startX = 0;
let startY = this.textPosition === 'top' ? this.textHeight : 0;
// 绘制条形码
if (this.is2d) {
this.ctx.fillStyle = this.color;
} else {
this.ctx.setFillStyle(this.color);
}
let drawnBars = 0;
for (let i = 0; i < binaryData.length; i++) {
if (binaryData[i] === '1') {
const x = startX + i * this.width;
this.ctx.fillRect(x, startY, this.width, this.height);
drawnBars++;
}
}
// 绘制文本
if (this.displayValue) {
this.drawText();
}
if (!this.is2d) {
this.ctx.draw();
}
},
// 绘制文本
drawText() {
const text = this.value;
const textColor = this.color;
// 构建字体字符串
let fontString = `${this.fontSize}px ${this.font}`;
if (this.fontOptions) {
fontString = `${this.fontOptions} ${fontString}`;
}
// 设置文本样式
if (this.is2d) {
this.ctx.font = fontString;
this.ctx.fillStyle = textColor;
this.ctx.textAlign = this.textAlign;
this.ctx.textBaseline = 'middle';
} else {
this.ctx.setFontSize(this.fontSize);
this.ctx.setFillStyle(textColor);
this.ctx.setTextAlign(this.textAlign);
this.ctx.setTextBaseline('middle');
}
// 计算文本位置
let textX;
if (this.textAlign === 'left') {
textX = this.textMargin;
} else if (this.textAlign === 'right') {
textX = this.barcodeWidth - this.textMargin;
} else {
// center
textX = this.barcodeWidth / 2;
}
let textY;
if (this.textPosition === 'top') {
// 文本在上方
textY = this.fontSize / 2 + this.textMargin;
} else {
// 文本在下方
textY = this.barcodeHeight - this.fontSize / 2 - this.textMargin;
}
// 绘制文本
this.ctx.fillText(text, textX, textY);
}
}
}
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
.u-barcode {
position: relative;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
&__canvas {
width: 100%;
height: 100%;
}
}
</style>
@@ -0,0 +1,46 @@
$u-button-active-opacity:0.75 !default;
$u-button-loading-text-margin-left:4px !default;
$u-button-text-color: #FFFFFF !default;
$u-button-text-plain-error-color:$u-error !default;
$u-button-text-plain-warning-color:$u-warning !default;
$u-button-text-plain-success-color:$u-success !default;
$u-button-text-plain-info-color:$u-info !default;
$u-button-text-plain-primary-color:$u-primary !default;
.u-button {
&--active {
opacity: $u-button-active-opacity;
}
&--active--plain {
background-color: rgb(217, 217, 217);
}
&__loading-text {
margin-left:$u-button-loading-text-margin-left;
}
&__text,
&__loading-text {
color:$u-button-text-color;
}
&__text--plain--error {
color:$u-button-text-plain-error-color;
}
&__text--plain--warning {
color:$u-button-text-plain-warning-color;
}
&__text--plain--success{
color:$u-button-text-plain-success-color;
}
&__text--plain--info {
color:$u-button-text-plain-info-color;
}
&__text--plain--primary {
color:$u-button-text-plain-primary-color;
}
}
@@ -0,0 +1,171 @@
/*
* @Author : LQ
* @Description :
* @version : 1.0
* @Date : 2021-08-16 10:04:04
* @LastAuthor : LQ
* @lastTime : 2021-08-16 10:04:24
* @FilePath : /u-view2.0/uview-ui/components/u-button/props.js
*/
export default {
props: {
// 是否细边框
hairline: {
type: Boolean,
default: ()=> uni.$u.props.button.hairline
},
// 按钮的预置样式,info,primary,error,warning,success
type: {
type: String,
default: ()=> uni.$u.props.button.type
},
// 按钮类型,text,default
round: {
type: [String, Number],
default: ()=> uni.$u.props.button.round
},
// 按钮尺寸,large,normal,small,mini
size: {
type: String,
default: ()=> uni.$u.props.button.size
},
// 按钮形状,circle(两边为半圆),square(带圆角)
shape: {
type: String,
default: ()=> uni.$u.props.button.shape
},
// 按钮是否镂空
plain: {
type: Boolean,
default: ()=> uni.$u.props.button.plain
},
// 是否禁止状态
disabled: {
type: Boolean,
default: ()=> uni.$u.props.button.disabled
},
// 是否加载中
loading: {
type: Boolean,
default: ()=> uni.$u.props.button.loading
},
// 加载中提示文字
loadingText: {
type: [String, Number],
default: ()=> uni.$u.props.button.loadingText
},
// 加载状态图标类型
loadingMode: {
type: String,
default: ()=> uni.$u.props.button.loadingMode
},
// 加载图标大小
loadingSize: {
type: [String, Number],
default: ()=> uni.$u.props.button.loadingSize
},
// 开放能力,具体请看uniapp稳定关于button组件部分说明
// https://uniapp.dcloud.io/component/button
openType: {
type: String,
default: ()=> uni.$u.props.button.openType
},
// 用于 <form> 组件,点击分别会触发 <form> 组件的 submit/reset 事件
// 取值为submit(提交表单),reset(重置表单)
formType: {
type: String,
default: ()=> uni.$u.props.button.formType
},
// 打开 APP 时,向 APP 传递的参数,open-type=launchApp时有效
// 只微信小程序、QQ小程序有效
appParameter: {
type: String,
default: ()=> uni.$u.props.button.appParameter
},
// 指定是否阻止本节点的祖先节点出现点击态,微信小程序有效
hoverStopPropagation: {
type: Boolean,
default: ()=> uni.$u.props.button.hoverStopPropagation
},
// 指定返回用户信息的语言,zh_CN 简体中文,zh_TW 繁体中文,en 英文。只微信小程序有效
lang: {
type: String,
default: ()=> uni.$u.props.button.lang
},
// 会话来源,open-type="contact"时有效。只微信小程序有效
sessionFrom: {
type: String,
default: ()=> uni.$u.props.button.sessionFrom
},
// 会话内消息卡片标题,open-type="contact"时有效
// 默认当前标题,只微信小程序有效
sendMessageTitle: {
type: String,
default: ()=> uni.$u.props.button.sendMessageTitle
},
// 会话内消息卡片点击跳转小程序路径,open-type="contact"时有效
// 默认当前分享路径,只微信小程序有效
sendMessagePath: {
type: String,
default: ()=> uni.$u.props.button.sendMessagePath
},
// 会话内消息卡片图片,open-type="contact"时有效
// 默认当前页面截图,只微信小程序有效
sendMessageImg: {
type: String,
default: ()=> uni.$u.props.button.sendMessageImg
},
// 是否显示会话内消息卡片,设置此参数为 true,用户进入客服会话会在右下角显示"可能要发送的小程序"提示,
// 用户点击后可以快速发送小程序消息,open-type="contact"时有效
showMessageCard: {
type: Boolean,
default: ()=> uni.$u.props.button.showMessageCard
},
// 额外传参参数,用于小程序的data-xxx属性,通过target.dataset.name获取
dataName: {
type: String,
default: ()=> uni.$u.props.button.dataName
},
// 节流,一定时间内只能触发一次
throttleTime: {
type: [String, Number],
default: ()=> uni.$u.props.button.throttleTime
},
// 按住后多久出现点击态,单位毫秒
hoverStartTime: {
type: [String, Number],
default: ()=> uni.$u.props.button.hoverStartTime
},
// 手指松开后点击态保留时间,单位毫秒
hoverStayTime: {
type: [String, Number],
default: ()=> uni.$u.props.button.hoverStayTime
},
// 按钮文字,之所以通过props传入,是因为slot传入的话
// nvue中无法控制文字的样式
text: {
type: [String, Number],
default: ()=> uni.$u.props.button.text
},
// 按钮图标
icon: {
type: String,
default: ()=> uni.$u.props.button.icon
},
// 按钮图标
iconColor: {
type: String,
default: ()=> uni.$u.props.button.iconColor
},
// 按钮图标位置:left, right
iconPosition: {
type: String,
default: ()=> uni.$u.props.button.iconPosition
},
// 按钮颜色,支持传入linear-gradient渐变色
color: {
type: String,
default: ()=> uni.$u.props.button.color
}
}
}
@@ -0,0 +1,497 @@
<template>
<!-- #ifndef APP-NVUE -->
<button
class="u-button u-reset-button"
:hover-class="!disabled && !loading ? 'u-button--active' : ''"
:style="[baseColor, $u.addStyle(customStyle)]"
:class="bemClass"
:hover-start-time="Number(hoverStartTime)"
:hover-stay-time="Number(hoverStayTime)"
:form-type="formType"
:open-type="openType"
:app-parameter="appParameter"
:hover-stop-propagation="hoverStopPropagation"
:send-message-title="sendMessageTitle"
:send-message-path="sendMessagePath"
:lang="lang"
:data-name="dataName"
:session-from="sessionFrom"
:send-message-img="sendMessageImg"
:show-message-card="showMessageCard"
@getphonenumber="getphonenumber"
@getuserinfo="getuserinfo"
@error="error"
@opensetting="opensetting"
@launchapp="launchapp"
@agreeprivacyauthorization="agreeprivacyauthorization"
@chooseavatar="chooseavatar"
@tap="clickHandler"
>
<template v-if="loading">
<u-loading-icon :mode="loadingMode" :size="loadingSize * 1.15" :color="elLoadingColor"></u-loading-icon>
<text class="u-button__loading-text" :style="[textStyle]">{{ loadingText || text}}</text>
</template>
<template v-else>
<u-icon v-if="icon" :name="icon" :color="elIconColor" :size="textSize * 1.35"></u-icon>
<slot>
<text class="u-button__text" :style="[textStyle]">{{ text }}</text>
</slot>
</template>
</button>
<!-- #endif -->
<!-- #ifdef APP-NVUE -->
<view
class="u-button"
:class="bemClass"
:style="[baseColor, $u.addStyle(customStyle)]"
:hover-start-time="Number(hoverStartTime)"
:hover-stay-time="Number(hoverStayTime)"
:hover-class="!disabled && !loading && !color && (plain || type === 'info')
? 'u-button--active--plain'
: !disabled && !loading && !plain
? 'u-button--active'
: ''
"
@tap="clickHandler"
>
<template v-if="loading">
<u-loading-icon :mode="loadingMode" :size="loadingSize * 1.15" :color="elLoadingColor"></u-loading-icon>
<text class="u-button__loading-text" :style="[textStyle]"
:class="[plain && `u-button__text--plain--${type}`]">{{ loadingText || text }}</text>
</template>
<template v-else>
<u-icon v-if="icon" :name="icon" :color="elIconColor" :size="textSize * 1.35"></u-icon>
<text class="u-button__text" :class="[plain && `u-button__text--plain--${type}`]"
:style="[textStyle]">{{ text }}</text>
</template>
</view>
<!-- #endif -->
</template>
<script>
import button from "../../libs/mixin/button.js"
import openType from "../../libs/mixin/openType.js"
import props from "./props.js"
import mixin from '../../libs/mixin/mixin'
import mpMixin from '../../libs/mixin/mpMixin'
/**
* button 按钮
* @description Button 按钮
* @tutorial https://www.uviewui.com/components/button.html
*
* @property {Boolean} hairline 是否显示按钮的细边框 (默认 true )
* @property {String} type 按钮的预置样式,info,primary,error,warning,success (默认 'info' )
* @property {String} size 按钮尺寸,large,normal,mini (默认 normal)
* @property {String} shape 按钮形状,circle(两边为半圆),square(带圆角) (默认 'square' )
* @property {Boolean} plain 按钮是否镂空,背景色透明 (默认 false)
* @property {Boolean} disabled 是否禁用 (默认 false)
* @property {Boolean} loading 按钮名称前是否带 loading 图标(App-nvue 平台,在 ios 上为雪花,Android上为圆圈) (默认 false)
* @property {String | Number} loadingText 加载中提示文字
* @property {String} loadingMode 加载状态图标类型 (默认 'spinner' )
* @property {String | Number} loadingSize 加载图标大小 (默认 15 )
* @property {String} openType 开放能力,具体请看uniapp稳定关于button组件部分说明
* @property {String} formType 用于 <form> 组件,点击分别会触发 <form> 组件的 submit/reset 事件
* @property {String} appParameter 打开 APP 时,向 APP 传递的参数,open-type=launchApp时有效 (注:只微信小程序、QQ小程序有效)
* @property {Boolean} hoverStopPropagation 指定是否阻止本节点的祖先节点出现点击态,微信小程序有效(默认 true )
* @property {String} lang 指定返回用户信息的语言,zh_CN 简体中文,zh_TW 繁体中文,en 英文(默认 en )
* @property {String} sessionFrom 会话来源,openType="contact"时有效
* @property {String} sendMessageTitle 会话内消息卡片标题,openType="contact"时有效
* @property {String} sendMessagePath 会话内消息卡片点击跳转小程序路径,openType="contact"时有效
* @property {String} sendMessageImg 会话内消息卡片图片,openType="contact"时有效
* @property {Boolean} showMessageCard 是否显示会话内消息卡片,设置此参数为 true,用户进入客服会话会在右下角显示"可能要发送的小程序"提示,用户点击后可以快速发送小程序消息,openType="contact"时有效(默认false)
* @property {String} dataName 额外传参参数,用于小程序的data-xxx属性,通过target.dataset.name获取
* @property {String | Number} throttleTime 节流,一定时间内只能触发一次 (默认 0 )
* @property {String | Number} hoverStartTime 按住后多久出现点击态,单位毫秒 (默认 0 )
* @property {String | Number} hoverStayTime 手指松开后点击态保留时间,单位毫秒 (默认 200 )
* @property {String | Number} text 按钮文字,之所以通过props传入,是因为slot传入的话(注:nvue中无法控制文字的样式)
* @property {String} icon 按钮图标
* @property {String} iconColor 按钮图标颜色
* @property {String} color 按钮颜色,支持传入linear-gradient渐变色
* @property {Object} customStyle 定义需要用到的外部样式
*
* @event {Function} click 非禁止并且非加载中,才能点击
* @event {Function} getphonenumber open-type="getPhoneNumber"时有效
* @event {Function} getuserinfo 用户点击该按钮时,会返回获取到的用户信息,从返回参数的detail中获取到的值同uni.getUserInfo
* @event {Function} error 当使用开放能力时,发生错误的回调
* @event {Function} opensetting 在打开授权设置页并关闭后回调
* @event {Function} launchapp 打开 APP 成功的回调
* @event {Function} chooseavatar 选择头像回调,openType="chooseAvatar"时有效
* @event {Function} agreeprivacyauthorization 用户同意隐私协议事件回调
* @example <u-button>月落</u-button>
*/
export default {
name: "u-button",
mixins: [
mpMixin,
mixin,
props,
// #ifdef MP
button,
openType
// #endif
],
data() {
return {
elLoadingColor:'',
elIconColor:''
};
},
computed: {
// 生成bem风格的类名
bemClass() {
// this.bem为一个computed变量,在mixin中
if (!this.color) {
return this.bem(
"button",
["type", "shape", "size"],
["disabled", "plain", "hairline"]
);
} else {
// 由于nvue的原因,在有color参数时,不需要传入type,否则会生成type相关的类型,影响最终的样式
return this.bem(
"button",
["shape", "size"],
["disabled", "plain", "hairline"]
);
}
},
baseColor() {
let style = {};
if (this.color) {
// 针对自定义了color颜色的情况,镂空状态下,就是用自定义的颜色
style.color = this.plain ? this.color : "white";
if (!this.plain) {
// 非镂空,背景色使用自定义的颜色
style["background-color"] = this.color;
}
if (this.color.indexOf("gradient") !== -1) {
// 如果自定义的颜色为渐变色,不显示边框,以及通过backgroundImage设置渐变色
// weex文档说明可以写borderWidth的形式,为什么这里需要分开写?
// 因为weex是阿里巴巴为了部门业绩考核而做的你懂的东西,所以需要这么写才有效
style.borderTopWidth = 0;
style.borderRightWidth = 0;
style.borderBottomWidth = 0;
style.borderLeftWidth = 0;
if (!this.plain) {
style.backgroundImage = this.color;
}
} else {
// 非渐变色,则设置边框相关的属性
style.borderColor = this.color;
style.borderWidth = "1px";
style.borderStyle = "solid";
}
}
// 为了兼容安卓nvue,只能这么分开写
let radius = this.shape == 'circle' ? 100 : this.round
if(radius) {
radius = uni.$u.addUnit(radius)
style.borderTopLeftRadius = radius
style.borderTopRightRadius = radius
style.borderBottomLeftRadius = radius
style.borderBottomRightRadius = radius
}
if(this.icon && this.iconPosition === 'right'){
style.flexDirection = 'row-reverse';
}
return style;
},
// nvue版本按钮的字体不会继承父组件的颜色,需要对每一个text组件进行单独的设置
textStyle() {
let style = {};
style.fontSize = uni.$u.addUnit(this.textSize);
if(this.icon){
if(this.iconPosition === 'left'){
style.marginLeft = uni.$u.addUnit(2);
}else{
style.marginRight = uni.$u.addUnit(2);
}
}
//#ifdef APP-NVUE
// 针对自定义了color颜色的情况,镂空状态下,就是用自定义的颜色
if (this.type === "info") {
style.color = this.$u.theme.mainColor;
}
if (this.color) {
style.color = this.plain ? this.color : "white";
}
// #endif
return style;
},
// 字体大小
textSize() {
let fontSize = 14,
{ size } = this;
if (size === "large") fontSize = 16;
if (size === "normal") fontSize = 14;
if (size === "small") fontSize = 12;
if (size === "mini") fontSize = 10;
return fontSize;
},
},
watch: {
color: {
immediate: true,
handler(newVal) {
this.iconColorCom()
this.loadingColorCom()
}
},
iconColor: {
immediate: true,
handler(newVal) {
this.iconColorCom()
}
}
},
// #ifdef VUE3
emits: ['click', 'error', 'launchapp', 'opensetting', 'getuserinfo', 'getphonenumber', 'agreeprivacyauthorization', 'chooseavatar'],
// #endif
methods: {
iconColorCom() {
// 如果是镂空状态,设置了color就用color值,否则使用主题颜色,
// u-icon的color能接受一个主题颜色的值
if (this.iconColor) this.elIconColor = this.iconColor;
if (this.plain) {
this.elIconColor = this.color ? this.color : this.type;
} else {
this.elIconColor = this.type === "info" ? "#000000" : "#ffffff";
}
},
loadingColorCom() {
if (this.plain) {
// 如果有设置color值,则用color值,否则使用type主题颜色
this.elLoadingColor = this.color ? this.color : this.$u.theme[this.type];
}
if (this.type === "info") {
this.elLoadingColor = "#c9c9c9";
}
this.elLoadingColor = "rgb(200, 200, 200)";
},
clickHandler(e) {
// 非禁止并且非加载中,才能点击
if (!this.disabled && !this.loading) {
// 进行节流控制,每this.throttle毫秒内,只在开始处执行
uni.$u.throttle(() => {
this.$emit("click", e);
}, this.throttleTime);
}
},
// 下面为对接uniapp官方按钮开放能力事件回调的对接
getphonenumber(res) {
this.$emit("getphonenumber", res);
},
getuserinfo(res) {
this.$emit("getuserinfo", res);
},
error(res) {
this.$emit("error", res);
},
opensetting(res) {
this.$emit("opensetting", res);
},
launchapp(res) {
this.$emit("launchapp", res);
},
agreeprivacyauthorization(res) {
this.$emit("agreeprivacyauthorization", res);
},
chooseavatar(res) {
this.$emit("chooseavatar", res);
},
},
};
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
/* #ifndef APP-NVUE */
@import "./vue.scss";
/* #endif */
/* #ifdef APP-NVUE */
@import "./nvue.scss";
/* #endif */
$u-button-u-button-height: 40px !default;
$u-button-text-font-size: 15px !default;
$u-button-loading-text-font-size: 15px !default;
$u-button-loading-text-margin-left: 4px !default;
$u-button-large-width: 100% !default;
$u-button-large-height: 50px !default;
$u-button-normal-padding: 0 12px !default;
$u-button-large-padding: 0 15px !default;
$u-button-normal-font-size: 14px !default;
$u-button-small-min-width: 60px !default;
$u-button-small-height: 30px !default;
$u-button-small-padding: 0px 8px !default;
$u-button-mini-padding: 0px 8px !default;
$u-button-small-font-size: 12px !default;
$u-button-mini-height: 22px !default;
$u-button-mini-font-size: 10px !default;
$u-button-mini-min-width: 50px !default;
$u-button-disabled-opacity: 0.5 !default;
$u-button-info-color: #323233 !default;
$u-button-info-background-color: $u-info-light !default;
$u-button-info-border-color: $u-info-light !default;
$u-button-info-border-width: 1px !default;
$u-button-info-border-style: solid !default;
$u-button-success-color: #fff !default;
$u-button-success-background-color: $u-success !default;
$u-button-success-border-color: $u-button-success-background-color !default;
$u-button-success-border-width: 1px !default;
$u-button-success-border-style: solid !default;
$u-button-primary-color: #fff !default;
$u-button-primary-background-color: $u-primary !default;
$u-button-primary-border-color: $u-button-primary-background-color !default;
$u-button-primary-border-width: 1px !default;
$u-button-primary-border-style: solid !default;
$u-button-error-color: #fff !default;
$u-button-error-background-color: $u-error !default;
$u-button-error-border-color: $u-button-error-background-color !default;
$u-button-error-border-width: 1px !default;
$u-button-error-border-style: solid !default;
$u-button-warning-color: #fff !default;
$u-button-warning-background-color: $u-warning !default;
$u-button-warning-border-color: $u-button-warning-background-color !default;
$u-button-warning-border-width: 1px !default;
$u-button-warning-border-style: solid !default;
$u-button-block-width: 100% !default;
$u-button-icon-min-width: 1em !default;
$u-button-plain-background-color: #fff !default;
$u-button-hairline-border-width: 0.5px !default;
.u-button {
position: relative;
align-items: center;
justify-content: center;
@include flex;
/* #ifndef APP-NVUE */
box-sizing: border-box;
/* #endif */
flex-direction: row;
&__text {
font-size: $u-button-text-font-size;
}
&__loading-text {
font-size: $u-button-loading-text-font-size;
margin-left: $u-button-loading-text-margin-left;
}
&--large {
/* #ifndef APP-NVUE */
width: $u-button-large-width;
/* #endif */
height: $u-button-large-height;
padding: $u-button-large-padding;
}
&--normal {
height: $u-button-u-button-height;
padding: $u-button-normal-padding;
font-size: $u-button-normal-font-size;
}
&--small {
/* #ifndef APP-NVUE */
min-width: $u-button-small-min-width;
/* #endif */
height: $u-button-small-height;
padding: $u-button-small-padding;
font-size: $u-button-small-font-size;
}
&--mini {
height: $u-button-mini-height;
font-size: $u-button-mini-font-size;
/* #ifndef APP-NVUE */
min-width: $u-button-mini-min-width;
/* #endif */
padding: $u-button-mini-padding;
}
&--disabled {
opacity: $u-button-disabled-opacity;
}
&--info {
color: $u-button-info-color;
background-color: $u-button-info-background-color;
border-color: $u-button-info-border-color;
border-width: $u-button-info-border-width;
border-style: $u-button-info-border-style;
}
&--success {
color: $u-button-success-color;
background-color: $u-button-success-background-color;
border-color: $u-button-success-border-color;
border-width: $u-button-success-border-width;
border-style: $u-button-success-border-style;
}
&--primary {
color: $u-button-primary-color;
background-color: $u-button-primary-background-color;
border-color: $u-button-primary-border-color;
border-width: $u-button-primary-border-width;
border-style: $u-button-primary-border-style;
}
&--error {
color: $u-button-error-color;
background-color: $u-button-error-background-color;
border-color: $u-button-error-border-color;
border-width: $u-button-error-border-width;
border-style: $u-button-error-border-style;
}
&--warning {
color: $u-button-warning-color;
background-color: $u-button-warning-background-color;
border-color: $u-button-warning-border-color;
border-width: $u-button-warning-border-width;
border-style: $u-button-warning-border-style;
}
&--block {
@include flex;
width: $u-button-block-width;
}
&--circle {
}
&--square {
}
&__icon {
/* #ifndef APP-NVUE */
min-width: $u-button-icon-min-width;
line-height: inherit !important;
vertical-align: top;
/* #endif */
}
&--plain {
background-color: $u-button-plain-background-color;
}
&--hairline {
border-width: $u-button-hairline-border-width !important;
}
}
</style>
@@ -0,0 +1,80 @@
// nvue下hover-class无效
$u-button-before-top:50% !default;
$u-button-before-left:50% !default;
$u-button-before-width:100% !default;
$u-button-before-height:100% !default;
$u-button-before-transform:translate(-50%, -50%) !default;
$u-button-before-opacity:0 !default;
$u-button-before-background-color:#000 !default;
$u-button-before-border-color:#000 !default;
$u-button-active-before-opacity:.15 !default;
$u-button-icon-margin-left:4px !default;
$u-button-plain-u-button-info-color:$u-info;
$u-button-plain-u-button-success-color:$u-success;
$u-button-plain-u-button-error-color:$u-error;
$u-button-plain-u-button-warning-color:$u-error;
.u-button {
width: 100%;
&__text {
white-space: nowrap;
line-height: 1;
}
&:before {
position: absolute;
top:$u-button-before-top;
left:$u-button-before-left;
width:$u-button-before-width;
height:$u-button-before-height;
border: inherit;
border-radius: inherit;
transform:$u-button-before-transform;
opacity:$u-button-before-opacity;
content: " ";
background-color:$u-button-before-background-color;
border-color:$u-button-before-border-color;
}
&--active {
&:before {
opacity: .15
}
}
&__icon+&__text:not(:empty),
&__loading-text {
margin-left:$u-button-icon-margin-left;
}
&--plain {
&.u-button--primary {
color: $u-primary;
}
}
&--plain {
&.u-button--info {
color:$u-button-plain-u-button-info-color;
}
}
&--plain {
&.u-button--success {
color:$u-button-plain-u-button-success-color;
}
}
&--plain {
&.u-button--error {
color:$u-button-plain-u-button-error-color;
}
}
&--plain {
&.u-button--warning {
color:$u-button-plain-u-button-warning-color;
}
}
}
@@ -0,0 +1,105 @@
<template>
<view class="u-calendar-header u-border-bottom">
<text class="u-calendar-header__title" v-if="showTitle">
{{ title }}
</text>
<text class="u-calendar-header__subtitle" v-if="showSubtitle">
{{ subtitle }}
</text>
<view class="u-calendar-header__weekdays">
<text
v-for="(item, index) in weekdaysList"
:key="index"
class="u-calendar-header__weekdays__weekday"
>
{{ item }}
</text>
</view>
</view>
</template>
<script>
import mixin from '../../libs/mixin/mixin';
import mpMixin from '../../libs/mixin/mpMixin';
export default {
name: 'u-calendar-header',
mixins: [mpMixin, mixin],
props: {
// 标题
title: {
type: String,
default: '',
},
// 副标题
subtitle: {
type: String,
default: '',
},
// 是否显示标题
showTitle: {
type: Boolean,
default: true,
},
// 是否显示副标题
showSubtitle: {
type: Boolean,
default: true,
},
// 星期几
weekdays: {
type: String,
default: '',
},
},
data() {
return {};
},
computed: {
weekdaysList() {
return this.weekdays.split(',');
},
},
methods: {
name() {},
},
};
</script>
<style lang="scss" scoped>
@import '../../libs/css/components.scss';
.u-calendar-header {
padding-bottom: 4px;
&__title {
font-size: 16px;
color: $u-main-color;
text-align: center;
height: 42px;
line-height: 42px;
font-weight: bold;
}
&__subtitle {
font-size: 14px;
color: $u-main-color;
height: 40px;
text-align: center;
line-height: 40px;
font-weight: bold;
}
&__weekdays {
@include flex;
justify-content: space-between;
&__weekday {
font-size: 13px;
color: $u-main-color;
line-height: 30px;
flex: 1;
text-align: center;
}
}
}
</style>
@@ -0,0 +1,591 @@
<template>
<view class="u-calendar-month-wrapper" ref="u-calendar-month-wrapper">
<view v-for="(item, index) in months" :key="index" :class="[`u-calendar-month-${index}`]"
:ref="`u-calendar-month-${index}`" :id="`month-${index}`">
<text v-if="index !== 0" class="u-calendar-month__title">{{ item.year }}年{{ item.month }}月</text>
<view class="u-calendar-month__days">
<view v-if="showMark" class="u-calendar-month__days__month-mark-wrapper">
<text class="u-calendar-month__days__month-mark-wrapper__text">{{ item.month }}</text>
</view>
<view class="u-calendar-month__days__day" v-for="(item1, index1) in item.date" :key="index1"
:style="[dayStyle(index, index1, item1)]" @tap="clickHandler(index, index1, item1)"
:class="[item1.selected && 'u-calendar-month__days__day__select--selected']">
<view class="u-calendar-month__days__day__select" :style="[daySelectStyle(index, index1, item1)]">
<text class="u-calendar-month__days__day__select__info"
:class="[item1.disabled && 'u-calendar-month__days__day__select__info--disabled']"
:style="[textStyle(item1)]">{{ item1.day }}</text>
<text v-if="getBottomInfo(index, index1, item1)"
class="u-calendar-month__days__day__select__buttom-info"
:class="[item1.disabled && 'u-calendar-month__days__day__select__buttom-info--disabled']"
:style="[textStyle(item1)]">{{ getBottomInfo(index, index1, item1) }}</text>
<text v-if="item1.dot" class="u-calendar-month__days__day__select__dot"></text>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
import mixin from '../../libs/mixin/mixin'
import mpMixin from '../../libs/mixin/mpMixin'
// #ifdef APP-NVUE
// 由于nvue不支持百分比单位,需要查询宽度来计算每个日期的宽度
const dom = uni.requireNativePlugin('dom')
// #endif
import dayjs from '../../libs/util/dayjs.js';
export default {
name: 'u-calendar-month',
mixins: [mpMixin, mixin],
props: {
// 是否显示月份背景色
showMark: {
type: Boolean,
default: true
},
// 主题色,对底部按钮和选中日期有效
color: {
type: String,
default: '#3c9cff'
},
// 月份数据
months: {
type: Array,
default: () => []
},
// 日期选择类型
mode: {
type: String,
default: 'single'
},
// 日期行高
rowHeight: {
type: [String, Number],
default: 58
},
// mode=multiple时,最多可选多少个日期
maxCount: {
type: [String, Number],
default: Infinity
},
// mode=range时,第一个日期底部的提示文字
startText: {
type: String,
default: '开始'
},
// mode=range时,最后一个日期底部的提示文字
endText: {
type: String,
default: '结束'
},
// 默认选中的日期,mode为multiple或range是必须为数组格式
defaultDate: {
type: [Array, String, Date],
default: null
},
// 最小的可选日期
minDate: {
type: [String, Number],
default: 0
},
// 最大可选日期
maxDate: {
type: [String, Number],
default: 0
},
// 如果没有设置maxDate,则往后推多少个月
maxMonth: {
type: [String, Number],
default: 2
},
// 是否为只读状态,只读状态下禁止选择日期
readonly: {
type: Boolean,
default: false
},
// 日期区间最多可选天数,默认无限制,mode = range时有效
maxRange: {
type: [Number, String],
default: Infinity
},
// 范围选择超过最多可选天数时的提示文案,mode = range时有效
rangePrompt: {
type: String,
default: ''
},
// 范围选择超过最多可选天数时,是否展示提示文案,mode = range时有效
showRangePrompt: {
type: Boolean,
default: true
},
// 是否允许日期范围的起止时间为同一天,mode = range时有效
allowSameDay: {
type: Boolean,
default: false
},
// 选中的日期的形状,circle(圆形),square(带圆角) (默认 'square' )
shape: {
type: String,
default: 'square'
},
},
data() {
return {
// 每个日期的宽度
width: 0,
// 当前选中的日期item
item: {},
selected: []
}
},
watch: {
selectedChange: {
immediate: true,
handler(n) {
this.setDefaultDate()
}
}
},
computed: {
// 多个条件的变化,会引起选中日期的变化,这里统一管理监听
selectedChange() {
return [this.minDate, this.maxDate, this.defaultDate]
},
dayStyle(index1, index2, item) {
return (index1, index2, item) => {
const style = {}
let week = item.week
// 不进行四舍五入的形式保留2位小数
const dayWidth = Number(parseFloat(this.width / 7).toFixed(3).slice(0, -1))
// 得出每个日期的宽度
// #ifdef APP-NVUE
style.width = uni.$u.addUnit(dayWidth)
// #endif
style.height = uni.$u.addUnit(this.rowHeight)
if (index2 === 0) {
// 获取当前为星期几,如果为0,则为星期天,减一为每月第一天时,需要向左偏移的item个数
week = (week === 0 ? 7 : week) - 1
style.marginLeft = uni.$u.addUnit(week * dayWidth)
}
if (this.mode === 'range') {
// 之所以需要这么写,是因为DCloud公司的iOS客户端的开发者能力有限导致的bug
style.paddingLeft = 0
style.paddingRight = 0
style.paddingBottom = 0
style.paddingTop = 0
}
return style
}
},
daySelectStyle() {
return (index1, index2, item) => {
let date = dayjs(item.date).format("YYYY-MM-DD"),
style = {}
// 判断date是否在selected数组中,因为月份可能会需要补0,所以使用dateSame判断,而不用数组的includes判断
if (this.selected.some(item => this.dateSame(item, date))) {
style.backgroundColor = this.color
}
let radius = this.shape === 'circle' ? '100%' : '5px'
if (this.mode === 'single') {
if (date === this.selected[0]) {
// 因为需要对nvue的兼容,只能这么写,无法缩写,也无法通过类名控制等等
style.borderTopLeftRadius = radius
style.borderBottomLeftRadius = radius
style.borderTopRightRadius = radius
style.borderBottomRightRadius = radius
}
} else if (this.mode === 'range') {
if (this.selected.length >= 2) {
const len = this.selected.length - 1
// 第一个日期设置左上角和左下角的圆角
if (this.dateSame(date, this.selected[0])) {
style.borderTopLeftRadius = radius
style.borderBottomLeftRadius = radius
}
// 最后一个日期设置右上角和右下角的圆角
if (this.dateSame(date, this.selected[len])) {
style.borderTopRightRadius = radius
style.borderBottomRightRadius = radius
}
// 处于第一和最后一个之间的日期,背景色设置为浅色,通过将对应颜色进行等分,再取其尾部的颜色值
if (dayjs(date).isAfter(dayjs(this.selected[0])) && dayjs(date).isBefore(dayjs(this
.selected[len]))) {
style.backgroundColor = uni.$u.colorGradient(this.color, '#ffffff', 100)[90]
// 增加一个透明度,让范围区间的背景色也能看到底部的mark水印字符
style.opacity = 0.7
}
} else if (this.selected.length === 1) {
// 之所以需要这么写,是因为DCloud公司的iOS客户端的开发者能力有限导致的bug
// 进行还原操作,否则在nvue的iOS,uni-app有bug,会导致诡异的表现
style.borderTopLeftRadius = radius
style.borderBottomLeftRadius = radius
}
} else {
if (this.selected.some(item => this.dateSame(item, date))) {
style.borderTopLeftRadius = radius
style.borderBottomLeftRadius = radius
style.borderTopRightRadius = radius
style.borderBottomRightRadius = radius
}
}
return style
}
},
// 某个日期是否被选中
textStyle() {
return (item) => {
const date = dayjs(item.date).format("YYYY-MM-DD"),
style = {}
// 选中的日期,提示文字设置白色
if (this.selected.some(item => this.dateSame(item, date))) {
style.color = '#ffffff'
}
if (this.mode === 'range') {
const len = this.selected.length - 1
// 如果是范围选择模式,第一个和最后一个之间的日期,文字颜色设置为高亮的主题色
if (dayjs(date).isAfter(dayjs(this.selected[0])) && dayjs(date).isBefore(dayjs(this
.selected[len]))) {
style.color = this.color
}
}
return style
}
},
// 获取底部的提示文字
getBottomInfo() {
return (index1, index2, item) => {
const date = dayjs(item.date).format("YYYY-MM-DD")
const bottomInfo = item.bottomInfo
// 当为日期范围模式时,且选择的日期个数大于0时
if (this.mode === 'range' && this.selected.length > 0) {
if (this.selected.length === 1) {
// 选择了一个日期时,如果当前日期为数组中的第一个日期,则显示底部文字为“开始”
if (this.dateSame(date, this.selected[0])) return this.startText
else return bottomInfo
} else {
const len = this.selected.length - 1
// 如果数组中的日期大于2个时,第一个和最后一个显示为开始和结束日期
if (this.dateSame(date, this.selected[0]) && this.dateSame(date, this.selected[1]) &&
len === 1) {
// 如果长度为2,且第一个等于第二个日期,则提示语放在同一个item中
return `${this.startText}/${this.endText}`
} else if (this.dateSame(date, this.selected[0])) {
return this.startText
} else if (this.dateSame(date, this.selected[len])) {
return this.endText
} else {
return bottomInfo
}
}
} else {
return bottomInfo
}
}
}
},
mounted() {
this.init()
},
// #ifdef VUE3
emits: ["monthSelected", "updateMonthTop"],
// #endif
methods: {
init() {
// 初始化默认选中
this.$emit('monthSelected', this.selected)
this.$nextTick(() => {
// 这里需要另一个延时,因为获取宽度后,会进行月份数据渲染,只有渲染完成之后,才有真正的高度
// 因为nvue下,$nextTick并不是100%可靠的
uni.$u.sleep(10).then(() => {
this.getWrapperWidth()
this.getMonthRect()
})
})
},
// 判断两个日期是否相等
dateSame(date1, date2) {
return dayjs(date1).isSame(dayjs(date2))
},
// 获取月份数据区域的宽度,因为nvue不支持百分比,所以无法通过css设置每个日期item的宽度
getWrapperWidth() {
// #ifdef APP-NVUE
dom.getComponentRect(this.$refs['u-calendar-month-wrapper'], res => {
this.width = res.size.width
})
// #endif
// #ifndef APP-NVUE
this.$uGetRect('.u-calendar-month-wrapper').then(size => {
this.width = size.width
})
// #endif
},
getMonthRect() {
// 获取每个月份数据的尺寸,用于父组件在scroll-view滚动事件中,监听当前滚动到了第几个月份
const promiseAllArr = this.months.map((item, index) => this.getMonthRectByPromise(
`u-calendar-month-${index}`))
// 一次性返回
Promise.all(promiseAllArr).then(
sizes => {
let height = 1
const topArr = []
for (let i = 0; i < this.months.length; i++) {
// 添加到months数组中,供scroll-view滚动事件中,判断当前滚动到哪个月份
topArr[i] = height
height += sizes[i].height
}
// 由于微信下,无法通过this.months[i].top的形式(引用类型)去修改父组件的month的top值,所以使用事件形式对外发出
this.$emit('updateMonthTop', topArr)
})
},
// 获取每个月份区域的尺寸
getMonthRectByPromise(el) {
// #ifndef APP-NVUE
// $uGetRect为uView自带的节点查询简化方法,详见文档介绍:https://uview.d3u.cn/js/getRect.html
// 组件内部一般用this.$uGetRect,对外的为uni.$u.getRect,二者功能一致,名称不同
return new Promise(resolve => {
this.$uGetRect(`.${el}`).then(size => {
resolve(size)
})
})
// #endif
// #ifdef APP-NVUE
// nvue下,使用dom模块查询元素高度
// 返回一个promise,让调用此方法的主体能使用then回调
return new Promise(resolve => {
dom.getComponentRect(this.$refs[el][0], res => {
resolve(res.size)
})
})
// #endif
},
// 点击某一个日期
clickHandler(index1, index2, item) {
if (this.readonly) {
return;
}
this.item = item
const date = dayjs(item.date).format("YYYY-MM-DD")
if (item.disabled) return
// 对上一次选择的日期数组进行深度克隆
let selected = uni.$u.deepClone(this.selected)
if (this.mode === 'single') {
// 单选情况下,让数组中的元素为当前点击的日期
selected = [date]
} else if (this.mode === 'multiple') {
if (selected.some(item => this.dateSame(item, date))) {
// 如果点击的日期已在数组中,则进行移除操作,也就是达到反选的效果
const itemIndex = selected.findIndex(item => item === date)
selected.splice(itemIndex, 1)
} else {
// 如果点击的日期不在数组中,且已有的长度小于总可选长度时,则添加到数组中去
if (selected.length < this.maxCount) selected.push(date)
}
} else {
// 选择区间形式
if (selected.length === 0 || selected.length >= 2) {
// 如果原来就为0或者大于2的长度,则当前点击的日期,就是开始日期
selected = [date]
} else if (selected.length === 1) {
// 如果已经选择了开始日期
const existsDate = selected[0]
// 如果当前选择的日期小于上一次选择的日期,则当前的日期定为开始日期
if (dayjs(date).isBefore(existsDate)) {
selected = [date]
} else if (dayjs(date).isAfter(existsDate)) {
// 当前日期减去最大可选的日期天数,如果大于起始时间,则进行提示
if (dayjs(dayjs(date).subtract(this.maxRange, 'day')).isAfter(dayjs(selected[0])) && this.showRangePrompt) {
if (this.rangePrompt) {
uni.$u.toast(this.rangePrompt)
} else {
uni.$u.toast(`选择天数不能超过 ${this.maxRange} 天`)
}
return
}
// 如果当前日期大于已有日期,将当前的添加到数组尾部
selected.push(date)
const startDate = selected[0]
const endDate = selected[1]
const arr = []
let i = 0
do {
// 将开始和结束日期之间的日期添加到数组中
arr.push(dayjs(startDate).add(i, 'day').format("YYYY-MM-DD"))
i++
// 累加的日期小于结束日期时,继续下一次的循环
} while (dayjs(startDate).add(i, 'day').isBefore(dayjs(endDate)))
// 为了一次性修改数组,避免computed中多次触发,这里才用arr变量一次性赋值的方式,同时将最后一个日期添加近来
arr.push(endDate)
selected = arr
} else {
// 选择区间时,只有一个日期的情况下,且不允许选择起止为同一天的话,不允许选择自己
if (selected[0] === date && !this.allowSameDay) return
selected.push(date)
}
}
}
this.setSelected(selected)
},
// 设置默认日期
setDefaultDate() {
if (!this.defaultDate) {
// 如果没有设置默认日期,则将当天日期设置为默认选中的日期
const selected = [dayjs().format("YYYY-MM-DD")]
return this.setSelected(selected, false)
}
let defaultDate = []
const minDate = this.minDate || dayjs().format("YYYY-MM-DD")
const maxDate = this.maxDate || dayjs(minDate).add(this.maxMonth - 1, 'month').format("YYYY-MM-DD")
if (this.mode === 'single') {
// 单选模式,可以是字符串或数组,Date对象等
if (!uni.$u.test.array(this.defaultDate)) {
defaultDate = [dayjs(this.defaultDate).format("YYYY-MM-DD")]
} else {
defaultDate = [this.defaultDate[0]]
}
} else {
// 如果为非数组,则不执行
if (!uni.$u.test.array(this.defaultDate)) return
defaultDate = this.defaultDate
}
// 过滤用户传递的默认数组,取出只在可允许最大值与最小值之间的元素
defaultDate = defaultDate.filter(item => {
return dayjs(item).isAfter(dayjs(minDate).subtract(1, 'day')) && dayjs(item).isBefore(dayjs(
maxDate).add(1, 'day'))
})
this.setSelected(defaultDate, false)
},
setSelected(selected, event = true) {
this.selected = selected
event && this.$emit('monthSelected', this.selected)
}
}
}
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
.u-calendar-month-wrapper {
margin-top: 4px;
}
.u-calendar-month {
&__title {
font-size: 14px;
line-height: 42px;
height: 42px;
color: $u-main-color;
text-align: center;
font-weight: bold;
}
&__days {
position: relative;
@include flex;
flex-wrap: wrap;
&__month-mark-wrapper {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
@include flex;
justify-content: center;
align-items: center;
&__text {
font-size: 155px;
color: rgba(231, 232, 234, 0.83);
}
}
&__day {
@include flex;
padding: 2px;
/* #ifndef APP-NVUE */
// vue下使用css进行宽度计算,因为某些安卓机会无法进行js获取父元素宽度进行计算得出,会有偏移
width: calc(100% / 7);
box-sizing: border-box;
/* #endif */
&__select {
flex: 1;
@include flex;
align-items: center;
justify-content: center;
position: relative;
&__dot {
width: 7px;
height: 7px;
border-radius: 100px;
background-color: $u-error;
position: absolute;
top: 12px;
right: 7px;
}
&__buttom-info {
color: $u-content-color;
text-align: center;
position: absolute;
bottom: 5px;
font-size: 10px;
text-align: center;
left: 0;
right: 0;
&--selected {
color: #ffffff;
}
&--disabled {
color: #cacbcd;
}
}
&__info {
text-align: center;
font-size: 16px;
&--selected {
color: #ffffff;
}
&--disabled {
color: #cacbcd;
}
}
&--selected {
background-color: $u-primary;
@include flex;
justify-content: center;
align-items: center;
flex: 1;
border-radius: 3px;
}
&--range-selected {
opacity: 0.3;
border-radius: 0;
}
&--range-start-selected {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
&--range-end-selected {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
}
}
}
}
</style>

Some files were not shown because too many files have changed in this diff Show More