diff --git a/.gitignore b/.gitignore index ceaea36..4f0c686 100644 --- a/.gitignore +++ b/.gitignore @@ -1,132 +1,10 @@ -# ---> Node -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -lerna-debug.log* -.pnpm-debug.log* - -# Diagnostic reports (https://nodejs.org/api/report.html) -report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json - -# Runtime data -pids -*.pid -*.seed -*.pid.lock - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage -*.lcov - -# nyc test coverage -.nyc_output - -# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# Bower dependency directory (https://bower.io/) -bower_components - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (https://nodejs.org/api/addons.html) -build/Release - -# Dependency directories -node_modules/ -jspm_packages/ - -# Snowpack dependency directory (https://snowpack.dev/) -web_modules/ - -# TypeScript cache -*.tsbuildinfo - -# Optional npm cache directory -.npm - -# Optional eslint cache -.eslintcache - -# Optional stylelint cache -.stylelintcache - -# Microbundle cache -.rpt2_cache/ -.rts2_cache_cjs/ -.rts2_cache_es/ -.rts2_cache_umd/ - -# Optional REPL history -.node_repl_history - -# Output of 'npm pack' -*.tgz - -# Yarn Integrity file -.yarn-integrity - -# dotenv environment variable files -.env -.env.development.local -.env.test.local -.env.production.local -.env.local - -# parcel-bundler cache (https://parceljs.org/) -.cache -.parcel-cache - -# Next.js build output -.next -out - -# Nuxt.js build / generate output -.nuxt -dist - -# Gatsby files -.cache/ -# Comment in the public line in if your project uses Gatsby and not Next.js -# https://nextjs.org/blog/next-9-1#public-directory-support -# public - -# vuepress build output -.vuepress/dist - -# vuepress v2.x temp and cache directory -.temp -.cache - -# Docusaurus cache and generated files -.docusaurus - -# Serverless directories -.serverless/ - -# FuseBox cache -.fusebox/ - -# DynamoDB Local files -.dynamodb/ - -# TernJS port file -.tern-port - -# Stores VSCode versions used for testing VSCode extensions -.vscode-test - -# yarn v2 -.yarn/cache -.yarn/unplugged -.yarn/build-state.yml -.yarn/install-state.gz -.pnp.* - +.project +.DS_Store +*/.DS_Store +/.git/ +/.idea +/.hbuilderx +/.unpackage +/unpackage +unpackage/ +/.rar diff --git a/App.vue b/App.vue new file mode 100644 index 0000000..2cd4e5e --- /dev/null +++ b/App.vue @@ -0,0 +1,39 @@ + + + diff --git a/README.md b/README.md index 6d98f9a..5da3f95 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,3 @@ # orico-wms-pda -wms-pda(移动端) \ No newline at end of file +仓储系统PDA端 \ No newline at end of file diff --git a/components/w-select/readme.md b/components/w-select/readme.md new file mode 100644 index 0000000..106479e --- /dev/null +++ b/components/w-select/readme.md @@ -0,0 +1,72 @@ +#### props + +|名称 |类型 |默认值 |说明 | +|- |- |- |- | +|width |string |'200px' |选择框宽度 | +|height |string |'30px' |选择框高度 | +|bgColor |string |'#fff' |选择框背景颜色 | +|round |boolean|true |是否开启圆角 | +|defaultValue |string |'请选择'|默认显示的名称| | +|valueName |string |'label' |显示的内容字段名,必传 | +|keyName |string |'value' |绑定的内容字段名,必传 | +|list |array |[] |展示的内容列表 | +|showClose |boolean|true |是否显示删除按钮 | +|multiple |boolean|false |是否开启多选 | +|filterable |boolean|false |是否开启搜索功能,开启后直接输入值不选择也可以保存内容 | + + +#### events + +|事件名|说明| +|-|-| +|change|选择的内容改变时触发,返回的参数为列表的item| + + +#### 使用举例 + +需要绑定的值通过`v-model`绑定,如下面的`chooseValue`,需要获取item的值可以监听`@change`事件 + +开启多选`multiple`时,双向绑定的值为数组类型,在change事件中根据自己需求进行处理。 + +`valueName`属性必须传,告知组件需要显示的是什么值 + +```html + + +``` \ No newline at end of file diff --git a/components/w-select/w-select.vue b/components/w-select/w-select.vue new file mode 100644 index 0000000..57d5689 --- /dev/null +++ b/components/w-select/w-select.vue @@ -0,0 +1,630 @@ + + + + \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..c3ff205 --- /dev/null +++ b/index.html @@ -0,0 +1,20 @@ + + + + + + + + + + +
+ + + diff --git a/main.js b/main.js new file mode 100644 index 0000000..838ca97 --- /dev/null +++ b/main.js @@ -0,0 +1,39 @@ +import App from './App' + +// #ifndef VUE3 +import Vue from 'vue' +import './uni.promisify.adaptor' + +// 引入全局uView +import uView from '@/uni_modules/uview-ui' +import {broadcastScan} from '@/static/js/scanCode' +import api from '@/static/js/http' +import * as Util from "@/static/js/public.js"; + +import dir from '@/static/js/directive' +Vue.directive('preventReClick', dir.preventReClick) +//全局注入文件,方便在各个功能中直接使用 +Vue.prototype.$broadcastScan=broadcastScan +Vue.config.productionTip = false +Vue.prototype.$api = api +Vue.prototype.$util = Util; +App.mpType = 'app' +const app = new Vue({ + ...App +}) + +app.$mount() +Vue.use(uView) + + +// #endif + +// #ifdef VUE3 +import { createSSRApp } from 'vue' +export function createApp() { + const app = createSSRApp(App) + return { + app + } +} +// #endif \ No newline at end of file diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..656c0b9 --- /dev/null +++ b/manifest.json @@ -0,0 +1,108 @@ +{ + "name" : "wms", + "appid" : "__UNI__2420758", + "description" : "", + "versionName" : "1.0.6", + "versionCode" : 106, + "transformPx" : false, + /* 5+App特有相关 */ + "app-plus" : { + "usingComponents" : true, + "nvueStyleCompiler" : "uni-app", + "compilerVersion" : 3, + "splashscreen" : { + "alwaysShowBeforeRender" : true, + "waiting" : true, + "autoclose" : true, + "delay" : 0 + }, + /* 模块配置 */ + "modules" : {}, + /* 应用发布信息 */ + "distribute" : { + /* android打包配置 */ + "android" : { + "permissions" : [ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ] + }, + /* ios打包配置 */ + "ios" : { + "dSYMs" : false + }, + /* SDK配置 */ + "sdkConfigs" : { + "ad" : {} + }, + "icons" : { + "android" : { + "hdpi" : "unpackage/res/icons/72x72.png", + "xhdpi" : "unpackage/res/icons/96x96.png", + "xxhdpi" : "unpackage/res/icons/144x144.png", + "xxxhdpi" : "unpackage/res/icons/192x192.png" + }, + "ios" : { + "appstore" : "unpackage/res/icons/1024x1024.png", + "ipad" : { + "app" : "unpackage/res/icons/76x76.png", + "app@2x" : "unpackage/res/icons/152x152.png", + "notification" : "unpackage/res/icons/20x20.png", + "notification@2x" : "unpackage/res/icons/40x40.png", + "proapp@2x" : "unpackage/res/icons/167x167.png", + "settings" : "unpackage/res/icons/29x29.png", + "settings@2x" : "unpackage/res/icons/58x58.png", + "spotlight" : "unpackage/res/icons/40x40.png", + "spotlight@2x" : "unpackage/res/icons/80x80.png" + }, + "iphone" : { + "app@2x" : "unpackage/res/icons/120x120.png", + "app@3x" : "unpackage/res/icons/180x180.png", + "notification@2x" : "unpackage/res/icons/40x40.png", + "notification@3x" : "unpackage/res/icons/60x60.png", + "settings@2x" : "unpackage/res/icons/58x58.png", + "settings@3x" : "unpackage/res/icons/87x87.png", + "spotlight@2x" : "unpackage/res/icons/80x80.png", + "spotlight@3x" : "unpackage/res/icons/120x120.png" + } + } + } + } + }, + /* 快应用特有相关 */ + "quickapp" : {}, + /* 小程序特有相关 */ + "mp-weixin" : { + "appid" : "", + "setting" : { + "urlCheck" : false + }, + "usingComponents" : true + }, + "mp-alipay" : { + "usingComponents" : true + }, + "mp-baidu" : { + "usingComponents" : true + }, + "mp-toutiao" : { + "usingComponents" : true + }, + "uniStatistics" : { + "enable" : false + }, + "vueVersion" : "2" +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..fdbb02c --- /dev/null +++ b/package.json @@ -0,0 +1,16 @@ +{ + "id": "cus-selects-fan", + "name": "下拉选择,单选,精准搜索,模糊搜索,清除选择", + "displayName": "下拉选择,单选,精准搜索,模糊搜索,清除选择", + "version": "1.0.11", + "description": "自定义下拉选择 可以清除选择,可以模糊搜素,可以精准搜索 (有问题请在评论区留言-我会收到邮件,请各位可以给3-5颗小星星哦,小星星就是我更新优化的动力哦)", + "keywords": [ + "自定义下拉选择" + ], + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ] + } +} \ No newline at end of file diff --git a/pages.json b/pages.json new file mode 100644 index 0000000..983c76f --- /dev/null +++ b/pages.json @@ -0,0 +1,156 @@ +{ + "pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages + { + //登录 + "path": "pages/login", + "style": { + // 自定义导航栏 + "navigationStyle":"custom", + "navigationBarTitleText": "" + } + },{ + //拣货出库 + "path" : "pages/outbound/index", + "style" : + { + "navigationBarTitleText": "", + "navigationStyle":"custom", + "enablePullDownRefresh": false + } + },{ + //采购上架 + "path" : "pages/warehous/cgPutOnsale", + "style" : + { + "navigationBarTitleText": "", + "navigationStyle":"custom", + "enablePullDownRefresh": false + } + },{ + //首页 + "path" : "pages/index", + "style" : + { + "navigationBarTitleText": "", + "navigationStyle":"custom", + "enablePullDownRefresh": false + } + + },{ + //入库 + "path": "pages/warehous/index", + "style" : + { + "navigationBarTitleText": "", + "navigationStyle":"custom", + "enablePullDownRefresh": false + } + },{ + //收货 + "path" : "pages/warehous/takeDelivery", + "style" : + { + "navigationBarTitleText": "", + "navigationStyle":"custom", + "enablePullDownRefresh": false + } + },{ + //非采购上架 + "path" : "pages/warehous/noCgPutOnsale", + "style" : + { + "navigationBarTitleText": "", + "navigationStyle":"custom", + "enablePullDownRefresh": false + } + },{ + //其他内库操作菜单 + "path" : "pages/otherUnderwear/index", + "style" : + { + "navigationBarTitleText": "", + "navigationStyle":"custom", + "enablePullDownRefresh": false + } + },{ + //快速改箱/装箱 + "path" : "pages/otherUnderwear/fastSetbox", + "style" : + { + "navigationBarTitleText": "", + "navigationStyle":"custom", + "enablePullDownRefresh": false + } + },{ + //整箱移货上架 + "path" : "pages/otherUnderwear/moveBoxup", + "style" : + { + "navigationBarTitleText": "", + "navigationStyle":"custom", + "enablePullDownRefresh": false + } + },{ + //整箱移货上架 + "path" : "pages/otherUnderwear/moveBoxDown", + "style" : + { + "navigationBarTitleText": "", + "navigationStyle":"custom", + "enablePullDownRefresh": false + } + },{ + //出库回退上架 + "path" : "pages/otherUnderwear/outStockUp", + "style" : + { + "navigationBarTitleText": "", + "navigationStyle":"custom", + "enablePullDownRefresh": false + } + },{ + //入库回退下架 + "path" : "pages/otherUnderwear/inStockDown", + "style" : + { + "navigationBarTitleText": "", + "navigationStyle":"custom", + "enablePullDownRefresh": false + } + },{ + //库存查询 + "path" : "pages/otherUnderwear/inventory", + "style" : + { + "navigationBarTitleText": "", + "navigationStyle":"custom", + "enablePullDownRefresh": false + } + },{ + //出库箱信息 + "path" : "pages/otherUnderwear/outbound", + "style" : + { + "navigationBarTitleText": "", + "navigationStyle":"custom", + "enablePullDownRefresh": false + } + },{ + //盘点 + "path" : "pages/Inventory/index", + "style" : + { + "navigationBarTitleText": "", + "navigationStyle":"custom", + "enablePullDownRefresh": false + } + } + ], + "globalStyle": { + "navigationBarTextStyle": "black", + "navigationBarTitleText": "uni-app", + "navigationBarBackgroundColor": "#F8F8F8", + "backgroundColor": "#F8F8F8" + }, + "uniIdRouter": {} +} diff --git a/pages/Inventory/index.vue b/pages/Inventory/index.vue new file mode 100644 index 0000000..c1a61dc --- /dev/null +++ b/pages/Inventory/index.vue @@ -0,0 +1,1091 @@ + + + + + + diff --git a/pages/WPS Office.lnk b/pages/WPS Office.lnk new file mode 100644 index 0000000..0850814 Binary files /dev/null and b/pages/WPS Office.lnk differ diff --git a/pages/index.vue b/pages/index.vue new file mode 100644 index 0000000..a2a358c --- /dev/null +++ b/pages/index.vue @@ -0,0 +1,104 @@ + + + + + \ No newline at end of file diff --git a/pages/login.vue b/pages/login.vue new file mode 100644 index 0000000..46e1072 --- /dev/null +++ b/pages/login.vue @@ -0,0 +1,105 @@ + + + + + \ No newline at end of file diff --git a/pages/otherUnderwear/fastSetbox.vue b/pages/otherUnderwear/fastSetbox.vue new file mode 100644 index 0000000..0fd2f21 --- /dev/null +++ b/pages/otherUnderwear/fastSetbox.vue @@ -0,0 +1,1043 @@ + + + + + \ No newline at end of file diff --git a/pages/otherUnderwear/inStockDown.vue b/pages/otherUnderwear/inStockDown.vue new file mode 100644 index 0000000..243b8e9 --- /dev/null +++ b/pages/otherUnderwear/inStockDown.vue @@ -0,0 +1,650 @@ + + + + + + diff --git a/uni_modules/uview-ui/components/u-toast/u-toast.vue b/uni_modules/uview-ui/components/u-toast/u-toast.vue new file mode 100644 index 0000000..f194830 --- /dev/null +++ b/uni_modules/uview-ui/components/u-toast/u-toast.vue @@ -0,0 +1,291 @@ + + + + + diff --git a/uni_modules/uview-ui/components/u-toolbar/props.js b/uni_modules/uview-ui/components/u-toolbar/props.js new file mode 100644 index 0000000..1b72966 --- /dev/null +++ b/uni_modules/uview-ui/components/u-toolbar/props.js @@ -0,0 +1,34 @@ +export default { + props: { + // 是否展示工具条 + show: { + type: Boolean, + default: uni.$u.props.toolbar.show + }, + // 取消按钮的文字 + cancelText: { + type: String, + default: uni.$u.props.toolbar.cancelText + }, + // 确认按钮的文字 + confirmText: { + type: String, + default: uni.$u.props.toolbar.confirmText + }, + // 取消按钮的颜色 + cancelColor: { + type: String, + default: uni.$u.props.toolbar.cancelColor + }, + // 确认按钮的颜色 + confirmColor: { + type: String, + default: uni.$u.props.toolbar.confirmColor + }, + // 标题文字 + title: { + type: String, + default: uni.$u.props.toolbar.title + } + } +} diff --git a/uni_modules/uview-ui/components/u-toolbar/u-toolbar.vue b/uni_modules/uview-ui/components/u-toolbar/u-toolbar.vue new file mode 100644 index 0000000..290b771 --- /dev/null +++ b/uni_modules/uview-ui/components/u-toolbar/u-toolbar.vue @@ -0,0 +1,102 @@ + + + + + diff --git a/uni_modules/uview-ui/components/u-tooltip/clipboard.min.js b/uni_modules/uview-ui/components/u-tooltip/clipboard.min.js new file mode 100644 index 0000000..b7bff12 --- /dev/null +++ b/uni_modules/uview-ui/components/u-tooltip/clipboard.min.js @@ -0,0 +1,58 @@ +/*! + * clipboard.js v1.6.1 + * https://zenorocha.github.io/clipboard.js + * + * Licensed MIT © Zeno Rocha + */ +!(function (e) { if (typeof exports === 'object' && typeof module !== 'undefined')module.exports = e(); else if (typeof define === 'function' && define.amd)define([], e); else { let t; t = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : this, t.Clipboard = e() } }(() => { + let e; let t; let n; return (function e(t, n, o) { function i(a, c) { if (!n[a]) { if (!t[a]) { const l = typeof require === 'function' && require; if (!c && l) return l(a, !0); if (r) return r(a, !0); const u = new Error(`Cannot find module '${a}'`); throw u.code = 'MODULE_NOT_FOUND', u } const s = n[a] = { exports: {} }; t[a][0].call(s.exports, (e) => { const n = t[a][1][e]; return i(n || e) }, s, s.exports, e, t, n, o) } return n[a].exports } for (var r = typeof require === 'function' && require, a = 0; a < o.length; a++)i(o[a]); return i }({ + 1: [function (e, t, n) { function o(e, t) { for (;e && e.nodeType !== i;) { if (e.matches(t)) return e; e = e.parentNode } } var i = 9; if (typeof Element !== 'undefined' && !Element.prototype.matches) { const r = Element.prototype; r.matches = r.matchesSelector || r.mozMatchesSelector || r.msMatchesSelector || r.oMatchesSelector || r.webkitMatchesSelector }t.exports = o }, {}], + 2: [function (e, t, n) { function o(e, t, n, o, r) { const a = i.apply(this, arguments); return e.addEventListener(n, a, r), { destroy() { e.removeEventListener(n, a, r) } } } function i(e, t, n, o) { return function (n) { n.delegateTarget = r(n.target, t), n.delegateTarget && o.call(e, n) } } var r = e('./closest'); t.exports = o }, { './closest': 1 }], + 3: [function (e, t, n) { n.node = function (e) { return void 0 !== e && e instanceof HTMLElement && e.nodeType === 1 }, n.nodeList = function (e) { const t = Object.prototype.toString.call(e); return void 0 !== e && (t === '[object NodeList]' || t === '[object HTMLCollection]') && 'length' in e && (e.length === 0 || n.node(e[0])) }, n.string = function (e) { return typeof e === 'string' || e instanceof String }, n.fn = function (e) { const t = Object.prototype.toString.call(e); return t === '[object Function]' } }, {}], + 4: [function (e, t, n) { function o(e, t, n) { if (!e && !t && !n) throw new Error('Missing required arguments'); if (!c.string(t)) throw new TypeError('Second argument must be a String'); if (!c.fn(n)) throw new TypeError('Third argument must be a Function'); if (c.node(e)) return i(e, t, n); if (c.nodeList(e)) return r(e, t, n); if (c.string(e)) return a(e, t, n); throw new TypeError('First argument must be a String, HTMLElement, HTMLCollection, or NodeList') } function i(e, t, n) { return e.addEventListener(t, n), { destroy() { e.removeEventListener(t, n) } } } function r(e, t, n) { return Array.prototype.forEach.call(e, (e) => { e.addEventListener(t, n) }), { destroy() { Array.prototype.forEach.call(e, (e) => { e.removeEventListener(t, n) }) } } } function a(e, t, n) { return l(document.body, e, t, n) } var c = e('./is'); var l = e('delegate'); t.exports = o }, { './is': 3, delegate: 2 }], + 5: [function (e, t, n) { function o(e) { let t; if (e.nodeName === 'SELECT')e.focus(), t = e.value; else if (e.nodeName === 'INPUT' || e.nodeName === 'TEXTAREA') { const n = e.hasAttribute('readonly'); n || e.setAttribute('readonly', ''), e.select(), e.setSelectionRange(0, e.value.length), n || e.removeAttribute('readonly'), t = e.value } else { e.hasAttribute('contenteditable') && e.focus(); const o = window.getSelection(); const i = document.createRange(); i.selectNodeContents(e), o.removeAllRanges(), o.addRange(i), t = o.toString() } return t }t.exports = o }, {}], + 6: [function (e, t, n) { + function o() {}o.prototype = { + on(e, t, n) { const o = this.e || (this.e = {}); return (o[e] || (o[e] = [])).push({ fn: t, ctx: n }), this }, once(e, t, n) { function o() { i.off(e, o), t.apply(n, arguments) } var i = this; return o._ = t, this.on(e, o, n) }, emit(e) { const t = [].slice.call(arguments, 1); const n = ((this.e || (this.e = {}))[e] || []).slice(); let o = 0; const i = n.length; for (o; o < i; o++)n[o].fn.apply(n[o].ctx, t); return this }, off(e, t) { const n = this.e || (this.e = {}); const o = n[e]; const i = []; if (o && t) for (let r = 0, a = o.length; r < a; r++)o[r].fn !== t && o[r].fn._ !== t && i.push(o[r]); return i.length ? n[e] = i : delete n[e], this } + }, t.exports = o + }, {}], + 7: [function (t, n, o) { + !(function (i, r) { if (typeof e === 'function' && e.amd)e(['module', 'select'], r); else if (typeof o !== 'undefined')r(n, t('select')); else { const a = { exports: {} }; r(a, i.select), i.clipboardAction = a.exports } }(this, (e, t) => { + 'use strict' + + function n(e) { return e && e.__esModule ? e : { default: e } } function o(e, t) { if (!(e instanceof t)) throw new TypeError('Cannot call a class as a function') } const i = n(t); const r = typeof Symbol === 'function' && typeof Symbol.iterator === 'symbol' ? function (e) { return typeof e } : function (e) { return e && typeof Symbol === 'function' && e.constructor === Symbol && e !== Symbol.prototype ? 'symbol' : typeof e }; const a = (function () { function e(e, t) { for (let n = 0; n < t.length; n++) { const o = t[n]; o.enumerable = o.enumerable || !1, o.configurable = !0, 'value' in o && (o.writable = !0), Object.defineProperty(e, o.key, o) } } return function (t, n, o) { return n && e(t.prototype, n), o && e(t, o), t } }()); const c = (function () { + function e(t) { o(this, e), this.resolveOptions(t), this.initSelection() } return a(e, [{ key: 'resolveOptions', value: function e() { const t = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {}; this.action = t.action, this.emitter = t.emitter, this.target = t.target, this.text = t.text, this.trigger = t.trigger, this.selectedText = '' } }, { key: 'initSelection', value: function e() { this.text ? this.selectFake() : this.target && this.selectTarget() } }, { key: 'selectFake', value: function e() { const t = this; const n = document.documentElement.getAttribute('dir') == 'rtl'; this.removeFake(), this.fakeHandlerCallback = function () { return t.removeFake() }, this.fakeHandler = document.body.addEventListener('click', this.fakeHandlerCallback) || !0, this.fakeElem = document.createElement('textarea'), this.fakeElem.style.fontSize = '12pt', this.fakeElem.style.border = '0', this.fakeElem.style.padding = '0', this.fakeElem.style.margin = '0', this.fakeElem.style.position = 'absolute', this.fakeElem.style[n ? 'right' : 'left'] = '-9999px'; const o = window.pageYOffset || document.documentElement.scrollTop; this.fakeElem.style.top = `${o}px`, this.fakeElem.setAttribute('readonly', ''), this.fakeElem.value = this.text, document.body.appendChild(this.fakeElem), this.selectedText = (0, i.default)(this.fakeElem), this.copyText() } }, { key: 'removeFake', value: function e() { this.fakeHandler && (document.body.removeEventListener('click', this.fakeHandlerCallback), this.fakeHandler = null, this.fakeHandlerCallback = null), this.fakeElem && (document.body.removeChild(this.fakeElem), this.fakeElem = null) } }, { key: 'selectTarget', value: function e() { this.selectedText = (0, i.default)(this.target), this.copyText() } }, { key: 'copyText', value: function e() { let t = void 0; try { t = document.execCommand(this.action) } catch (e) { t = !1 } this.handleResult(t) } }, { + key: 'handleResult', + value: function e(t) { + this.emitter.emit(t ? 'success' : 'error', { + action: this.action, text: this.selectedText, trigger: this.trigger, clearSelection: this.clearSelection.bind(this) + }) + } + }, { key: 'clearSelection', value: function e() { this.target && this.target.blur(), window.getSelection().removeAllRanges() } }, { key: 'destroy', value: function e() { this.removeFake() } }, { key: 'action', set: function e() { const t = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : 'copy'; if (this._action = t, this._action !== 'copy' && this._action !== 'cut') throw new Error('Invalid "action" value, use either "copy" or "cut"') }, get: function e() { return this._action } }, { key: 'target', set: function e(t) { if (void 0 !== t) { if (!t || (typeof t === 'undefined' ? 'undefined' : r(t)) !== 'object' || t.nodeType !== 1) throw new Error('Invalid "target" value, use a valid Element'); if (this.action === 'copy' && t.hasAttribute('disabled')) throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute'); if (this.action === 'cut' && (t.hasAttribute('readonly') || t.hasAttribute('disabled'))) throw new Error('Invalid "target" attribute. You can\'t cut text from elements with "readonly" or "disabled" attributes'); this._target = t } }, get: function e() { return this._target } }]), e + }()); e.exports = c + })) + }, { select: 5 }], + 8: [function (t, n, o) { + !(function (i, r) { if (typeof e === 'function' && e.amd)e(['module', './clipboard-action', 'tiny-emitter', 'good-listener'], r); else if (typeof o !== 'undefined')r(n, t('./clipboard-action'), t('tiny-emitter'), t('good-listener')); else { const a = { exports: {} }; r(a, i.clipboardAction, i.tinyEmitter, i.goodListener), i.clipboard = a.exports } }(this, (e, t, n, o) => { + 'use strict' + + function i(e) { return e && e.__esModule ? e : { default: e } } function r(e, t) { if (!(e instanceof t)) throw new TypeError('Cannot call a class as a function') } function a(e, t) { if (!e) throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); return !t || typeof t !== 'object' && typeof t !== 'function' ? e : t } function c(e, t) { + if (typeof t !== 'function' && t !== null) throw new TypeError(`Super expression must either be null or a function, not ${typeof t}`); e.prototype = Object.create(t && t.prototype, { + constructor: { + value: e, enumerable: !1, writable: !0, configurable: !0 + } + }), t && (Object.setPrototypeOf ? Object.setPrototypeOf(e, t) : e.__proto__ = t) + } function l(e, t) { const n = `data-clipboard-${e}`; if (t.hasAttribute(n)) return t.getAttribute(n) } const u = i(t); const s = i(n); const f = i(o); const d = (function () { function e(e, t) { for (let n = 0; n < t.length; n++) { const o = t[n]; o.enumerable = o.enumerable || !1, o.configurable = !0, 'value' in o && (o.writable = !0), Object.defineProperty(e, o.key, o) } } return function (t, n, o) { return n && e(t.prototype, n), o && e(t, o), t } }()); const h = (function (e) { + function t(e, n) { r(this, t); const o = a(this, (t.__proto__ || Object.getPrototypeOf(t)).call(this)); return o.resolveOptions(n), o.listenClick(e), o } return c(t, e), d(t, [{ key: 'resolveOptions', value: function e() { const t = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {}; this.action = typeof t.action === 'function' ? t.action : this.defaultAction, this.target = typeof t.target === 'function' ? t.target : this.defaultTarget, this.text = typeof t.text === 'function' ? t.text : this.defaultText } }, { key: 'listenClick', value: function e(t) { const n = this; this.listener = (0, f.default)(t, 'click', (e) => n.onClick(e)) } }, { + key: 'onClick', + value: function e(t) { + const n = t.delegateTarget || t.currentTarget; this.clipboardAction && (this.clipboardAction = null), this.clipboardAction = new u.default({ + action: this.action(n), target: this.target(n), text: this.text(n), trigger: n, emitter: this + }) + } + }, { key: 'defaultAction', value: function e(t) { return l('action', t) } }, { key: 'defaultTarget', value: function e(t) { const n = l('target', t); if (n) return document.querySelector(n) } }, { key: 'defaultText', value: function e(t) { return l('text', t) } }, { key: 'destroy', value: function e() { this.listener.destroy(), this.clipboardAction && (this.clipboardAction.destroy(), this.clipboardAction = null) } }], [{ key: 'isSupported', value: function e() { const t = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : ['copy', 'cut']; const n = typeof t === 'string' ? [t] : t; let o = !!document.queryCommandSupported; return n.forEach((e) => { o = o && !!document.queryCommandSupported(e) }), o } }]), t + }(s.default)); e.exports = h + })) + }, { './clipboard-action': 7, 'good-listener': 4, 'tiny-emitter': 6 }] + }, {}, [8]))(8) +})) diff --git a/uni_modules/uview-ui/components/u-tooltip/props.js b/uni_modules/uview-ui/components/u-tooltip/props.js new file mode 100644 index 0000000..16aecbc --- /dev/null +++ b/uni_modules/uview-ui/components/u-tooltip/props.js @@ -0,0 +1,59 @@ +export default { + props: { + // 需要显示的提示文字 + text: { + type: [String, Number], + default: uni.$u.props.tooltip.text + }, + // 点击复制按钮时,复制的文本,为空则使用text值 + copyText: { + type: [String, Number], + default: uni.$u.props.tooltip.copyText + }, + // 文本大小 + size: { + type: [String, Number], + default: uni.$u.props.tooltip.size + }, + // 字体颜色 + color: { + type: String, + default: uni.$u.props.tooltip.color + }, + // 弹出提示框时,文本的背景色 + bgColor: { + type: String, + default: uni.$u.props.tooltip.bgColor + }, + // 弹出提示的方向,top-上方,bottom-下方 + direction: { + type: String, + default: uni.$u.props.tooltip.direction + }, + // 弹出提示的z-index,nvue无效 + zIndex: { + type: [String, Number], + default: uni.$u.props.tooltip.zIndex + }, + // 是否显示复制按钮 + showCopy: { + type: Boolean, + default: uni.$u.props.tooltip.showCopy + }, + // 扩展的按钮组 + buttons: { + type: Array, + default: uni.$u.props.tooltip.buttons + }, + // 是否显示透明遮罩以防止触摸穿透 + overlay: { + type: Boolean, + default: uni.$u.props.tooltip.overlay + }, + // 是否显示复制成功或者失败的toast + showToast: { + type: Boolean, + default: uni.$u.props.tooltip.showToast + } + } +} diff --git a/uni_modules/uview-ui/components/u-tooltip/u-tooltip.vue b/uni_modules/uview-ui/components/u-tooltip/u-tooltip.vue new file mode 100644 index 0000000..4bd8fc9 --- /dev/null +++ b/uni_modules/uview-ui/components/u-tooltip/u-tooltip.vue @@ -0,0 +1,365 @@ + + + + + diff --git a/uni_modules/uview-ui/components/u-tr/props.js b/uni_modules/uview-ui/components/u-tr/props.js new file mode 100644 index 0000000..7c11331 --- /dev/null +++ b/uni_modules/uview-ui/components/u-tr/props.js @@ -0,0 +1,5 @@ +export default { + props: { + + } +} diff --git a/uni_modules/uview-ui/components/u-tr/u-tr.vue b/uni_modules/uview-ui/components/u-tr/u-tr.vue new file mode 100644 index 0000000..dbbca08 --- /dev/null +++ b/uni_modules/uview-ui/components/u-tr/u-tr.vue @@ -0,0 +1,31 @@ + + + + + diff --git a/uni_modules/uview-ui/components/u-transition/nvue.ani-map.js b/uni_modules/uview-ui/components/u-transition/nvue.ani-map.js new file mode 100644 index 0000000..b86b962 --- /dev/null +++ b/uni_modules/uview-ui/components/u-transition/nvue.ani-map.js @@ -0,0 +1,68 @@ +export default { + fade: { + enter: { opacity: 0 }, + 'enter-to': { opacity: 1 }, + leave: { opacity: 1 }, + 'leave-to': { opacity: 0 } + }, + 'fade-up': { + enter: { opacity: 0, transform: 'translateY(100%)' }, + 'enter-to': { opacity: 1, transform: 'translateY(0)' }, + leave: { opacity: 1, transform: 'translateY(0)' }, + 'leave-to': { opacity: 0, transform: 'translateY(100%)' } + }, + 'fade-down': { + enter: { opacity: 0, transform: 'translateY(-100%)' }, + 'enter-to': { opacity: 1, transform: 'translateY(0)' }, + leave: { opacity: 1, transform: 'translateY(0)' }, + 'leave-to': { opacity: 0, transform: 'translateY(-100%)' } + }, + 'fade-left': { + enter: { opacity: 0, transform: 'translateX(-100%)' }, + 'enter-to': { opacity: 1, transform: 'translateY(0)' }, + leave: { opacity: 1, transform: 'translateY(0)' }, + 'leave-to': { opacity: 0, transform: 'translateX(-100%)' } + }, + 'fade-right': { + enter: { opacity: 0, transform: 'translateX(100%)' }, + 'enter-to': { opacity: 1, transform: 'translateY(0)' }, + leave: { opacity: 1, transform: 'translateY(0)' }, + 'leave-to': { opacity: 0, transform: 'translateX(100%)' } + }, + 'slide-up': { + enter: { transform: 'translateY(100%)' }, + 'enter-to': { transform: 'translateY(0)' }, + leave: { transform: 'translateY(0)' }, + 'leave-to': { transform: 'translateY(100%)' } + }, + 'slide-down': { + enter: { transform: 'translateY(-100%)' }, + 'enter-to': { transform: 'translateY(0)' }, + leave: { transform: 'translateY(0)' }, + 'leave-to': { transform: 'translateY(-100%)' } + }, + 'slide-left': { + enter: { transform: 'translateX(-100%)' }, + 'enter-to': { transform: 'translateY(0)' }, + leave: { transform: 'translateY(0)' }, + 'leave-to': { transform: 'translateX(-100%)' } + }, + 'slide-right': { + enter: { transform: 'translateX(100%)' }, + 'enter-to': { transform: 'translateY(0)' }, + leave: { transform: 'translateY(0)' }, + 'leave-to': { transform: 'translateX(100%)' } + }, + zoom: { + enter: { transform: 'scale(0.95)' }, + 'enter-to': { transform: 'scale(1)' }, + leave: { transform: 'scale(1)' }, + 'leave-to': { transform: 'scale(0.95)' } + }, + 'fade-zoom': { + enter: { opacity: 0, transform: 'scale(0.95)' }, + 'enter-to': { opacity: 1, transform: 'scale(1)' }, + leave: { opacity: 1, transform: 'scale(1)' }, + 'leave-to': { opacity: 0, transform: 'scale(0.95)' } + } +} diff --git a/uni_modules/uview-ui/components/u-transition/props.js b/uni_modules/uview-ui/components/u-transition/props.js new file mode 100644 index 0000000..f7b1c22 --- /dev/null +++ b/uni_modules/uview-ui/components/u-transition/props.js @@ -0,0 +1,24 @@ +export default { + props: { + // 是否展示组件 + show: { + type: Boolean, + default: uni.$u.props.transition.show + }, + // 使用的动画模式 + mode: { + type: String, + default: uni.$u.props.transition.mode + }, + // 动画的执行时间,单位ms + duration: { + type: [String, Number], + default: uni.$u.props.transition.duration + }, + // 使用的动画过渡函数 + timingFunction: { + type: String, + default: uni.$u.props.transition.timingFunction + } + } +} diff --git a/uni_modules/uview-ui/components/u-transition/transition.js b/uni_modules/uview-ui/components/u-transition/transition.js new file mode 100644 index 0000000..92e5681 --- /dev/null +++ b/uni_modules/uview-ui/components/u-transition/transition.js @@ -0,0 +1,157 @@ +// 定义一个一定时间后自动成功的promise,让调用nextTick方法处,进入下一个then方法 +const nextTick = () => new Promise(resolve => setTimeout(resolve, 1000 / 50)) +// nvue动画模块实现细节抽离在外部文件 +import animationMap from './nvue.ani-map.js' + +// #ifndef APP-NVUE +// 定义类名,通过给元素动态切换类名,赋予元素一定的css动画样式 +const getClassNames = (name) => ({ + enter: `u-${name}-enter u-${name}-enter-active`, + 'enter-to': `u-${name}-enter-to u-${name}-enter-active`, + leave: `u-${name}-leave u-${name}-leave-active`, + 'leave-to': `u-${name}-leave-to u-${name}-leave-active` +}) +// #endif + +// #ifdef APP-NVUE +// 引入nvue(weex)的animation动画模块,文档见: +// https://weex.apache.org/zh/docs/modules/animation.html#transition +const animation = uni.requireNativePlugin('animation') +const getStyle = (name) => animationMap[name] +// #endif + +export default { + methods: { + // 组件被点击发出事件 + clickHandler() { + this.$emit('click') + }, + // #ifndef APP-NVUE + // vue版本的组件进场处理 + vueEnter() { + // 动画进入时的类名 + const classNames = getClassNames(this.mode) + // 定义状态和发出动画进入前事件 + this.status = 'enter' + this.$emit('beforeEnter') + this.inited = true + this.display = true + this.classes = classNames.enter + this.$nextTick(async () => { + // #ifdef H5 + await uni.$u.sleep(20) + // #endif + // 标识动画尚未结束 + this.$emit('enter') + this.transitionEnded = false + // 组件动画进入后触发的事件 + this.$emit('afterEnter') + // 赋予组件enter-to类名 + this.classes = classNames['enter-to'] + }) + }, + // 动画离场处理 + vueLeave() { + // 如果不是展示状态,无需执行逻辑 + if (!this.display) return + const classNames = getClassNames(this.mode) + // 标记离开状态和发出事件 + this.status = 'leave' + this.$emit('beforeLeave') + // 获得类名 + this.classes = classNames.leave + + this.$nextTick(() => { + // 动画正在离场的状态 + this.transitionEnded = false + this.$emit('leave') + // 组件执行动画,到了执行的执行时间后,执行一些额外处理 + setTimeout(this.onTransitionEnd, this.duration) + this.classes = classNames['leave-to'] + }) + }, + // #endif + // #ifdef APP-NVUE + // nvue版本动画进场 + nvueEnter() { + // 获得样式的名称 + const currentStyle = getStyle(this.mode) + // 组件动画状态和发出事件 + this.status = 'enter' + this.$emit('beforeEnter') + // 展示生成组件元素 + this.inited = true + this.display = true + // 在nvue安卓上,由于渲染速度慢,在弹窗,键盘,日历等组件中,渲染其中的内容需要时间 + // 导致出现弹窗卡顿,这里让其一开始为透明状态,等一定时间渲染完成后,再让其隐藏起来,再让其按正常逻辑出现 + this.viewStyle = { + opacity: 0 + } + // 等待弹窗内容渲染完成 + this.$nextTick(() => { + // 合并样式 + this.viewStyle = currentStyle.enter + Promise.resolve() + .then(nextTick) + .then(() => { + // 组件开始进入前的事件 + this.$emit('enter') + // nvue的transition动画模块需要通过ref调用组件,注意此处的ref不同于vue的this.$refs['u-transition']用法 + animation.transition(this.$refs['u-transition'].ref, { + styles: currentStyle['enter-to'], + duration: this.duration, + timingFunction: this.timingFunction, + needLayout: false, + delay: 0 + }, () => { + // 动画执行完毕,发出事件 + this.$emit('afterEnter') + }) + }) + .catch(() => {}) + }) + }, + nvueLeave() { + if (!this.display) { + return + } + const currentStyle = getStyle(this.mode) + // 定义状态和事件 + this.status = 'leave' + this.$emit('beforeLeave') + // 合并样式 + this.viewStyle = currentStyle.leave + // 放到promise中处理执行过程 + Promise.resolve() + .then(nextTick) // 等待几十ms + .then(() => { + this.transitionEnded = false + // 动画正在离场的状态 + this.$emit('leave') + animation.transition(this.$refs['u-transition'].ref, { + styles: currentStyle['leave-to'], + duration: this.duration, + timingFunction: this.timingFunction, + needLayout: false, + delay: 0 + }, () => { + this.onTransitionEnd() + }) + }) + .catch(() => {}) + }, + // #endif + // 完成过渡后触发 + onTransitionEnd() { + // 如果已经是结束的状态,无需再处理 + if (this.transitionEnded) return + this.transitionEnded = true + // 发出组件动画执行后的事件 + this.$emit(this.status === 'leave' ? 'afterLeave' : 'afterEnter') + if (!this.show && this.display) { + this.display = false + this.inited = false + } + } + } +} diff --git a/uni_modules/uview-ui/components/u-transition/u-transition.vue b/uni_modules/uview-ui/components/u-transition/u-transition.vue new file mode 100644 index 0000000..22831dc --- /dev/null +++ b/uni_modules/uview-ui/components/u-transition/u-transition.vue @@ -0,0 +1,92 @@ + + + + + diff --git a/uni_modules/uview-ui/components/u-transition/vue.ani-style.scss b/uni_modules/uview-ui/components/u-transition/vue.ani-style.scss new file mode 100644 index 0000000..a31d88b --- /dev/null +++ b/uni_modules/uview-ui/components/u-transition/vue.ani-style.scss @@ -0,0 +1,113 @@ +/** + * vue版本动画内置的动画模式有如下: + * fade:淡入 + * zoom:缩放 + * fade-zoom:缩放淡入 + * fade-up:上滑淡入 + * fade-down:下滑淡入 + * fade-left:左滑淡入 + * fade-right:右滑淡入 + * slide-up:上滑进入 + * slide-down:下滑进入 + * slide-left:左滑进入 + * slide-right:右滑进入 + */ + +$u-zoom-scale: scale(0.95); + +.u-fade-enter-active, +.u-fade-leave-active { + transition-property: opacity; +} + +.u-fade-enter, +.u-fade-leave-to { + opacity: 0 +} + +.u-fade-zoom-enter, +.u-fade-zoom-leave-to { + transform: $u-zoom-scale; + opacity: 0; +} + +.u-fade-zoom-enter-active, +.u-fade-zoom-leave-active { + transition-property: transform, opacity; +} + +.u-fade-down-enter-active, +.u-fade-down-leave-active, +.u-fade-left-enter-active, +.u-fade-left-leave-active, +.u-fade-right-enter-active, +.u-fade-right-leave-active, +.u-fade-up-enter-active, +.u-fade-up-leave-active { + transition-property: opacity, transform; +} + +.u-fade-up-enter, +.u-fade-up-leave-to { + transform: translate3d(0, 100%, 0); + opacity: 0 +} + +.u-fade-down-enter, +.u-fade-down-leave-to { + transform: translate3d(0, -100%, 0); + opacity: 0 +} + +.u-fade-left-enter, +.u-fade-left-leave-to { + transform: translate3d(-100%, 0, 0); + opacity: 0 +} + +.u-fade-right-enter, +.u-fade-right-leave-to { + transform: translate3d(100%, 0, 0); + opacity: 0 +} + +.u-slide-down-enter-active, +.u-slide-down-leave-active, +.u-slide-left-enter-active, +.u-slide-left-leave-active, +.u-slide-right-enter-active, +.u-slide-right-leave-active, +.u-slide-up-enter-active, +.u-slide-up-leave-active { + transition-property: transform; +} + +.u-slide-up-enter, +.u-slide-up-leave-to { + transform: translate3d(0, 100%, 0) +} + +.u-slide-down-enter, +.u-slide-down-leave-to { + transform: translate3d(0, -100%, 0) +} + +.u-slide-left-enter, +.u-slide-left-leave-to { + transform: translate3d(-100%, 0, 0) +} + +.u-slide-right-enter, +.u-slide-right-leave-to { + transform: translate3d(100%, 0, 0) +} + +.u-zoom-enter-active, +.u-zoom-leave-active { + transition-property: transform +} + +.u-zoom-enter, +.u-zoom-leave-to { + transform: $u-zoom-scale +} diff --git a/uni_modules/uview-ui/components/u-upload/mixin.js b/uni_modules/uview-ui/components/u-upload/mixin.js new file mode 100644 index 0000000..410c775 --- /dev/null +++ b/uni_modules/uview-ui/components/u-upload/mixin.js @@ -0,0 +1,21 @@ +export default { + watch: { + // 监听accept的变化,判断是否符合个平台要求 + // 只有微信小程序才支持选择媒体,文件类型,所以这里做一个判断提示 + accept: { + immediate: true, + handler(val) { + // #ifndef MP-WEIXIN + if (val === 'all' || val === 'media') { + uni.$u.error('只有微信小程序才支持把accept配置为all、media之一') + } + // #endif + // #ifndef H5 || MP-WEIXIN + if (val === 'file') { + uni.$u.error('只有微信小程序和H5(HX2.9.9)才支持把accept配置为file') + } + // #endif + } + } + } +} diff --git a/uni_modules/uview-ui/components/u-upload/props.js b/uni_modules/uview-ui/components/u-upload/props.js new file mode 100644 index 0000000..b106ae7 --- /dev/null +++ b/uni_modules/uview-ui/components/u-upload/props.js @@ -0,0 +1,124 @@ +export default { + props: { + // 接受的文件类型, 可选值为all media image file video + accept: { + type: String, + default: uni.$u.props.upload.accept + }, + // 图片或视频拾取模式,当accept为image类型时设置capture可选额外camera可以直接调起摄像头 + capture: { + type: [String, Array], + default: uni.$u.props.upload.capture + }, + // 当accept为video时生效,是否压缩视频,默认为true + compressed: { + type: Boolean, + default: uni.$u.props.upload.compressed + }, + // 当accept为video时生效,可选值为back或front + camera: { + type: String, + default: uni.$u.props.upload.camera + }, + // 当accept为video时生效,拍摄视频最长拍摄时间,单位秒 + maxDuration: { + type: Number, + default: uni.$u.props.upload.maxDuration + }, + // 上传区域的图标,只能内置图标 + uploadIcon: { + type: String, + default: uni.$u.props.upload.uploadIcon + }, + // 上传区域的图标的颜色,默认 + uploadIconColor: { + type: String, + default: uni.$u.props.upload.uploadIconColor + }, + // 是否开启文件读取前事件 + useBeforeRead: { + type: Boolean, + default: uni.$u.props.upload.useBeforeRead + }, + // 读取后的处理函数 + afterRead: { + type: Function, + default: null + }, + // 读取前的处理函数 + beforeRead: { + type: Function, + default: null + }, + // 是否显示组件自带的图片预览功能 + previewFullImage: { + type: Boolean, + default: uni.$u.props.upload.previewFullImage + }, + // 最大上传数量 + maxCount: { + type: [String, Number], + default: uni.$u.props.upload.maxCount + }, + // 是否启用 + disabled: { + type: Boolean, + default: uni.$u.props.upload.disabled + }, + // 预览上传的图片时的裁剪模式,和image组件mode属性一致 + imageMode: { + type: String, + default: uni.$u.props.upload.imageMode + }, + // 标识符,可以在回调函数的第二项参数中获取 + name: { + type: String, + default: uni.$u.props.upload.name + }, + // 所选的图片的尺寸, 可选值为original compressed + sizeType: { + type: Array, + default: uni.$u.props.upload.sizeType + }, + // 是否开启图片多选,部分安卓机型不支持 + multiple: { + type: Boolean, + default: uni.$u.props.upload.multiple + }, + // 是否展示删除按钮 + deletable: { + type: Boolean, + default: uni.$u.props.upload.deletable + }, + // 文件大小限制,单位为byte + maxSize: { + type: [String, Number], + default: uni.$u.props.upload.maxSize + }, + // 显示已上传的文件列表 + fileList: { + type: Array, + default: uni.$u.props.upload.fileList + }, + // 上传区域的提示文字 + uploadText: { + type: String, + default: uni.$u.props.upload.uploadText + }, + // 内部预览图片区域和选择图片按钮的区域宽度 + width: { + type: [String, Number], + default: uni.$u.props.upload.width + }, + // 内部预览图片区域和选择图片按钮的区域高度 + height: { + type: [String, Number], + default: uni.$u.props.upload.height + }, + // 是否在上传完成后展示预览图 + previewImage: { + type: Boolean, + default: uni.$u.props.upload.previewImage + } + } +} diff --git a/uni_modules/uview-ui/components/u-upload/u-upload.vue b/uni_modules/uview-ui/components/u-upload/u-upload.vue new file mode 100644 index 0000000..1dac8a7 --- /dev/null +++ b/uni_modules/uview-ui/components/u-upload/u-upload.vue @@ -0,0 +1,558 @@ + + + + + diff --git a/uni_modules/uview-ui/components/u-upload/utils.js b/uni_modules/uview-ui/components/u-upload/utils.js new file mode 100644 index 0000000..88cb602 --- /dev/null +++ b/uni_modules/uview-ui/components/u-upload/utils.js @@ -0,0 +1,151 @@ +function pickExclude(obj, keys) { + // 某些情况下,type可能会为 + if (!['[object Object]', '[object File]'].includes(Object.prototype.toString.call(obj))) { + return {} + } + return Object.keys(obj).reduce((prev, key) => { + if (!keys.includes(key)) { + prev[key] = obj[key] + } + return prev + }, {}) +} + +function formatImage(res) { + return res.tempFiles.map((item) => ({ + ...pickExclude(item, ['path']), + type: 'image', + url: item.path, + thumb: item.path, + size: item.size, + // #ifdef H5 + name: item.name + // #endif + })) +} + +function formatVideo(res) { + return [ + { + ...pickExclude(res, ['tempFilePath', 'thumbTempFilePath', 'errMsg']), + type: 'video', + url: res.tempFilePath, + thumb: res.thumbTempFilePath, + size: res.size, + // #ifdef H5 + name: res.name + // #endif + } + ] +} + +function formatMedia(res) { + return res.tempFiles.map((item) => ({ + ...pickExclude(item, ['fileType', 'thumbTempFilePath', 'tempFilePath']), + type: res.type, + url: item.tempFilePath, + thumb: res.type === 'video' ? item.thumbTempFilePath : item.tempFilePath, + size: item.size + })) +} + +function formatFile(res) { + return res.tempFiles.map((item) => ({ + ...pickExclude(item, ['path']), + url: item.path, + size:item.size, + // #ifdef H5 + name: item.name, + type: item.type + // #endif + })) +} +export function chooseFile({ + accept, + multiple, + capture, + compressed, + maxDuration, + sizeType, + camera, + maxCount +}) { + return new Promise((resolve, reject) => { + switch (accept) { + case 'image': + uni.chooseImage({ + count: multiple ? Math.min(maxCount, 9) : 1, + sourceType: capture, + sizeType, + success: (res) => resolve(formatImage(res)), + fail: reject + }) + break + // #ifdef MP-WEIXIN + // 只有微信小程序才支持chooseMedia接口 + case 'media': + wx.chooseMedia({ + count: multiple ? Math.min(maxCount, 9) : 1, + sourceType: capture, + maxDuration, + sizeType, + camera, + success: (res) => resolve(formatMedia(res)), + fail: reject + }) + break + // #endif + case 'video': + uni.chooseVideo({ + sourceType: capture, + compressed, + maxDuration, + camera, + success: (res) => resolve(formatVideo(res)), + fail: reject + }) + break + // #ifdef MP-WEIXIN || H5 + // 只有微信小程序才支持chooseMessageFile接口 + case 'file': + // #ifdef MP-WEIXIN + wx.chooseMessageFile({ + count: multiple ? maxCount : 1, + type: accept, + success: (res) => resolve(formatFile(res)), + fail: reject + }) + // #endif + // #ifdef H5 + // 需要hx2.9.9以上才支持uni.chooseFile + uni.chooseFile({ + count: multiple ? maxCount : 1, + type: accept, + success: (res) => resolve(formatFile(res)), + fail: reject + }) + // #endif + break + // #endif + default: + // 此为保底选项,在accept不为上面任意一项的时候选取全部文件 + // #ifdef MP-WEIXIN + wx.chooseMessageFile({ + count: multiple ? maxCount : 1, + type: 'all', + success: (res) => resolve(formatFile(res)), + fail: reject + }) + // #endif + // #ifdef H5 + // 需要hx2.9.9以上才支持uni.chooseFile + uni.chooseFile({ + count: multiple ? maxCount : 1, + type: 'all', + success: (res) => resolve(formatFile(res)), + fail: reject + }) + // #endif + } + }) +} diff --git a/uni_modules/uview-ui/components/uview-ui/uview-ui.vue b/uni_modules/uview-ui/components/uview-ui/uview-ui.vue new file mode 100644 index 0000000..bcd3662 --- /dev/null +++ b/uni_modules/uview-ui/components/uview-ui/uview-ui.vue @@ -0,0 +1,15 @@ + + + + + + + diff --git a/uni_modules/uview-ui/index.js b/uni_modules/uview-ui/index.js new file mode 100644 index 0000000..651c090 --- /dev/null +++ b/uni_modules/uview-ui/index.js @@ -0,0 +1,79 @@ +// 看到此报错,是因为没有配置vue.config.js的【transpileDependencies】,详见:https://www.uviewui.com/components/npmSetting.html#_5-cli模式额外配置 +const pleaseSetTranspileDependencies = {}, babelTest = pleaseSetTranspileDependencies?.test + + + +// 引入全局mixin +import mixin from './libs/mixin/mixin.js' +// 小程序特有的mixin +import mpMixin from './libs/mixin/mpMixin.js' +// 全局挂载引入http相关请求拦截插件 +import Request from './libs/luch-request' + +// 路由封装 +import route from './libs/util/route.js' +// 颜色渐变相关,colorGradient-颜色渐变,hexToRgb-十六进制颜色转rgb颜色,rgbToHex-rgb转十六进制 +import colorGradient from './libs/function/colorGradient.js' + +// 规则检验 +import test from './libs/function/test.js' +// 防抖方法 +import debounce from './libs/function/debounce.js' +// 节流方法 +import throttle from './libs/function/throttle.js' +// 公共文件写入的方法 +import index from './libs/function/index.js' + +// 配置信息 +import config from './libs/config/config.js' +// props配置信息 +import props from './libs/config/props.js' +// 各个需要fixed的地方的z-index配置文件 +import zIndex from './libs/config/zIndex.js' +// 关于颜色的配置,特殊场景使用 +import color from './libs/config/color.js' +// 平台 +import platform from './libs/function/platform' + +const $u = { + route, + date: index.timeFormat, // 另名date + colorGradient: colorGradient.colorGradient, + hexToRgb: colorGradient.hexToRgb, + rgbToHex: colorGradient.rgbToHex, + colorToRgba: colorGradient.colorToRgba, + test, + type: ['primary', 'success', 'error', 'warning', 'info'], + http: new Request(), + config, // uView配置信息相关,比如版本号 + zIndex, + debounce, + throttle, + mixin, + mpMixin, + props, + ...index, + color, + platform +} + +// $u挂载到uni对象上 +uni.$u = $u + +const install = (Vue) => { + // 时间格式化,同时两个名称,date和timeFormat + Vue.filter('timeFormat', (timestamp, format) => uni.$u.timeFormat(timestamp, format)) + Vue.filter('date', (timestamp, format) => uni.$u.timeFormat(timestamp, format)) + // 将多久以前的方法,注入到全局过滤器 + Vue.filter('timeFrom', (timestamp, format) => uni.$u.timeFrom(timestamp, format)) + // 同时挂载到uni和Vue.prototype中 + // #ifndef APP-NVUE + // 只有vue,挂载到Vue.prototype才有意义,因为nvue中全局Vue.prototype和Vue.mixin是无效的 + Vue.prototype.$u = $u + Vue.mixin(mixin) + // #endif +} + +export default { + install +} diff --git a/uni_modules/uview-ui/index.scss b/uni_modules/uview-ui/index.scss new file mode 100644 index 0000000..8fcfa83 --- /dev/null +++ b/uni_modules/uview-ui/index.scss @@ -0,0 +1,23 @@ +// 引入公共基础类 +@import "./libs/css/common.scss"; +@import "./libs/css/color.scss"; + +// 非nvue的样式 +/* #ifndef APP-NVUE */ +@import "./libs/css/vue.scss"; +/* #endif */ + +// nvue的特有样式 +/* #ifdef APP-NVUE */ +@import "./libs/css/nvue.scss"; +/* #endif */ + +// 小程序特有的样式 +/* #ifdef MP */ +@import "./libs/css/mp.scss"; +/* #endif */ + +// H5特有的样式 +/* #ifdef H5 */ +@import "./libs/css/h5.scss"; +/* #endif */ \ No newline at end of file diff --git a/uni_modules/uview-ui/libs/config/color.js b/uni_modules/uview-ui/libs/config/color.js new file mode 100644 index 0000000..56b4187 --- /dev/null +++ b/uni_modules/uview-ui/libs/config/color.js @@ -0,0 +1,17 @@ +// 为了让用户能够自定义主题,会逐步弃用此文件,各颜色通过css提供 +// 为了给某些特殊场景使用和向后兼容,无需删除此文件(2020-06-20) +const color = { + primary: '#3c9cff', + info: '#909399', + default: '#909399', + warning: '#f9ae3d', + error: '#f56c6c', + success: '#5ac725', + mainColor: '#303133', + contentColor: '#606266', + tipsColor: '#909399', + lightColor: '#c0c4cc', + borderColor: '#e4e7ed' +} + +export default color diff --git a/uni_modules/uview-ui/libs/config/config.js b/uni_modules/uview-ui/libs/config/config.js new file mode 100644 index 0000000..a3a784e --- /dev/null +++ b/uni_modules/uview-ui/libs/config/config.js @@ -0,0 +1,34 @@ +// 此版本发布于2023-03-27 +const version = '2.0.36' + +// 开发环境才提示,生产环境不会提示 +if (process.env.NODE_ENV === 'development') { + console.log(`\n %c uView V${version} %c https://uviewui.com/ \n\n`, 'color: #ffffff; background: #3c9cff; padding:5px 0; border-radius: 5px;'); +} + +export default { + v: version, + version, + // 主题名称 + type: [ + 'primary', + 'success', + 'info', + 'error', + 'warning' + ], + // 颜色部分,本来可以通过scss的:export导出供js使用,但是奈何nvue不支持 + color: { + 'u-primary': '#2979ff', + 'u-warning': '#ff9900', + 'u-success': '#19be6b', + 'u-error': '#fa3534', + 'u-info': '#909399', + 'u-main-color': '#303133', + 'u-content-color': '#606266', + 'u-tips-color': '#909399', + 'u-light-color': '#c0c4cc' + }, + // 默认单位,可以通过配置为rpx,那么在用于传入组件大小参数为数值时,就默认为rpx + unit: 'px' +} diff --git a/uni_modules/uview-ui/libs/config/props.js b/uni_modules/uview-ui/libs/config/props.js new file mode 100644 index 0000000..6930d48 --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props.js @@ -0,0 +1,190 @@ +/** + * 此文件的作用为统一配置所有组件的props参数 + * 借此用户可以全局覆盖组件的props默认值 + * 无需在每个引入组件的页面中都配置一次 + */ +import config from './config' + +import actionSheet from './props/actionSheet.js' +import album from './props/album.js' +import alert from './props/alert.js' +import avatar from './props/avatar' +import avatarGroup from './props/avatarGroup' +import backtop from './props/backtop' +import badge from './props/badge' +import button from './props/button' +import calendar from './props/calendar' +import carKeyboard from './props/carKeyboard' +import cell from './props/cell' +import cellGroup from './props/cellGroup' +import checkbox from './props/checkbox' +import checkboxGroup from './props/checkboxGroup' +import circleProgress from './props/circleProgress' +import code from './props/code' +import codeInput from './props/codeInput' +import col from './props/col' +import collapse from './props/collapse' +import collapseItem from './props/collapseItem' +import columnNotice from './props/columnNotice' +import countDown from './props/countDown' +import countTo from './props/countTo' +import datetimePicker from './props/datetimePicker' +import divider from './props/divider' +import empty from './props/empty' +import form from './props/form' +import formItem from './props/formItem' +import gap from './props/gap' +import grid from './props/grid' +import gridItem from './props/gridItem' +import icon from './props/icon' +import image from './props/image' +import indexAnchor from './props/indexAnchor' +import indexList from './props/indexList' +import input from './props/input' +import keyboard from './props/keyboard' +import line from './props/line' +import lineProgress from './props/lineProgress' +import link from './props/link' +import list from './props/list' +import listItem from './props/listItem' +import loadingIcon from './props/loadingIcon' +import loadingPage from './props/loadingPage' +import loadmore from './props/loadmore' +import modal from './props/modal' +import navbar from './props/navbar' +import noNetwork from './props/noNetwork' +import noticeBar from './props/noticeBar' +import notify from './props/notify' +import numberBox from './props/numberBox' +import numberKeyboard from './props/numberKeyboard' +import overlay from './props/overlay' +import parse from './props/parse' +import picker from './props/picker' +import popup from './props/popup' +import radio from './props/radio' +import radioGroup from './props/radioGroup' +import rate from './props/rate' +import readMore from './props/readMore' +import row from './props/row' +import rowNotice from './props/rowNotice' +import scrollList from './props/scrollList' +import search from './props/search' +import section from './props/section' +import skeleton from './props/skeleton' +import slider from './props/slider' +import statusBar from './props/statusBar' +import steps from './props/steps' +import stepsItem from './props/stepsItem' +import sticky from './props/sticky' +import subsection from './props/subsection' +import swipeAction from './props/swipeAction' +import swipeActionItem from './props/swipeActionItem' +import swiper from './props/swiper' +import swipterIndicator from './props/swipterIndicator' +import _switch from './props/switch' +import tabbar from './props/tabbar' +import tabbarItem from './props/tabbarItem' +import tabs from './props/tabs' +import tag from './props/tag' +import text from './props/text' +import textarea from './props/textarea' +import toast from './props/toast' +import toolbar from './props/toolbar' +import tooltip from './props/tooltip' +import transition from './props/transition' +import upload from './props/upload' + +const { + color +} = config + +export default { + ...actionSheet, + ...album, + ...alert, + ...avatar, + ...avatarGroup, + ...backtop, + ...badge, + ...button, + ...calendar, + ...carKeyboard, + ...cell, + ...cellGroup, + ...checkbox, + ...checkboxGroup, + ...circleProgress, + ...code, + ...codeInput, + ...col, + ...collapse, + ...collapseItem, + ...columnNotice, + ...countDown, + ...countTo, + ...datetimePicker, + ...divider, + ...empty, + ...form, + ...formItem, + ...gap, + ...grid, + ...gridItem, + ...icon, + ...image, + ...indexAnchor, + ...indexList, + ...input, + ...keyboard, + ...line, + ...lineProgress, + ...link, + ...list, + ...listItem, + ...loadingIcon, + ...loadingPage, + ...loadmore, + ...modal, + ...navbar, + ...noNetwork, + ...noticeBar, + ...notify, + ...numberBox, + ...numberKeyboard, + ...overlay, + ...parse, + ...picker, + ...popup, + ...radio, + ...radioGroup, + ...rate, + ...readMore, + ...row, + ...rowNotice, + ...scrollList, + ...search, + ...section, + ...skeleton, + ...slider, + ...statusBar, + ...steps, + ...stepsItem, + ...sticky, + ...subsection, + ...swipeAction, + ...swipeActionItem, + ...swiper, + ...swipterIndicator, + ..._switch, + ...tabbar, + ...tabbarItem, + ...tabs, + ...tag, + ...text, + ...textarea, + ...toast, + ...toolbar, + ...tooltip, + ...transition, + ...upload +} diff --git a/uni_modules/uview-ui/libs/config/props/actionSheet.js b/uni_modules/uview-ui/libs/config/props/actionSheet.js new file mode 100644 index 0000000..d8061a7 --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/actionSheet.js @@ -0,0 +1,25 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 16:44:35 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/actionSheet.js + */ +export default { + // action-sheet组件 + actionSheet: { + show: false, + title: '', + description: '', + actions: () => [], + index: '', + cancelText: '', + closeOnClickAction: true, + safeAreaInsetBottom: true, + openType: '', + closeOnClickOverlay: true, + round: 0 + } +} diff --git a/uni_modules/uview-ui/libs/config/props/album.js b/uni_modules/uview-ui/libs/config/props/album.js new file mode 100644 index 0000000..8877326 --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/album.js @@ -0,0 +1,25 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 16:47:24 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/album.js + */ +export default { + // album 组件 + album: { + urls: () => [], + keyName: '', + singleSize: 180, + multipleSize: 70, + space: 6, + singleMode: 'scaleToFill', + multipleMode: 'aspectFill', + maxCount: 9, + previewFullImage: true, + rowCount: 3, + showMore: true + } +} diff --git a/uni_modules/uview-ui/libs/config/props/alert.js b/uni_modules/uview-ui/libs/config/props/alert.js new file mode 100644 index 0000000..8f8182c --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/alert.js @@ -0,0 +1,22 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 16:48:53 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/alert.js + */ +export default { + // alert警告组件 + alert: { + title: '', + type: 'warning', + description: '', + closable: false, + showIcon: false, + effect: 'light', + center: false, + fontSize: 14 + } +} diff --git a/uni_modules/uview-ui/libs/config/props/avatar.js b/uni_modules/uview-ui/libs/config/props/avatar.js new file mode 100644 index 0000000..c097d4e --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/avatar.js @@ -0,0 +1,28 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 16:49:22 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/avatar.js + */ +export default { + // avatar 组件 + avatar: { + src: '', + shape: 'circle', + size: 40, + mode: 'scaleToFill', + text: '', + bgColor: '#c0c4cc', + color: '#ffffff', + fontSize: 18, + icon: '', + mpAvatar: false, + randomBgColor: false, + defaultUrl: '', + colorIndex: '', + name: '' + } +} diff --git a/uni_modules/uview-ui/libs/config/props/avatarGroup.js b/uni_modules/uview-ui/libs/config/props/avatarGroup.js new file mode 100644 index 0000000..f4a66c3 --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/avatarGroup.js @@ -0,0 +1,23 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 16:49:55 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/avatarGroup.js + */ +export default { + // avatarGroup 组件 + avatarGroup: { + urls: () => [], + maxCount: 5, + shape: 'circle', + mode: 'scaleToFill', + showMore: true, + size: 40, + keyName: '', + gap: 0.5, + extraValue: 0 + } +} diff --git a/uni_modules/uview-ui/libs/config/props/backtop.js b/uni_modules/uview-ui/libs/config/props/backtop.js new file mode 100644 index 0000000..80f17d0 --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/backtop.js @@ -0,0 +1,27 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 16:50:18 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/backtop.js + */ +export default { + // backtop组件 + backtop: { + mode: 'circle', + icon: 'arrow-upward', + text: '', + duration: 100, + scrollTop: 0, + top: 400, + bottom: 100, + right: 20, + zIndex: 9, + iconStyle: () => ({ + color: '#909399', + fontSize: '19px' + }) + } +} diff --git a/uni_modules/uview-ui/libs/config/props/badge.js b/uni_modules/uview-ui/libs/config/props/badge.js new file mode 100644 index 0000000..44ee7cc --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/badge.js @@ -0,0 +1,27 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-23 19:51:50 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/badge.js + */ +export default { + // 徽标数组件 + badge: { + isDot: false, + value: '', + show: true, + max: 999, + type: 'error', + showZero: false, + bgColor: null, + color: null, + shape: 'circle', + numberType: 'overflow', + offset: () => [], + inverted: false, + absolute: false + } +} diff --git a/uni_modules/uview-ui/libs/config/props/button.js b/uni_modules/uview-ui/libs/config/props/button.js new file mode 100644 index 0000000..acd65fc --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/button.js @@ -0,0 +1,42 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 16:51:27 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/button.js + */ +export default { + // button组件 + button: { + hairline: false, + type: 'info', + size: 'normal', + shape: 'square', + plain: false, + disabled: false, + loading: false, + loadingText: '', + loadingMode: 'spinner', + loadingSize: 15, + openType: '', + formType: '', + appParameter: '', + hoverStopPropagation: true, + lang: 'en', + sessionFrom: '', + sendMessageTitle: '', + sendMessagePath: '', + sendMessageImg: '', + showMessageCard: false, + dataName: '', + throttleTime: 0, + hoverStartTime: 0, + hoverStayTime: 200, + text: '', + icon: '', + iconColor: '', + color: '' + } +} diff --git a/uni_modules/uview-ui/libs/config/props/calendar.js b/uni_modules/uview-ui/libs/config/props/calendar.js new file mode 100644 index 0000000..bfd2bd6 --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/calendar.js @@ -0,0 +1,42 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 16:52:43 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/calendar.js + */ +export default { + // calendar 组件 + calendar: { + title: '日期选择', + showTitle: true, + showSubtitle: true, + mode: 'single', + startText: '开始', + endText: '结束', + customList: () => [], + color: '#3c9cff', + minDate: 0, + maxDate: 0, + defaultDate: null, + maxCount: Number.MAX_SAFE_INTEGER, // Infinity + rowHeight: 56, + formatter: null, + showLunar: false, + showMark: true, + confirmText: '确定', + confirmDisabledText: '确定', + show: false, + closeOnClickOverlay: false, + readonly: false, + showConfirm: true, + maxRange: Number.MAX_SAFE_INTEGER, // Infinity + rangePrompt: '', + showRangePrompt: true, + allowSameDay: false, + round: 0, + monthNum: 3 + } +} diff --git a/uni_modules/uview-ui/libs/config/props/carKeyboard.js b/uni_modules/uview-ui/libs/config/props/carKeyboard.js new file mode 100644 index 0000000..af1baa0 --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/carKeyboard.js @@ -0,0 +1,15 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 16:53:20 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/carKeyboard.js + */ +export default { + // 车牌号键盘 + carKeyboard: { + random: false + } +} diff --git a/uni_modules/uview-ui/libs/config/props/cell.js b/uni_modules/uview-ui/libs/config/props/cell.js new file mode 100644 index 0000000..425ea3f --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/cell.js @@ -0,0 +1,35 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-23 20:53:09 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/cell.js + */ +export default { + // cell组件的props + cell: { + customClass: '', + title: '', + label: '', + value: '', + icon: '', + disabled: false, + border: true, + center: false, + url: '', + linkType: 'navigateTo', + clickable: false, + isLink: false, + required: false, + arrowDirection: '', + iconStyle: {}, + rightIconStyle: {}, + rightIcon: 'arrow-right', + titleStyle: {}, + size: '', + stop: true, + name: '' + } +} diff --git a/uni_modules/uview-ui/libs/config/props/cellGroup.js b/uni_modules/uview-ui/libs/config/props/cellGroup.js new file mode 100644 index 0000000..d48a9cd --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/cellGroup.js @@ -0,0 +1,17 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 16:54:16 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/cellGroup.js + */ +export default { + // cell-group组件的props + cellGroup: { + title: '', + border: true, + customStyle: {} + } +} diff --git a/uni_modules/uview-ui/libs/config/props/checkbox.js b/uni_modules/uview-ui/libs/config/props/checkbox.js new file mode 100644 index 0000000..2310901 --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/checkbox.js @@ -0,0 +1,27 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-23 21:06:59 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/checkbox.js + */ +export default { + // checkbox组件 + checkbox: { + name: '', + shape: '', + size: '', + checkbox: false, + disabled: '', + activeColor: '', + inactiveColor: '', + iconSize: '', + iconColor: '', + label: '', + labelSize: '', + labelColor: '', + labelDisabled: '' + } +} diff --git a/uni_modules/uview-ui/libs/config/props/checkboxGroup.js b/uni_modules/uview-ui/libs/config/props/checkboxGroup.js new file mode 100644 index 0000000..8798fa4 --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/checkboxGroup.js @@ -0,0 +1,29 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 16:54:47 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/checkboxGroup.js + */ +export default { + // checkbox-group组件 + checkboxGroup: { + name: '', + value: () => [], + shape: 'square', + disabled: false, + activeColor: '#2979ff', + inactiveColor: '#c8c9cc', + size: 18, + placement: 'row', + labelSize: 14, + labelColor: '#303133', + labelDisabled: false, + iconColor: '#ffffff', + iconSize: 12, + iconPlacement: 'left', + borderBottom: false + } +} diff --git a/uni_modules/uview-ui/libs/config/props/circleProgress.js b/uni_modules/uview-ui/libs/config/props/circleProgress.js new file mode 100644 index 0000000..b3a9b43 --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/circleProgress.js @@ -0,0 +1,15 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 16:55:02 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/circleProgress.js + */ +export default { + // circleProgress 组件 + circleProgress: { + percentage: 30 + } +} diff --git a/uni_modules/uview-ui/libs/config/props/code.js b/uni_modules/uview-ui/libs/config/props/code.js new file mode 100644 index 0000000..693417a --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/code.js @@ -0,0 +1,21 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 16:55:27 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/code.js + */ + +export default { + // code 组件 + code: { + seconds: 60, + startText: '获取验证码', + changeText: 'X秒重新获取', + endText: '重新获取', + keepRunning: false, + uniqueKey: '' + } +} diff --git a/uni_modules/uview-ui/libs/config/props/codeInput.js b/uni_modules/uview-ui/libs/config/props/codeInput.js new file mode 100644 index 0000000..cac9265 --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/codeInput.js @@ -0,0 +1,29 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 16:55:58 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/codeInput.js + */ +export default { + // codeInput 组件 + codeInput: { + adjustPosition: true, + maxlength: 6, + dot: false, + mode: 'box', + hairline: false, + space: 10, + value: '', + focus: false, + bold: false, + color: '#606266', + fontSize: 18, + size: 35, + disabledKeyboard: false, + borderColor: '#c9cacc', + disabledDot: true + } +} diff --git a/uni_modules/uview-ui/libs/config/props/col.js b/uni_modules/uview-ui/libs/config/props/col.js new file mode 100644 index 0000000..7621653 --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/col.js @@ -0,0 +1,19 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 16:56:12 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/col.js + */ +export default { + // col 组件 + col: { + span: 12, + offset: 0, + justify: 'start', + align: 'stretch', + textAlign: 'left' + } +} diff --git a/uni_modules/uview-ui/libs/config/props/collapse.js b/uni_modules/uview-ui/libs/config/props/collapse.js new file mode 100644 index 0000000..c2b9fdd --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/collapse.js @@ -0,0 +1,17 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 16:56:30 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/collapse.js + */ +export default { + // collapse 组件 + collapse: { + value: null, + accordion: false, + border: true + } +} diff --git a/uni_modules/uview-ui/libs/config/props/collapseItem.js b/uni_modules/uview-ui/libs/config/props/collapseItem.js new file mode 100644 index 0000000..74ce682 --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/collapseItem.js @@ -0,0 +1,25 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 16:56:42 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/collapseItem.js + */ +export default { + // collapseItem 组件 + collapseItem: { + title: '', + value: '', + label: '', + disabled: false, + isLink: true, + clickable: true, + border: true, + align: 'left', + name: '', + icon: '', + duration: 300 + } +} diff --git a/uni_modules/uview-ui/libs/config/props/columnNotice.js b/uni_modules/uview-ui/libs/config/props/columnNotice.js new file mode 100644 index 0000000..147c0aa --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/columnNotice.js @@ -0,0 +1,24 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 16:57:16 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/columnNotice.js + */ +export default { + // columnNotice 组件 + columnNotice: { + text: '', + icon: 'volume', + mode: '', + color: '#f9ae3d', + bgColor: '#fdf6ec', + fontSize: 14, + speed: 80, + step: false, + duration: 1500, + disableTouch: true + } +} diff --git a/uni_modules/uview-ui/libs/config/props/countDown.js b/uni_modules/uview-ui/libs/config/props/countDown.js new file mode 100644 index 0000000..81e33b1 --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/countDown.js @@ -0,0 +1,18 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:11:29 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/countDown.js + */ +export default { + // u-count-down 计时器组件 + countDown: { + time: 0, + format: 'HH:mm:ss', + autoStart: true, + millisecond: false + } +} diff --git a/uni_modules/uview-ui/libs/config/props/countTo.js b/uni_modules/uview-ui/libs/config/props/countTo.js new file mode 100644 index 0000000..a536cde --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/countTo.js @@ -0,0 +1,25 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 16:57:32 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/countTo.js + */ +export default { + // countTo 组件 + countTo: { + startVal: 0, + endVal: 0, + duration: 2000, + autoplay: true, + decimals: 0, + useEasing: true, + decimal: '.', + color: '#606266', + fontSize: 22, + bold: false, + separator: '' + } +} diff --git a/uni_modules/uview-ui/libs/config/props/datetimePicker.js b/uni_modules/uview-ui/libs/config/props/datetimePicker.js new file mode 100644 index 0000000..4f90966 --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/datetimePicker.js @@ -0,0 +1,36 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 16:57:48 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/datetimePicker.js + */ +export default { + // datetimePicker 组件 + datetimePicker: { + show: false, + showToolbar: true, + value: '', + title: '', + mode: 'datetime', + maxDate: new Date(new Date().getFullYear() + 10, 0, 1).getTime(), + minDate: new Date(new Date().getFullYear() - 10, 0, 1).getTime(), + minHour: 0, + maxHour: 23, + minMinute: 0, + maxMinute: 59, + filter: null, + formatter: null, + loading: false, + itemHeight: 44, + cancelText: '取消', + confirmText: '确认', + cancelColor: '#909193', + confirmColor: '#3c9cff', + visibleItemCount: 5, + closeOnClickOverlay: false, + defaultIndex: () => [] + } +} diff --git a/uni_modules/uview-ui/libs/config/props/divider.js b/uni_modules/uview-ui/libs/config/props/divider.js new file mode 100644 index 0000000..55a8ce4 --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/divider.js @@ -0,0 +1,23 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 16:58:03 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/divider.js + */ +export default { + // divider组件 + divider: { + dashed: false, + hairline: true, + dot: false, + textPosition: 'center', + text: '', + textSize: 14, + textColor: '#909399', + lineColor: '#dcdfe6' + } + +} diff --git a/uni_modules/uview-ui/libs/config/props/empty.js b/uni_modules/uview-ui/libs/config/props/empty.js new file mode 100644 index 0000000..fe20445 --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/empty.js @@ -0,0 +1,26 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:03:27 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/empty.js + */ +export default { + // empty组件 + empty: { + icon: '', + text: '', + textColor: '#c0c4cc', + textSize: 14, + iconColor: '#c0c4cc', + iconSize: 90, + mode: 'data', + width: 160, + height: 160, + show: true, + marginTop: 0 + } + +} diff --git a/uni_modules/uview-ui/libs/config/props/form.js b/uni_modules/uview-ui/libs/config/props/form.js new file mode 100644 index 0000000..41b122e --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/form.js @@ -0,0 +1,22 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:03:49 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/form.js + */ +export default { + // form 组件 + form: { + model: () => ({}), + rules: () => ({}), + errorType: 'message', + borderBottom: true, + labelPosition: 'left', + labelWidth: 45, + labelAlign: 'left', + labelStyle: () => ({}) + } +} diff --git a/uni_modules/uview-ui/libs/config/props/formItem.js b/uni_modules/uview-ui/libs/config/props/formItem.js new file mode 100644 index 0000000..4b7c90a --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/formItem.js @@ -0,0 +1,23 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:04:32 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/formItem.js + */ +export default { + // formItem 组件 + formItem: { + label: '', + prop: '', + borderBottom: '', + labelPosition: '', + labelWidth: '', + rightIcon: '', + leftIcon: '', + required: false, + leftIconStyle: '', + } +} diff --git a/uni_modules/uview-ui/libs/config/props/gap.js b/uni_modules/uview-ui/libs/config/props/gap.js new file mode 100644 index 0000000..60a21af --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/gap.js @@ -0,0 +1,19 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:05:25 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/gap.js + */ +export default { + // gap组件 + gap: { + bgColor: 'transparent', + height: 20, + marginTop: 0, + marginBottom: 0, + customStyle: {} + } +} diff --git a/uni_modules/uview-ui/libs/config/props/grid.js b/uni_modules/uview-ui/libs/config/props/grid.js new file mode 100644 index 0000000..60abeb7 --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/grid.js @@ -0,0 +1,17 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:05:57 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/grid.js + */ +export default { + // grid组件 + grid: { + col: 3, + border: false, + align: 'left' + } +} diff --git a/uni_modules/uview-ui/libs/config/props/gridItem.js b/uni_modules/uview-ui/libs/config/props/gridItem.js new file mode 100644 index 0000000..1b747f4 --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/gridItem.js @@ -0,0 +1,16 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:06:13 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/gridItem.js + */ +export default { + // grid-item组件 + gridItem: { + name: null, + bgColor: 'transparent' + } +} diff --git a/uni_modules/uview-ui/libs/config/props/icon.js b/uni_modules/uview-ui/libs/config/props/icon.js new file mode 100644 index 0000000..1d81d2d --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/icon.js @@ -0,0 +1,36 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 18:00:14 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/icon.js + */ +import config from '../config' + +const { + color +} = config +export default { + // icon组件 + icon: { + name: '', + color: color['u-content-color'], + size: '16px', + bold: false, + index: '', + hoverClass: '', + customPrefix: 'uicon', + label: '', + labelPos: 'right', + labelSize: '15px', + labelColor: color['u-content-color'], + space: '3px', + imgMode: '', + width: '', + height: '', + top: 0, + stop: false + } +} diff --git a/uni_modules/uview-ui/libs/config/props/image.js b/uni_modules/uview-ui/libs/config/props/image.js new file mode 100644 index 0000000..2552db6 --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/image.js @@ -0,0 +1,30 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:01:51 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/image.js + */ +export default { + // image组件 + image: { + src: '', + mode: 'aspectFill', + width: '300', + height: '225', + shape: 'square', + radius: 0, + lazyLoad: true, + showMenuByLongpress: true, + loadingIcon: 'photo', + errorIcon: 'error-circle', + showLoading: true, + showError: true, + fade: true, + webp: false, + duration: 500, + bgColor: '#f3f4f6' + } +} diff --git a/uni_modules/uview-ui/libs/config/props/indexAnchor.js b/uni_modules/uview-ui/libs/config/props/indexAnchor.js new file mode 100644 index 0000000..bb20d46 --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/indexAnchor.js @@ -0,0 +1,19 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:13:15 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/indexAnchor.js + */ +export default { + // indexAnchor 组件 + indexAnchor: { + text: '', + color: '#606266', + size: 14, + bgColor: '#dedede', + height: 32 + } +} diff --git a/uni_modules/uview-ui/libs/config/props/indexList.js b/uni_modules/uview-ui/libs/config/props/indexList.js new file mode 100644 index 0000000..dc6ce94 --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/indexList.js @@ -0,0 +1,19 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:13:35 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/indexList.js + */ +export default { + // indexList 组件 + indexList: { + inactiveColor: '#606266', + activeColor: '#5677fc', + indexList: () => [], + sticky: true, + customNavHeight: 0 + } +} diff --git a/uni_modules/uview-ui/libs/config/props/input.js b/uni_modules/uview-ui/libs/config/props/input.js new file mode 100644 index 0000000..4f0edc6 --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/input.js @@ -0,0 +1,48 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:13:55 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/input.js + */ +export default { + // index 组件 + input: { + value: '', + type: 'text', + fixed: false, + disabled: false, + disabledColor: '#f5f7fa', + clearable: false, + password: false, + maxlength: -1, + placeholder: null, + placeholderClass: 'input-placeholder', + placeholderStyle: 'color: #c0c4cc', + showWordLimit: false, + confirmType: 'done', + confirmHold: false, + holdKeyboard: false, + focus: false, + autoBlur: false, + disableDefaultPadding: false, + cursor: -1, + cursorSpacing: 30, + selectionStart: -1, + selectionEnd: -1, + adjustPosition: true, + inputAlign: 'left', + fontSize: '15px', + color: '#303133', + prefixIcon: '', + prefixIconStyle: '', + suffixIcon: '', + suffixIconStyle: '', + border: 'surround', + readonly: false, + shape: 'square', + formatter: null + } +} diff --git a/uni_modules/uview-ui/libs/config/props/keyboard.js b/uni_modules/uview-ui/libs/config/props/keyboard.js new file mode 100644 index 0000000..57182bd --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/keyboard.js @@ -0,0 +1,30 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:07:49 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/keyboard.js + */ +export default { + // 键盘组件 + keyboard: { + mode: 'number', + dotDisabled: false, + tooltip: true, + showTips: true, + tips: '', + showCancel: true, + showConfirm: true, + random: false, + safeAreaInsetBottom: true, + closeOnClickOverlay: true, + show: false, + overlay: true, + zIndex: 10075, + cancelText: '取消', + confirmText: '确定', + autoChange: false + } +} diff --git a/uni_modules/uview-ui/libs/config/props/line.js b/uni_modules/uview-ui/libs/config/props/line.js new file mode 100644 index 0000000..2c87af2 --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/line.js @@ -0,0 +1,20 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:04:49 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/line.js + */ +export default { + // line组件 + line: { + color: '#d6d7d9', + length: '100%', + direction: 'row', + hairline: true, + margin: 0, + dashed: false + } +} diff --git a/uni_modules/uview-ui/libs/config/props/lineProgress.js b/uni_modules/uview-ui/libs/config/props/lineProgress.js new file mode 100644 index 0000000..cdfcb0e --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/lineProgress.js @@ -0,0 +1,19 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:14:11 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/lineProgress.js + */ +export default { + // lineProgress 组件 + lineProgress: { + activeColor: '#19be6b', + inactiveColor: '#ececec', + percentage: 0, + showText: true, + height: 12 + } +} diff --git a/uni_modules/uview-ui/libs/config/props/link.js b/uni_modules/uview-ui/libs/config/props/link.js new file mode 100644 index 0000000..6c4c883 --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/link.js @@ -0,0 +1,26 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:45:36 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/link.js + */ +import config from '../config' + +const { + color +} = config +export default { + // link超链接组件props参数 + link: { + color: color['u-primary'], + fontSize: 15, + underLine: false, + href: '', + mpTips: '链接已复制,请在浏览器打开', + lineColor: '', + text: '' + } +} diff --git a/uni_modules/uview-ui/libs/config/props/list.js b/uni_modules/uview-ui/libs/config/props/list.js new file mode 100644 index 0000000..a830c32 --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/list.js @@ -0,0 +1,28 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:14:53 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/list.js + */ +export default { + // list 组件 + list: { + showScrollbar: false, + lowerThreshold: 50, + upperThreshold: 0, + scrollTop: 0, + offsetAccuracy: 10, + enableFlex: false, + pagingEnabled: false, + scrollable: true, + scrollIntoView: '', + scrollWithAnimation: false, + enableBackToTop: false, + height: 0, + width: 0, + preLoadScreen: 1 + } +} diff --git a/uni_modules/uview-ui/libs/config/props/listItem.js b/uni_modules/uview-ui/libs/config/props/listItem.js new file mode 100644 index 0000000..7fe2166 --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/listItem.js @@ -0,0 +1,15 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:15:40 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/listItem.js + */ +export default { + // listItem 组件 + listItem: { + anchor: '' + } +} diff --git a/uni_modules/uview-ui/libs/config/props/loadingIcon.js b/uni_modules/uview-ui/libs/config/props/loadingIcon.js new file mode 100644 index 0000000..f4739c4 --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/loadingIcon.js @@ -0,0 +1,30 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:45:47 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/loadingIcon.js + */ +import config from '../config' + +const { + color +} = config +export default { + // loading-icon加载中图标组件 + loadingIcon: { + show: true, + color: color['u-tips-color'], + textColor: color['u-tips-color'], + vertical: false, + mode: 'spinner', + size: 24, + textSize: 15, + text: '', + timingFunction: 'ease-in-out', + duration: 1200, + inactiveColor: '' + } +} diff --git a/uni_modules/uview-ui/libs/config/props/loadingPage.js b/uni_modules/uview-ui/libs/config/props/loadingPage.js new file mode 100644 index 0000000..dc53109 --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/loadingPage.js @@ -0,0 +1,23 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:00:23 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/loadingPage.js + */ +export default { + // loading-page组件 + loadingPage: { + loadingText: '正在加载', + image: '', + loadingMode: 'circle', + loading: false, + bgColor: '#ffffff', + color: '#C8C8C8', + fontSize: 19, + iconSize: 28, + loadingColor: '#C8C8C8' + } +} diff --git a/uni_modules/uview-ui/libs/config/props/loadmore.js b/uni_modules/uview-ui/libs/config/props/loadmore.js new file mode 100644 index 0000000..67c1160 --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/loadmore.js @@ -0,0 +1,32 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:15:26 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/loadmore.js + */ +export default { + // loadmore 组件 + loadmore: { + status: 'loadmore', + bgColor: 'transparent', + icon: true, + fontSize: 14, + iconSize: 17, + color: '#606266', + loadingIcon: 'spinner', + loadmoreText: '加载更多', + loadingText: '正在加载...', + nomoreText: '没有更多了', + isDot: false, + iconColor: '#b7b7b7', + marginTop: 10, + marginBottom: 10, + height: 'auto', + line: false, + lineColor: '#E6E8EB', + dashed: false, + } +} diff --git a/uni_modules/uview-ui/libs/config/props/modal.js b/uni_modules/uview-ui/libs/config/props/modal.js new file mode 100644 index 0000000..2ae3fff --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/modal.js @@ -0,0 +1,30 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:15:59 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/modal.js + */ +export default { + // modal 组件 + modal: { + show: false, + title: '', + content: '', + confirmText: '确认', + cancelText: '取消', + showConfirmButton: true, + showCancelButton: false, + confirmColor: '#2979ff', + cancelColor: '#606266', + buttonReverse: false, + zoom: true, + asyncClose: false, + closeOnClickOverlay: false, + negativeTop: 0, + width: '650rpx', + confirmButtonShape: '' + } +} diff --git a/uni_modules/uview-ui/libs/config/props/navbar.js b/uni_modules/uview-ui/libs/config/props/navbar.js new file mode 100644 index 0000000..614a99d --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/navbar.js @@ -0,0 +1,32 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:16:18 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/navbar.js + */ +import color from '../color' +export default { + // navbar 组件 + navbar: { + safeAreaInsetTop: true, + placeholder: false, + fixed: true, + border: false, + leftIcon: 'arrow-left', + leftText: '', + rightText: '', + rightIcon: '', + title: '', + bgColor: '#ffffff', + titleWidth: '400rpx', + height: '44px', + leftIconSize: 20, + leftIconColor: color.mainColor, + autoBack: false, + titleStyle: '' + } + +} diff --git a/uni_modules/uview-ui/libs/config/props/noNetwork.js b/uni_modules/uview-ui/libs/config/props/noNetwork.js new file mode 100644 index 0000000..74dba1b --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/noNetwork.js @@ -0,0 +1,18 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:16:39 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/noNetwork.js + */ +export default { + // noNetwork + noNetwork: { + tips: '哎呀,网络信号丢失', + zIndex: '', + image: 'data:image/png;base64,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' + } + +} diff --git a/uni_modules/uview-ui/libs/config/props/noticeBar.js b/uni_modules/uview-ui/libs/config/props/noticeBar.js new file mode 100644 index 0000000..02c660a --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/noticeBar.js @@ -0,0 +1,27 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:17:13 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/noticeBar.js + */ +export default { + // noticeBar + noticeBar: { + text: () => [], + direction: 'row', + step: false, + icon: 'volume', + mode: '', + color: '#f9ae3d', + bgColor: '#fdf6ec', + speed: 80, + fontSize: 14, + duration: 2000, + disableTouch: true, + url: '', + linkType: 'navigateTo' + } +} diff --git a/uni_modules/uview-ui/libs/config/props/notify.js b/uni_modules/uview-ui/libs/config/props/notify.js new file mode 100644 index 0000000..1042d2a --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/notify.js @@ -0,0 +1,22 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:10:21 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/notify.js + */ +export default { + // notify组件 + notify: { + top: 0, + type: 'primary', + color: '#ffffff', + bgColor: '', + message: '', + duration: 3000, + fontSize: 15, + safeAreaInsetTop: false + } +} diff --git a/uni_modules/uview-ui/libs/config/props/numberBox.js b/uni_modules/uview-ui/libs/config/props/numberBox.js new file mode 100644 index 0000000..424f0ca --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/numberBox.js @@ -0,0 +1,35 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:11:46 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/numberBox.js + */ +export default { + // 步进器组件 + numberBox: { + name: '', + value: 0, + min: 1, + max: Number.MAX_SAFE_INTEGER, + step: 1, + integer: false, + disabled: false, + disabledInput: false, + asyncChange: false, + inputWidth: 35, + showMinus: true, + showPlus: true, + decimalLength: null, + longPress: true, + color: '#323233', + buttonSize: 30, + bgColor: '#EBECEE', + cursorSpacing: 100, + disableMinus: false, + disablePlus: false, + iconStyle: '' + } +} diff --git a/uni_modules/uview-ui/libs/config/props/numberKeyboard.js b/uni_modules/uview-ui/libs/config/props/numberKeyboard.js new file mode 100644 index 0000000..7b45065 --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/numberKeyboard.js @@ -0,0 +1,17 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:08:05 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/numberKeyboard.js + */ +export default { + // 数字键盘 + numberKeyboard: { + mode: 'number', + dotDisabled: false, + random: false + } +} diff --git a/uni_modules/uview-ui/libs/config/props/overlay.js b/uni_modules/uview-ui/libs/config/props/overlay.js new file mode 100644 index 0000000..c26d068 --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/overlay.js @@ -0,0 +1,18 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:06:50 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/overlay.js + */ +export default { + // overlay组件 + overlay: { + show: false, + zIndex: 10070, + duration: 300, + opacity: 0.5 + } +} diff --git a/uni_modules/uview-ui/libs/config/props/parse.js b/uni_modules/uview-ui/libs/config/props/parse.js new file mode 100644 index 0000000..feb22b9 --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/parse.js @@ -0,0 +1,22 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:17:33 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/parse.js + */ +export default { + // parse + parse: { + copyLink: true, + errorImg: '', + lazyLoad: false, + loadingImg: '', + pauseVideo: true, + previewImg: true, + setTitle: true, + showImgMenu: true + } +} diff --git a/uni_modules/uview-ui/libs/config/props/picker.js b/uni_modules/uview-ui/libs/config/props/picker.js new file mode 100644 index 0000000..f06b321 --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/picker.js @@ -0,0 +1,29 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:18:20 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/picker.js + */ +export default { + // picker + picker: { + show: false, + showToolbar: true, + title: '', + columns: () => [], + loading: false, + itemHeight: 44, + cancelText: '取消', + confirmText: '确定', + cancelColor: '#909193', + confirmColor: '#3c9cff', + visibleItemCount: 5, + keyName: 'text', + closeOnClickOverlay: false, + defaultIndex: () => [], + immediateChange: false + } +} diff --git a/uni_modules/uview-ui/libs/config/props/popup.js b/uni_modules/uview-ui/libs/config/props/popup.js new file mode 100644 index 0000000..0cc1bc0 --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/popup.js @@ -0,0 +1,29 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:06:33 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/popup.js + */ +export default { + // popup组件 + popup: { + show: false, + overlay: true, + mode: 'bottom', + duration: 300, + closeable: false, + overlayStyle: () => {}, + closeOnClickOverlay: true, + zIndex: 10075, + safeAreaInsetBottom: true, + safeAreaInsetTop: false, + closeIconPos: 'top-right', + round: 0, + zoom: true, + bgColor: '', + overlayOpacity: 0.5 + } +} diff --git a/uni_modules/uview-ui/libs/config/props/radio.js b/uni_modules/uview-ui/libs/config/props/radio.js new file mode 100644 index 0000000..4df200f --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/radio.js @@ -0,0 +1,27 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:02:34 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/radio.js + */ +export default { + // radio组件 + radio: { + name: '', + shape: '', + disabled: '', + labelDisabled: '', + activeColor: '', + inactiveColor: '', + iconSize: '', + labelSize: '', + label: '', + labelColor: '', + size: '', + iconColor: '', + placement: '' + } +} diff --git a/uni_modules/uview-ui/libs/config/props/radioGroup.js b/uni_modules/uview-ui/libs/config/props/radioGroup.js new file mode 100644 index 0000000..728e9db --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/radioGroup.js @@ -0,0 +1,30 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:03:12 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/radioGroup.js + */ +export default { + // radio-group组件 + radioGroup: { + value: '', + disabled: false, + shape: 'circle', + activeColor: '#2979ff', + inactiveColor: '#c8c9cc', + name: '', + size: 18, + placement: 'row', + label: '', + labelColor: '#303133', + labelSize: 14, + labelDisabled: false, + iconColor: '#ffffff', + iconSize: 12, + borderBottom: false, + iconPlacement: 'left' + } +} diff --git a/uni_modules/uview-ui/libs/config/props/rate.js b/uni_modules/uview-ui/libs/config/props/rate.js new file mode 100644 index 0000000..d31c61a --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/rate.js @@ -0,0 +1,26 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:05:09 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/rate.js + */ +export default { + // rate组件 + rate: { + value: 1, + count: 5, + disabled: false, + size: 18, + inactiveColor: '#b2b2b2', + activeColor: '#FA3534', + gutter: 4, + minCount: 1, + allowHalf: false, + activeIcon: 'star-fill', + inactiveIcon: 'star', + touchable: true + } +} diff --git a/uni_modules/uview-ui/libs/config/props/readMore.js b/uni_modules/uview-ui/libs/config/props/readMore.js new file mode 100644 index 0000000..09b11cc --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/readMore.js @@ -0,0 +1,22 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:18:41 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/readMore.js + */ +export default { + // readMore + readMore: { + showHeight: 400, + toggle: false, + closeText: '展开阅读全文', + openText: '收起', + color: '#2979ff', + fontSize: 14, + textIndent: '2em', + name: '' + } +} diff --git a/uni_modules/uview-ui/libs/config/props/row.js b/uni_modules/uview-ui/libs/config/props/row.js new file mode 100644 index 0000000..573a431 --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/row.js @@ -0,0 +1,17 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:18:58 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/row.js + */ +export default { + // row + row: { + gutter: 0, + justify: 'start', + align: 'center' + } +} diff --git a/uni_modules/uview-ui/libs/config/props/rowNotice.js b/uni_modules/uview-ui/libs/config/props/rowNotice.js new file mode 100644 index 0000000..cd9d0a0 --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/rowNotice.js @@ -0,0 +1,21 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:19:13 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/rowNotice.js + */ +export default { + // rowNotice + rowNotice: { + text: '', + icon: 'volume', + mode: '', + color: '#f9ae3d', + bgColor: '#fdf6ec', + fontSize: 14, + speed: 80 + } +} diff --git a/uni_modules/uview-ui/libs/config/props/scrollList.js b/uni_modules/uview-ui/libs/config/props/scrollList.js new file mode 100644 index 0000000..441e63a --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/scrollList.js @@ -0,0 +1,20 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:19:28 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/scrollList.js + */ +export default { + // scrollList + scrollList: { + indicatorWidth: 50, + indicatorBarWidth: 20, + indicator: true, + indicatorColor: '#f2f2f2', + indicatorActiveColor: '#3c9cff', + indicatorStyle: '' + } +} diff --git a/uni_modules/uview-ui/libs/config/props/search.js b/uni_modules/uview-ui/libs/config/props/search.js new file mode 100644 index 0000000..2699954 --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/search.js @@ -0,0 +1,37 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:19:45 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/search.js + */ +export default { + // search + search: { + shape: 'round', + bgColor: '#f2f2f2', + placeholder: '请输入关键字', + clearabled: true, + focus: false, + showAction: true, + actionStyle: () => ({}), + actionText: '搜索', + inputAlign: 'left', + inputStyle: () => ({}), + disabled: false, + borderColor: 'transparent', + searchIconColor: '#909399', + searchIconSize: 22, + color: '#606266', + placeholderColor: '#909399', + searchIcon: 'search', + margin: '0', + animation: false, + value: '', + maxlength: '-1', + height: 32, + label: null + } +} diff --git a/uni_modules/uview-ui/libs/config/props/section.js b/uni_modules/uview-ui/libs/config/props/section.js new file mode 100644 index 0000000..f432648 --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/section.js @@ -0,0 +1,24 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:07:33 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/section.js + */ +export default { + // u-section组件 + section: { + title: '', + subTitle: '更多', + right: true, + fontSize: 15, + bold: true, + color: '#303133', + subColor: '#909399', + showLine: true, + lineColor: '', + arrow: true + } +} diff --git a/uni_modules/uview-ui/libs/config/props/skeleton.js b/uni_modules/uview-ui/libs/config/props/skeleton.js new file mode 100644 index 0000000..83b777d --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/skeleton.js @@ -0,0 +1,25 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:20:14 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/skeleton.js + */ +export default { + // skeleton + skeleton: { + loading: true, + animate: true, + rows: 0, + rowsWidth: '100%', + rowsHeight: 18, + title: true, + titleWidth: '50%', + titleHeight: 18, + avatar: false, + avatarSize: 32, + avatarShape: 'circle' + } +} diff --git a/uni_modules/uview-ui/libs/config/props/slider.js b/uni_modules/uview-ui/libs/config/props/slider.js new file mode 100644 index 0000000..50cc37f --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/slider.js @@ -0,0 +1,25 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:08:25 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/slider.js + */ +export default { + // slider组件 + slider: { + value: 0, + blockSize: 18, + min: 0, + max: 100, + step: 1, + activeColor: '#2979ff', + inactiveColor: '#c0c4cc', + blockColor: '#ffffff', + showValue: false, + disabled:false, + blockStyle: () => {} + } +} diff --git a/uni_modules/uview-ui/libs/config/props/statusBar.js b/uni_modules/uview-ui/libs/config/props/statusBar.js new file mode 100644 index 0000000..d237a83 --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/statusBar.js @@ -0,0 +1,15 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:20:39 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/statusBar.js + */ +export default { + // statusBar + statusBar: { + bgColor: 'transparent' + } +} diff --git a/uni_modules/uview-ui/libs/config/props/steps.js b/uni_modules/uview-ui/libs/config/props/steps.js new file mode 100644 index 0000000..881c39e --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/steps.js @@ -0,0 +1,21 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:12:37 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/steps.js + */ +export default { + // steps组件 + steps: { + direction: 'row', + current: 0, + activeColor: '#3c9cff', + inactiveColor: '#969799', + activeIcon: '', + inactiveIcon: '', + dot: false + } +} diff --git a/uni_modules/uview-ui/libs/config/props/stepsItem.js b/uni_modules/uview-ui/libs/config/props/stepsItem.js new file mode 100644 index 0000000..5dba8f4 --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/stepsItem.js @@ -0,0 +1,18 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:12:55 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/stepsItem.js + */ +export default { + // steps-item组件 + stepsItem: { + title: '', + desc: '', + iconSize: 17, + error: false + } +} diff --git a/uni_modules/uview-ui/libs/config/props/sticky.js b/uni_modules/uview-ui/libs/config/props/sticky.js new file mode 100644 index 0000000..b034604 --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/sticky.js @@ -0,0 +1,20 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:01:30 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/sticky.js + */ +export default { + // sticky组件 + sticky: { + offsetTop: 0, + customNavHeight: 0, + disabled: false, + bgColor: 'transparent', + zIndex: '', + index: '' + } +} diff --git a/uni_modules/uview-ui/libs/config/props/subsection.js b/uni_modules/uview-ui/libs/config/props/subsection.js new file mode 100644 index 0000000..9a165ff --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/subsection.js @@ -0,0 +1,23 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:12:20 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/subsection.js + */ +export default { + // subsection组件 + subsection: { + list: [], + current: 0, + activeColor: '#3c9cff', + inactiveColor: '#303133', + mode: 'button', + fontSize: 12, + bold: true, + bgColor: '#eeeeef', + keyName: 'name' + } +} diff --git a/uni_modules/uview-ui/libs/config/props/swipeAction.js b/uni_modules/uview-ui/libs/config/props/swipeAction.js new file mode 100644 index 0000000..25051b8 --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/swipeAction.js @@ -0,0 +1,15 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:00:42 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/swipeAction.js + */ +export default { + // swipe-action组件 + swipeAction: { + autoClose: true + } +} diff --git a/uni_modules/uview-ui/libs/config/props/swipeActionItem.js b/uni_modules/uview-ui/libs/config/props/swipeActionItem.js new file mode 100644 index 0000000..40ef27c --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/swipeActionItem.js @@ -0,0 +1,21 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:01:13 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/swipeActionItem.js + */ +export default { + // swipeActionItem 组件 + swipeActionItem: { + show: false, + name: '', + disabled: false, + threshold: 20, + autoClose: true, + options: [], + duration: 300 + } +} diff --git a/uni_modules/uview-ui/libs/config/props/swiper.js b/uni_modules/uview-ui/libs/config/props/swiper.js new file mode 100644 index 0000000..0e6a3b7 --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/swiper.js @@ -0,0 +1,39 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:21:38 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/swiper.js + */ +export default { + // swiper 组件 + swiper: { + list: () => [], + indicator: false, + indicatorActiveColor: '#FFFFFF', + indicatorInactiveColor: 'rgba(255, 255, 255, 0.35)', + indicatorStyle: '', + indicatorMode: 'line', + autoplay: true, + current: 0, + currentItemId: '', + interval: 3000, + duration: 300, + circular: false, + previousMargin: 0, + nextMargin: 0, + acceleration: false, + displayMultipleItems: 1, + easingFunction: 'default', + keyName: 'url', + imgMode: 'aspectFill', + height: 130, + bgColor: '#f3f4f6', + radius: 4, + loading: false, + showTitle: false + } + +} diff --git a/uni_modules/uview-ui/libs/config/props/swipterIndicator.js b/uni_modules/uview-ui/libs/config/props/swipterIndicator.js new file mode 100644 index 0000000..4b59e6e --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/swipterIndicator.js @@ -0,0 +1,19 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:22:07 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/swiperIndicator.js + */ +export default { + // swiperIndicator 组件 + swiperIndicator: { + length: 0, + current: 0, + indicatorActiveColor: '', + indicatorInactiveColor: '', + indicatorMode: 'line' + } +} diff --git a/uni_modules/uview-ui/libs/config/props/switch.js b/uni_modules/uview-ui/libs/config/props/switch.js new file mode 100644 index 0000000..e6400b4 --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/switch.js @@ -0,0 +1,24 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:22:24 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/switch.js + */ +export default { + // switch + switch: { + loading: false, + disabled: false, + size: 25, + activeColor: '#2979ff', + inactiveColor: '#ffffff', + value: false, + activeValue: true, + inactiveValue: false, + asyncChange: false, + space: 0 + } +} diff --git a/uni_modules/uview-ui/libs/config/props/tabbar.js b/uni_modules/uview-ui/libs/config/props/tabbar.js new file mode 100644 index 0000000..187112d --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/tabbar.js @@ -0,0 +1,22 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:22:40 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/tabbar.js + */ +export default { + // tabbar + tabbar: { + value: null, + safeAreaInsetBottom: true, + border: true, + zIndex: 1, + activeColor: '#1989fa', + inactiveColor: '#7d7e80', + fixed: true, + placeholder: true + } +} diff --git a/uni_modules/uview-ui/libs/config/props/tabbarItem.js b/uni_modules/uview-ui/libs/config/props/tabbarItem.js new file mode 100644 index 0000000..d036ce5 --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/tabbarItem.js @@ -0,0 +1,20 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:22:55 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/tabbarItem.js + */ +export default { + // + tabbarItem: { + name: null, + icon: '', + badge: null, + dot: false, + text: '', + badgeStyle: 'top: 6px;right:2px;' + } +} diff --git a/uni_modules/uview-ui/libs/config/props/tabs.js b/uni_modules/uview-ui/libs/config/props/tabs.js new file mode 100644 index 0000000..81c794a --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/tabs.js @@ -0,0 +1,32 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:23:14 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/tabs.js + */ +export default { + // + tabs: { + duration: 300, + list: () => [], + lineColor: '#3c9cff', + activeStyle: () => ({ + color: '#303133' + }), + inactiveStyle: () => ({ + color: '#606266' + }), + lineWidth: 20, + lineHeight: 3, + lineBgSize: 'cover', + itemStyle: () => ({ + height: '44px' + }), + scrollable: true, + current: 0, + keyName: 'name' + } +} diff --git a/uni_modules/uview-ui/libs/config/props/tag.js b/uni_modules/uview-ui/libs/config/props/tag.js new file mode 100644 index 0000000..125ce94 --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/tag.js @@ -0,0 +1,29 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:23:37 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/tag.js + */ +export default { + // tag 组件 + tag: { + type: 'primary', + disabled: false, + size: 'medium', + shape: 'square', + text: '', + bgColor: '', + color: '', + borderColor: '', + closeColor: '#C6C7CB', + name: '', + plainFill: false, + plain: false, + closable: false, + show: true, + icon: '' + } +} diff --git a/uni_modules/uview-ui/libs/config/props/text.js b/uni_modules/uview-ui/libs/config/props/text.js new file mode 100644 index 0000000..7e73606 --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/text.js @@ -0,0 +1,38 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:23:58 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/text.js + */ +export default { + // text 组件 + text: { + type: '', + show: true, + text: '', + prefixIcon: '', + suffixIcon: '', + mode: '', + href: '', + format: '', + call: false, + openType: '', + bold: false, + block: false, + lines: '', + color: '#303133', + size: 15, + iconStyle: () => ({ + fontSize: '15px' + }), + decoration: 'none', + margin: 0, + lineHeight: '', + align: 'left', + wordWrap: 'normal' + } + +} diff --git a/uni_modules/uview-ui/libs/config/props/textarea.js b/uni_modules/uview-ui/libs/config/props/textarea.js new file mode 100644 index 0000000..44519f9 --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/textarea.js @@ -0,0 +1,36 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:24:32 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/textarea.js + */ +export default { + // textarea 组件 + textarea: { + value: '', + placeholder: '', + placeholderClass: 'textarea-placeholder', + placeholderStyle: 'color: #c0c4cc', + height: 70, + confirmType: 'done', + disabled: false, + count: false, + focus: false, + autoHeight: false, + fixed: false, + cursorSpacing: 0, + cursor: '', + showConfirmBar: true, + selectionStart: -1, + selectionEnd: -1, + adjustPosition: true, + disableDefaultPadding: false, + holdKeyboard: false, + maxlength: 140, + border: 'surround', + formatter: null + } +} diff --git a/uni_modules/uview-ui/libs/config/props/toast.js b/uni_modules/uview-ui/libs/config/props/toast.js new file mode 100644 index 0000000..a50134b --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/toast.js @@ -0,0 +1,30 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:07:07 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/toast.js + */ +export default { + // toast组件 + toast: { + zIndex: 10090, + loading: false, + text: '', + icon: '', + type: '', + loadingMode: '', + show: '', + overlay: false, + position: 'center', + params: () => {}, + duration: 2000, + isTab: false, + url: '', + callback: null, + back: false + } + +} diff --git a/uni_modules/uview-ui/libs/config/props/toolbar.js b/uni_modules/uview-ui/libs/config/props/toolbar.js new file mode 100644 index 0000000..3289967 --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/toolbar.js @@ -0,0 +1,21 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:24:55 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/toolbar.js + */ +export default { + // toolbar 组件 + toolbar: { + show: true, + cancelText: '取消', + confirmText: '确认', + cancelColor: '#909193', + confirmColor: '#3c9cff', + title: '' + } + +} diff --git a/uni_modules/uview-ui/libs/config/props/tooltip.js b/uni_modules/uview-ui/libs/config/props/tooltip.js new file mode 100644 index 0000000..115e030 --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/tooltip.js @@ -0,0 +1,25 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:25:14 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/tooltip.js + */ +export default { + // tooltip 组件 + tooltip: { + text: '', + copyText: '', + size: 14, + color: '#606266', + bgColor: 'transparent', + direction: 'top', + zIndex: 10071, + showCopy: true, + buttons: () => [], + overlay: true, + showToast: true + } +} diff --git a/uni_modules/uview-ui/libs/config/props/transition.js b/uni_modules/uview-ui/libs/config/props/transition.js new file mode 100644 index 0000000..0fad118 --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/transition.js @@ -0,0 +1,18 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 16:59:00 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/transition.js + */ +export default { + // transition动画组件的props + transition: { + show: false, + mode: 'fade', + duration: '300', + timingFunction: 'ease-out' + } +} diff --git a/uni_modules/uview-ui/libs/config/props/upload.js b/uni_modules/uview-ui/libs/config/props/upload.js new file mode 100644 index 0000000..fc7ca92 --- /dev/null +++ b/uni_modules/uview-ui/libs/config/props/upload.js @@ -0,0 +1,36 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:09:50 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/upload.js + */ +export default { + // upload组件 + upload: { + accept: 'image', + capture: () => ['album', 'camera'], + compressed: true, + camera: 'back', + maxDuration: 60, + uploadIcon: 'camera-fill', + uploadIconColor: '#D3D4D6', + useBeforeRead: false, + previewFullImage: true, + maxCount: 52, + disabled: false, + imageMode: 'aspectFill', + name: '', + sizeType: () => ['original', 'compressed'], + multiple: false, + deletable: true, + maxSize: Number.MAX_VALUE, + fileList: () => [], + uploadText: '', + width: 80, + height: 80, + previewImage: true + } +} diff --git a/uni_modules/uview-ui/libs/config/zIndex.js b/uni_modules/uview-ui/libs/config/zIndex.js new file mode 100644 index 0000000..5fc3682 --- /dev/null +++ b/uni_modules/uview-ui/libs/config/zIndex.js @@ -0,0 +1,20 @@ +// uniapp在H5中各API的z-index值如下: +/** + * actionsheet: 999 + * modal: 999 + * navigate: 998 + * tabbar: 998 + * toast: 999 + */ + +export default { + toast: 10090, + noNetwork: 10080, + // popup包含popup,actionsheet,keyboard,picker的值 + popup: 10075, + mask: 10070, + navbar: 980, + topTips: 975, + sticky: 970, + indexListSticky: 965 +} diff --git a/uni_modules/uview-ui/libs/css/color.scss b/uni_modules/uview-ui/libs/css/color.scss new file mode 100644 index 0000000..3237ba4 --- /dev/null +++ b/uni_modules/uview-ui/libs/css/color.scss @@ -0,0 +1,155 @@ +.u-primary-light { + color: $u-primary-light; +} + +.u-warning-light { + color: $u-warning-light; +} + +.u-success-light { + color: $u-success-light; +} + +.u-error-light { + color: $u-error-light; +} + +.u-info-light { + color: $u-info-light; +} + +.u-primary-light-bg { + background-color: $u-primary-light; +} + +.u-warning-light-bg { + background-color: $u-warning-light; +} + +.u-success-light-bg { + background-color: $u-success-light; +} + +.u-error-light-bg { + background-color: $u-error-light; +} + +.u-info-light-bg { + background-color: $u-info-light; +} + +.u-primary-dark { + color: $u-primary-dark; +} + +.u-warning-dark { + color: $u-warning-dark; +} + +.u-success-dark { + color: $u-success-dark; +} + +.u-error-dark { + color: $u-error-dark; +} + +.u-info-dark { + color: $u-info-dark; +} + +.u-primary-dark-bg { + background-color: $u-primary-dark; +} + +.u-warning-dark-bg { + background-color: $u-warning-dark; +} + +.u-success-dark-bg { + background-color: $u-success-dark; +} + +.u-error-dark-bg { + background-color: $u-error-dark; +} + +.u-info-dark-bg { + background-color: $u-info-dark; +} + +.u-primary-disabled { + color: $u-primary-disabled; +} + +.u-warning-disabled { + color: $u-warning-disabled; +} + +.u-success-disabled { + color: $u-success-disabled; +} + +.u-error-disabled { + color: $u-error-disabled; +} + +.u-info-disabled { + color: $u-info-disabled; +} + +.u-primary { + color: $u-primary; +} + +.u-warning { + color: $u-warning; +} + +.u-success { + color: $u-success; +} + +.u-error { + color: $u-error; +} + +.u-info { + color: $u-info; +} + +.u-primary-bg { + background-color: $u-primary; +} + +.u-warning-bg { + background-color: $u-warning; +} + +.u-success-bg { + background-color: $u-success; +} + +.u-error-bg { + background-color: $u-error; +} + +.u-info-bg { + background-color: $u-info; +} + +.u-main-color { + color: $u-main-color; +} + +.u-content-color { + color: $u-content-color; +} + +.u-tips-color { + color: $u-tips-color; +} + +.u-light-color { + color: $u-light-color; +} diff --git a/uni_modules/uview-ui/libs/css/common.scss b/uni_modules/uview-ui/libs/css/common.scss new file mode 100644 index 0000000..11f1e53 --- /dev/null +++ b/uni_modules/uview-ui/libs/css/common.scss @@ -0,0 +1,97 @@ +// 超出行数,自动显示行尾省略号,最多5行 +// 来自uView的温馨提示:当您在控制台看到此报错,说明需要在App.vue的style标签加上【lang="scss"】 +@for $i from 1 through 5 { + .u-line-#{$i} { + /* #ifdef APP-NVUE */ + // nvue下,可以直接使用lines属性,这是weex特有样式 + lines: $i; + text-overflow: ellipsis; + overflow: hidden; + flex: 1; + /* #endif */ + + /* #ifndef APP-NVUE */ + // vue下,单行和多行显示省略号需要单独处理 + @if $i == '1' { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } @else { + display: -webkit-box!important; + overflow: hidden; + text-overflow: ellipsis; + word-break: break-all; + -webkit-line-clamp: $i; + -webkit-box-orient: vertical!important; + } + /* #endif */ + } +} + + +// 此处加上!important并非随意乱用,而是因为目前*.nvue页面编译到H5时, +// App.vue的样式会被uni-app的view元素的自带border属性覆盖,导致无效 +// 综上,这是uni-app的缺陷导致我们为了多端兼容,而必须要加上!important +// 移动端兼容性较好,直接使用0.5px去实现细边框,不使用伪元素形式实现 +.u-border { + border-width: 0.5px!important; + border-color: $u-border-color!important; + border-style: solid; +} + +.u-border-top { + border-top-width: 0.5px!important; + border-color: $u-border-color!important; + border-top-style: solid; +} + +.u-border-left { + border-left-width: 0.5px!important; + border-color: $u-border-color!important; + border-left-style: solid; +} + +.u-border-right { + border-right-width: 0.5px!important; + border-color: $u-border-color!important; + border-right-style: solid; +} + +.u-border-bottom { + border-bottom-width: 0.5px!important; + border-color: $u-border-color!important; + border-bottom-style: solid; +} + +.u-border-top-bottom { + border-top-width: 0.5px!important; + border-bottom-width: 0.5px!important; + border-color: $u-border-color!important; + border-top-style: solid; + border-bottom-style: solid; +} + +// 去除button的所有默认样式,让其表现跟普通的view、text元素一样 +.u-reset-button { + padding: 0; + background-color: transparent; + /* #ifndef APP-PLUS */ + font-size: inherit; + line-height: inherit; + color: inherit; + /* #endif */ + /* #ifdef APP-NVUE */ + border-width: 0; + /* #endif */ +} + +/* #ifndef APP-NVUE */ +.u-reset-button::after { + border: none; +} +/* #endif */ + +.u-hover-class { + opacity: 0.7; +} + diff --git a/uni_modules/uview-ui/libs/css/components.scss b/uni_modules/uview-ui/libs/css/components.scss new file mode 100644 index 0000000..766679e --- /dev/null +++ b/uni_modules/uview-ui/libs/css/components.scss @@ -0,0 +1,15 @@ +@import "./mixin.scss"; + +/* #ifndef APP-NVUE */ +// 由于uView是基于nvue环境进行开发的,此环境中普通元素默认为flex-direction: column; +// 所以在非nvue中,需要对元素进行重置为flex-direction: column; 否则可能会表现异常 +view, scroll-view, swiper-item { + display: flex; + flex-direction: column; + flex-shrink: 0; + flex-grow: 0; + flex-basis: auto; + align-items: stretch; + align-content: flex-start; +} +/* #endif */ diff --git a/uni_modules/uview-ui/libs/css/flex.scss b/uni_modules/uview-ui/libs/css/flex.scss new file mode 100644 index 0000000..6d61be9 --- /dev/null +++ b/uni_modules/uview-ui/libs/css/flex.scss @@ -0,0 +1,257 @@ +// .u-flex { +// @include vue-flex(row); +// } + +// .u-flex-x { +// @include vue-flex(row); +// } + +// .u-flex-y { +// @include vue-flex(column); +// } + +// .u-flex-xy-center { +// @include vue-flex(row); +// justify-content: center; +// align-items: center; +// } + +// .u-flex-x-center { +// @include vue-flex(row); +// justify-content: center; +// } + +// .u-flex-y-center { +// @include vue-flex(column); +// justify-content: center; +// } + + +// flex布局 +.u-flex, +.u-flex-row, +.u-flex-x { + @include flex; +} + +.u-flex-y, +.u-flex-column { + @include flex(column); +} + +.u-flex-x-center { + @include flex; + justify-content: center; +} + +.u-flex-xy-center { + @include flex; + justify-content: center; + align-items: center; +} + +.u-flex-y-center { + @include flex; + align-items: center; +} + +.u-flex-x-left { + @include flex; +} + +.u-flex-x-reverse, +.u-flex-row-reverse { + flex-direction: row-reverse; +} + +.u-flex-y-reverse, +.u-flex-column-reverse { + flex-direction: column-reverse; +} + +/* #ifndef APP-NVUE */ +// 此处为vue版本的简写,因为nvue不支持同时作用于两个类名的样式写法 +// nvue下只能写成class="u-flex-x u-flex-x-reverse的形式" +.u-flex.u-flex-reverse, +.u-flex-row.u-flex-reverse, +.u-flex-x.u-flex-reverse { + flex-direction: row-reverse; +} + +.u-flex-column.u-flex-reverse, +.u-flex-y.u-flex-reverse { + flex-direction: column-reverse; +} + +// 自动伸缩 +.u-flex-fill { + flex: 1 1 auto +} + +// 边界自动伸缩 +.u-margin-top-auto, +.u-m-t-auto { + margin-top: auto !important +} + +.u-margin-right-auto, +.u-m-r-auto { + margin-right: auto !important +} + +.u-margin-bottom-auto, +.u-m-b-auto { + margin-bottom: auto !important +} + +.u-margin-left-auto, +.u-m-l-auto { + margin-left: auto !important +} + +.u-margin-center-auto, +.u-m-c-auto { + margin-left: auto !important; + margin-right: auto !important +} + +.u-margin-middle-auto, +.u-m-m-auto { + margin-top: auto !important; + margin-bottom: auto !important +} +/* #endif */ + +// 换行 +.u-flex-wrap { + flex-wrap: wrap; +} + +// 反向换行 +.u-flex-wrap-reverse { + flex-wrap: wrap-reverse; +} + +// 主轴起点对齐 +.u-flex-start { + justify-content: flex-start +} + +// 主轴中间对齐 +.u-flex-center { + justify-content: center +} + +// 主轴终点对齐 +.u-flex-end { + justify-content: flex-end +} + +// 主轴等比间距 +.u-flex-between { + justify-content: space-between +} + +// 主轴均分间距 +.u-flex-around { + justify-content: space-around +} + +// 交叉轴起点对齐 +.u-flex-items-start { + align-items: flex-start +} + +// 交叉轴中间对齐 +.u-flex-items-center { + align-items: center +} + +// 交叉轴终点对齐 +.u-flex-items-end { + align-items: flex-end +} + +// 交叉轴第一行文字基线对齐 +.u-flex-items-baseline { + align-items: baseline +} + +// 交叉轴方向拉伸对齐 +.u-flex-items-stretch { + align-items: stretch +} + + +// 以下属于项目(子元素)的类 + +// 子元素交叉轴起点对齐 +.u-flex-self-start { + align-self: flex-start +} + +// 子元素交叉轴居中对齐 +.u-flex-self-center { + align-self: center +} + +// 子元素交叉轴终点对齐 +.u-flex-self-end { + align-self: flex-end +} + +// 子元素交叉轴第一行文字基线对齐 +.u-flex-self-baseline { + align-self: baseline +} + +// 子元素交叉轴方向拉伸对齐 +.u-flex-self-stretch { + align-self: stretch +} + +// 多轴交叉时的对齐方式 + +// 起点对齐 +.u-flex-content-start { + align-content: flex-start +} + +// 居中对齐 +.u-flex-content-center { + align-content: center +} + +// 终点对齐 +.u-flex-content-end { + align-content: flex-end +} + +// 两端对齐 +.u-flex-content-between { + align-content: space-between +} + +// 均分间距 +.u-flex-content-around { + align-content: space-around +} + +// 全部居中对齐 +.u-flex-middle { + justify-content: center; + align-items: center; + align-self: center; + align-content: center +} + +// 是否可以放大 +.u-flex-grow { + flex-grow: 1 +} + +// 是否可以缩小 +.u-flex-shrink { + flex-shrink: 1 +} + diff --git a/uni_modules/uview-ui/libs/css/h5.scss b/uni_modules/uview-ui/libs/css/h5.scss new file mode 100644 index 0000000..e69de29 diff --git a/uni_modules/uview-ui/libs/css/mixin.scss b/uni_modules/uview-ui/libs/css/mixin.scss new file mode 100644 index 0000000..7e35b3b --- /dev/null +++ b/uni_modules/uview-ui/libs/css/mixin.scss @@ -0,0 +1,8 @@ +// 通过scss的mixin功能,把原来需要写4行的css,变成一行 +// 目的是保持代码干净整洁,不至于在nvue下,到处都要写display:flex的条件编译 +@mixin flex($direction: row) { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: $direction; +} diff --git a/uni_modules/uview-ui/libs/css/mp.scss b/uni_modules/uview-ui/libs/css/mp.scss new file mode 100644 index 0000000..e69de29 diff --git a/uni_modules/uview-ui/libs/css/nvue.scss b/uni_modules/uview-ui/libs/css/nvue.scss new file mode 100644 index 0000000..e69de29 diff --git a/uni_modules/uview-ui/libs/css/vue.scss b/uni_modules/uview-ui/libs/css/vue.scss new file mode 100644 index 0000000..3ae4d29 --- /dev/null +++ b/uni_modules/uview-ui/libs/css/vue.scss @@ -0,0 +1,27 @@ +// 历遍生成4个方向的底部安全区 +@each $d in top, right, bottom, left { + .u-safe-area-inset-#{$d} { + padding-#{$d}: 0; + padding-#{$d}: constant(safe-area-inset-#{$d}); + padding-#{$d}: env(safe-area-inset-#{$d}); + } +} + +//提升H5端uni.toast()的层级,避免被uView的modal等遮盖 +/* #ifdef H5 */ +uni-toast { + z-index: 10090; +} +uni-toast .uni-toast { + z-index: 10090; +} +/* #endif */ + +// 隐藏scroll-view的滚动条 +::-webkit-scrollbar { + display: none; + width: 0 !important; + height: 0 !important; + -webkit-appearance: none; + background: transparent; +} \ No newline at end of file diff --git a/uni_modules/uview-ui/libs/function/colorGradient.js b/uni_modules/uview-ui/libs/function/colorGradient.js new file mode 100644 index 0000000..9727732 --- /dev/null +++ b/uni_modules/uview-ui/libs/function/colorGradient.js @@ -0,0 +1,134 @@ +/** + * 求两个颜色之间的渐变值 + * @param {string} startColor 开始的颜色 + * @param {string} endColor 结束的颜色 + * @param {number} step 颜色等分的份额 + * */ +function colorGradient(startColor = 'rgb(0, 0, 0)', endColor = 'rgb(255, 255, 255)', step = 10) { + const startRGB = hexToRgb(startColor, false) // 转换为rgb数组模式 + const startR = startRGB[0] + const startG = startRGB[1] + const startB = startRGB[2] + + const endRGB = hexToRgb(endColor, false) + const endR = endRGB[0] + const endG = endRGB[1] + const endB = endRGB[2] + + const sR = (endR - startR) / step // 总差值 + const sG = (endG - startG) / step + const sB = (endB - startB) / step + const colorArr = [] + for (let i = 0; i < step; i++) { + // 计算每一步的hex值 + let hex = rgbToHex(`rgb(${Math.round((sR * i + startR))},${Math.round((sG * i + startG))},${Math.round((sB + * i + startB))})`) + // 确保第一个颜色值为startColor的值 + if (i === 0) hex = rgbToHex(startColor) + // 确保最后一个颜色值为endColor的值 + if (i === step - 1) hex = rgbToHex(endColor) + colorArr.push(hex) + } + return colorArr +} + +// 将hex表示方式转换为rgb表示方式(这里返回rgb数组模式) +function hexToRgb(sColor, str = true) { + const reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/ + sColor = String(sColor).toLowerCase() + if (sColor && reg.test(sColor)) { + if (sColor.length === 4) { + let sColorNew = '#' + for (let i = 1; i < 4; i += 1) { + sColorNew += sColor.slice(i, i + 1).concat(sColor.slice(i, i + 1)) + } + sColor = sColorNew + } + // 处理六位的颜色值 + const sColorChange = [] + for (let i = 1; i < 7; i += 2) { + sColorChange.push(parseInt(`0x${sColor.slice(i, i + 2)}`)) + } + if (!str) { + return sColorChange + } + return `rgb(${sColorChange[0]},${sColorChange[1]},${sColorChange[2]})` + } if (/^(rgb|RGB)/.test(sColor)) { + const arr = sColor.replace(/(?:\(|\)|rgb|RGB)*/g, '').split(',') + return arr.map((val) => Number(val)) + } + return sColor +} + +// 将rgb表示方式转换为hex表示方式 +function rgbToHex(rgb) { + const _this = rgb + const reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/ + if (/^(rgb|RGB)/.test(_this)) { + const aColor = _this.replace(/(?:\(|\)|rgb|RGB)*/g, '').split(',') + let strHex = '#' + for (let i = 0; i < aColor.length; i++) { + let hex = Number(aColor[i]).toString(16) + hex = String(hex).length == 1 ? `${0}${hex}` : hex // 保证每个rgb的值为2位 + if (hex === '0') { + hex += hex + } + strHex += hex + } + if (strHex.length !== 7) { + strHex = _this + } + return strHex + } if (reg.test(_this)) { + const aNum = _this.replace(/#/, '').split('') + if (aNum.length === 6) { + return _this + } if (aNum.length === 3) { + let numHex = '#' + for (let i = 0; i < aNum.length; i += 1) { + numHex += (aNum[i] + aNum[i]) + } + return numHex + } + } else { + return _this + } +} + +/** +* JS颜色十六进制转换为rgb或rgba,返回的格式为 rgba(255,255,255,0.5)字符串 +* sHex为传入的十六进制的色值 +* alpha为rgba的透明度 +*/ +function colorToRgba(color, alpha) { + color = rgbToHex(color) + // 十六进制颜色值的正则表达式 + const reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/ + /* 16进制颜色转为RGB格式 */ + let sColor = String(color).toLowerCase() + if (sColor && reg.test(sColor)) { + if (sColor.length === 4) { + let sColorNew = '#' + for (let i = 1; i < 4; i += 1) { + sColorNew += sColor.slice(i, i + 1).concat(sColor.slice(i, i + 1)) + } + sColor = sColorNew + } + // 处理六位的颜色值 + const sColorChange = [] + for (let i = 1; i < 7; i += 2) { + sColorChange.push(parseInt(`0x${sColor.slice(i, i + 2)}`)) + } + // return sColorChange.join(',') + return `rgba(${sColorChange.join(',')},${alpha})` + } + + return sColor +} + +export default { + colorGradient, + hexToRgb, + rgbToHex, + colorToRgba +} diff --git a/uni_modules/uview-ui/libs/function/debounce.js b/uni_modules/uview-ui/libs/function/debounce.js new file mode 100644 index 0000000..ad3996b --- /dev/null +++ b/uni_modules/uview-ui/libs/function/debounce.js @@ -0,0 +1,29 @@ +let timeout = null + +/** + * 防抖原理:一定时间内,只有最后一次操作,再过wait毫秒后才执行函数 + * + * @param {Function} func 要执行的回调函数 + * @param {Number} wait 延时的时间 + * @param {Boolean} immediate 是否立即执行 + * @return null + */ +function debounce(func, wait = 500, immediate = false) { + // 清除定时器 + if (timeout !== null) clearTimeout(timeout) + // 立即执行,此类情况一般用不到 + if (immediate) { + const callNow = !timeout + timeout = setTimeout(() => { + timeout = null + }, wait) + if (callNow) typeof func === 'function' && func() + } else { + // 设置定时器,当最后一次操作后,timeout不会再被清除,所以在延时wait毫秒后执行func回调方法 + timeout = setTimeout(() => { + typeof func === 'function' && func() + }, wait) + } +} + +export default debounce diff --git a/uni_modules/uview-ui/libs/function/digit.js b/uni_modules/uview-ui/libs/function/digit.js new file mode 100644 index 0000000..c8260a0 --- /dev/null +++ b/uni_modules/uview-ui/libs/function/digit.js @@ -0,0 +1,167 @@ +let _boundaryCheckingState = true; // 是否进行越界检查的全局开关 + +/** + * 把错误的数据转正 + * @private + * @example strip(0.09999999999999998)=0.1 + */ +function strip(num, precision = 15) { + return +parseFloat(Number(num).toPrecision(precision)); +} + +/** + * Return digits length of a number + * @private + * @param {*number} num Input number + */ +function digitLength(num) { + // Get digit length of e + const eSplit = num.toString().split(/[eE]/); + const len = (eSplit[0].split('.')[1] || '').length - +(eSplit[1] || 0); + return len > 0 ? len : 0; +} + +/** + * 把小数转成整数,如果是小数则放大成整数 + * @private + * @param {*number} num 输入数 + */ +function float2Fixed(num) { + if (num.toString().indexOf('e') === -1) { + return Number(num.toString().replace('.', '')); + } + const dLen = digitLength(num); + return dLen > 0 ? strip(Number(num) * Math.pow(10, dLen)) : Number(num); +} + +/** + * 检测数字是否越界,如果越界给出提示 + * @private + * @param {*number} num 输入数 + */ +function checkBoundary(num) { + if (_boundaryCheckingState) { + if (num > Number.MAX_SAFE_INTEGER || num < Number.MIN_SAFE_INTEGER) { + console.warn(`${num} 超出了精度限制,结果可能不正确`); + } + } +} + +/** + * 把递归操作扁平迭代化 + * @param {number[]} arr 要操作的数字数组 + * @param {function} operation 迭代操作 + * @private + */ +function iteratorOperation(arr, operation) { + const [num1, num2, ...others] = arr; + let res = operation(num1, num2); + + others.forEach((num) => { + res = operation(res, num); + }); + + return res; +} + +/** + * 高精度乘法 + * @export + */ +export function times(...nums) { + if (nums.length > 2) { + return iteratorOperation(nums, times); + } + + const [num1, num2] = nums; + const num1Changed = float2Fixed(num1); + const num2Changed = float2Fixed(num2); + const baseNum = digitLength(num1) + digitLength(num2); + const leftValue = num1Changed * num2Changed; + + checkBoundary(leftValue); + + return leftValue / Math.pow(10, baseNum); +} + +/** + * 高精度加法 + * @export + */ +export function plus(...nums) { + if (nums.length > 2) { + return iteratorOperation(nums, plus); + } + + const [num1, num2] = nums; + // 取最大的小数位 + const baseNum = Math.pow(10, Math.max(digitLength(num1), digitLength(num2))); + // 把小数都转为整数然后再计算 + return (times(num1, baseNum) + times(num2, baseNum)) / baseNum; +} + +/** + * 高精度减法 + * @export + */ +export function minus(...nums) { + if (nums.length > 2) { + return iteratorOperation(nums, minus); + } + + const [num1, num2] = nums; + const baseNum = Math.pow(10, Math.max(digitLength(num1), digitLength(num2))); + return (times(num1, baseNum) - times(num2, baseNum)) / baseNum; +} + +/** + * 高精度除法 + * @export + */ +export function divide(...nums) { + if (nums.length > 2) { + return iteratorOperation(nums, divide); + } + + const [num1, num2] = nums; + const num1Changed = float2Fixed(num1); + const num2Changed = float2Fixed(num2); + checkBoundary(num1Changed); + checkBoundary(num2Changed); + // 重要,这里必须用strip进行修正 + return times(num1Changed / num2Changed, strip(Math.pow(10, digitLength(num2) - digitLength(num1)))); +} + +/** + * 四舍五入 + * @export + */ +export function round(num, ratio) { + const base = Math.pow(10, ratio); + let result = divide(Math.round(Math.abs(times(num, base))), base); + if (num < 0 && result !== 0) { + result = times(result, -1); + } + // 位数不足则补0 + return result; +} + +/** + * 是否进行边界检查,默认开启 + * @param flag 标记开关,true 为开启,false 为关闭,默认为 true + * @export + */ +export function enableBoundaryChecking(flag = true) { + _boundaryCheckingState = flag; +} + + +export default { + times, + plus, + minus, + divide, + round, + enableBoundaryChecking, +}; + diff --git a/uni_modules/uview-ui/libs/function/index.js b/uni_modules/uview-ui/libs/function/index.js new file mode 100644 index 0000000..bd80ee7 --- /dev/null +++ b/uni_modules/uview-ui/libs/function/index.js @@ -0,0 +1,731 @@ +import test from './test.js' +import { round } from './digit.js' +/** + * @description 如果value小于min,取min;如果value大于max,取max + * @param {number} min + * @param {number} max + * @param {number} value + */ +function range(min = 0, max = 0, value = 0) { + return Math.max(min, Math.min(max, Number(value))) +} + +/** + * @description 用于获取用户传递值的px值 如果用户传递了"xxpx"或者"xxrpx",取出其数值部分,如果是"xxxrpx"还需要用过uni.upx2px进行转换 + * @param {number|string} value 用户传递值的px值 + * @param {boolean} unit + * @returns {number|string} + */ +function getPx(value, unit = false) { + if (test.number(value)) { + return unit ? `${value}px` : Number(value) + } + // 如果带有rpx,先取出其数值部分,再转为px值 + if (/(rpx|upx)$/.test(value)) { + return unit ? `${uni.upx2px(parseInt(value))}px` : Number(uni.upx2px(parseInt(value))) + } + return unit ? `${parseInt(value)}px` : parseInt(value) +} + +/** + * @description 进行延时,以达到可以简写代码的目的 比如: await uni.$u.sleep(20)将会阻塞20ms + * @param {number} value 堵塞时间 单位ms 毫秒 + * @returns {Promise} 返回promise + */ +function sleep(value = 30) { + return new Promise((resolve) => { + setTimeout(() => { + resolve() + }, value) + }) +} +/** + * @description 运行期判断平台 + * @returns {string} 返回所在平台(小写) + * @link 运行期判断平台 https://uniapp.dcloud.io/frame?id=判断平台 + */ +function os() { + return uni.getSystemInfoSync().platform.toLowerCase() +} +/** + * @description 获取系统信息同步接口 + * @link 获取系统信息同步接口 https://uniapp.dcloud.io/api/system/info?id=getsysteminfosync + */ +function sys() { + return uni.getSystemInfoSync() +} + +/** + * @description 取一个区间数 + * @param {Number} min 最小值 + * @param {Number} max 最大值 + */ +function random(min, max) { + if (min >= 0 && max > 0 && max >= min) { + const gab = max - min + 1 + return Math.floor(Math.random() * gab + min) + } + return 0 +} + +/** + * @param {Number} len uuid的长度 + * @param {Boolean} firstU 将返回的首字母置为"u" + * @param {Nubmer} radix 生成uuid的基数(意味着返回的字符串都是这个基数),2-二进制,8-八进制,10-十进制,16-十六进制 + */ +function guid(len = 32, firstU = true, radix = null) { + const chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('') + const uuid = [] + radix = radix || chars.length + + if (len) { + // 如果指定uuid长度,只是取随机的字符,0|x为位运算,能去掉x的小数位,返回整数位 + for (let i = 0; i < len; i++) uuid[i] = chars[0 | Math.random() * radix] + } else { + let r + // rfc4122标准要求返回的uuid中,某些位为固定的字符 + uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-' + uuid[14] = '4' + + for (let i = 0; i < 36; i++) { + if (!uuid[i]) { + r = 0 | Math.random() * 16 + uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r] + } + } + } + // 移除第一个字符,并用u替代,因为第一个字符为数值时,该guuid不能用作id或者class + if (firstU) { + uuid.shift() + return `u${uuid.join('')}` + } + return uuid.join('') +} + +/** +* @description 获取父组件的参数,因为支付宝小程序不支持provide/inject的写法 + this.$parent在非H5中,可以准确获取到父组件,但是在H5中,需要多次this.$parent.$parent.xxx + 这里默认值等于undefined有它的含义,因为最顶层元素(组件)的$parent就是undefined,意味着不传name + 值(默认为undefined),就是查找最顶层的$parent +* @param {string|undefined} name 父组件的参数名 +*/ +function $parent(name = undefined) { + let parent = this.$parent + // 通过while历遍,这里主要是为了H5需要多层解析的问题 + while (parent) { + // 父组件 + if (parent.$options && parent.$options.name !== name) { + // 如果组件的name不相等,继续上一级寻找 + parent = parent.$parent + } else { + return parent + } + } + return false +} + +/** + * @description 样式转换 + * 对象转字符串,或者字符串转对象 + * @param {object | string} customStyle 需要转换的目标 + * @param {String} target 转换的目的,object-转为对象,string-转为字符串 + * @returns {object|string} + */ +function addStyle(customStyle, target = 'object') { + // 字符串转字符串,对象转对象情形,直接返回 + if (test.empty(customStyle) || typeof(customStyle) === 'object' && target === 'object' || target === 'string' && + typeof(customStyle) === 'string') { + return customStyle + } + // 字符串转对象 + if (target === 'object') { + // 去除字符串样式中的两端空格(中间的空格不能去掉,比如padding: 20px 0如果去掉了就错了),空格是无用的 + customStyle = trim(customStyle) + // 根据";"将字符串转为数组形式 + const styleArray = customStyle.split(';') + const style = {} + // 历遍数组,拼接成对象 + for (let i = 0; i < styleArray.length; i++) { + // 'font-size:20px;color:red;',如此最后字符串有";"的话,会导致styleArray最后一个元素为空字符串,这里需要过滤 + if (styleArray[i]) { + const item = styleArray[i].split(':') + style[trim(item[0])] = trim(item[1]) + } + } + return style + } + // 这里为对象转字符串形式 + let string = '' + for (const i in customStyle) { + // 驼峰转为中划线的形式,否则css内联样式,无法识别驼峰样式属性名 + const key = i.replace(/([A-Z])/g, '-$1').toLowerCase() + string += `${key}:${customStyle[i]};` + } + // 去除两端空格 + return trim(string) +} + +/** + * @description 添加单位,如果有rpx,upx,%,px等单位结尾或者值为auto,直接返回,否则加上px单位结尾 + * @param {string|number} value 需要添加单位的值 + * @param {string} unit 添加的单位名 比如px + */ +function addUnit(value = 'auto', unit = uni?.$u?.config?.unit ?? 'px') { + value = String(value) + // 用uView内置验证规则中的number判断是否为数值 + return test.number(value) ? `${value}${unit}` : value +} + +/** + * @description 深度克隆 + * @param {object} obj 需要深度克隆的对象 + * @param cache 缓存 + * @returns {*} 克隆后的对象或者原值(不是对象) + */ +function deepClone(obj, cache = new WeakMap()) { + if (obj === null || typeof obj !== 'object') return obj; + if (cache.has(obj)) return cache.get(obj); + let clone; + if (obj instanceof Date) { + clone = new Date(obj.getTime()); + } else if (obj instanceof RegExp) { + clone = new RegExp(obj); + } else if (obj instanceof Map) { + clone = new Map(Array.from(obj, ([key, value]) => [key, deepClone(value, cache)])); + } else if (obj instanceof Set) { + clone = new Set(Array.from(obj, value => deepClone(value, cache))); + } else if (Array.isArray(obj)) { + clone = obj.map(value => deepClone(value, cache)); + } else if (Object.prototype.toString.call(obj) === '[object Object]') { + clone = Object.create(Object.getPrototypeOf(obj)); + cache.set(obj, clone); + for (const [key, value] of Object.entries(obj)) { + clone[key] = deepClone(value, cache); + } + } else { + clone = Object.assign({}, obj); + } + cache.set(obj, clone); + return clone; +} + +/** + * @description JS对象深度合并 + * @param {object} target 需要拷贝的对象 + * @param {object} source 拷贝的来源对象 + * @returns {object|boolean} 深度合并后的对象或者false(入参有不是对象) + */ +function deepMerge(target = {}, source = {}) { + target = deepClone(target) + if (typeof target !== 'object' || target === null || typeof source !== 'object' || source === null) return target; + const merged = Array.isArray(target) ? target.slice() : Object.assign({}, target); + for (const prop in source) { + if (!source.hasOwnProperty(prop)) continue; + const sourceValue = source[prop]; + const targetValue = merged[prop]; + if (sourceValue instanceof Date) { + merged[prop] = new Date(sourceValue); + } else if (sourceValue instanceof RegExp) { + merged[prop] = new RegExp(sourceValue); + } else if (sourceValue instanceof Map) { + merged[prop] = new Map(sourceValue); + } else if (sourceValue instanceof Set) { + merged[prop] = new Set(sourceValue); + } else if (typeof sourceValue === 'object' && sourceValue !== null) { + merged[prop] = deepMerge(targetValue, sourceValue); + } else { + merged[prop] = sourceValue; + } + } + return merged; +} + +/** + * @description error提示 + * @param {*} err 错误内容 + */ +function error(err) { + // 开发环境才提示,生产环境不会提示 + if (process.env.NODE_ENV === 'development') { + console.error(`uView提示:${err}`) + } +} + +/** + * @description 打乱数组 + * @param {array} array 需要打乱的数组 + * @returns {array} 打乱后的数组 + */ +function randomArray(array = []) { + // 原理是sort排序,Math.random()产生0<= x < 1之间的数,会导致x-0.05大于或者小于0 + return array.sort(() => Math.random() - 0.5) +} + +// padStart 的 polyfill,因为某些机型或情况,还无法支持es7的padStart,比如电脑版的微信小程序 +// 所以这里做一个兼容polyfill的兼容处理 +if (!String.prototype.padStart) { + // 为了方便表示这里 fillString 用了ES6 的默认参数,不影响理解 + String.prototype.padStart = function(maxLength, fillString = ' ') { + if (Object.prototype.toString.call(fillString) !== '[object String]') { + throw new TypeError( + 'fillString must be String' + ) + } + const str = this + // 返回 String(str) 这里是为了使返回的值是字符串字面量,在控制台中更符合直觉 + if (str.length >= maxLength) return String(str) + + const fillLength = maxLength - str.length + let times = Math.ceil(fillLength / fillString.length) + while (times >>= 1) { + fillString += fillString + if (times === 1) { + fillString += fillString + } + } + return fillString.slice(0, fillLength) + str + } +} + +/** + * @description 格式化时间 + * @param {String|Number} dateTime 需要格式化的时间戳 + * @param {String} fmt 格式化规则 yyyy:mm:dd|yyyy:mm|yyyy年mm月dd日|yyyy年mm月dd日 hh时MM分等,可自定义组合 默认yyyy-mm-dd + * @returns {string} 返回格式化后的字符串 + */ + function timeFormat(dateTime = null, formatStr = 'yyyy-mm-dd') { + let date + // 若传入时间为假值,则取当前时间 + if (!dateTime) { + date = new Date() + } + // 若为unix秒时间戳,则转为毫秒时间戳(逻辑有点奇怪,但不敢改,以保证历史兼容) + else if (/^\d{10}$/.test(dateTime?.toString().trim())) { + date = new Date(dateTime * 1000) + } + // 若用户传入字符串格式时间戳,new Date无法解析,需做兼容 + else if (typeof dateTime === 'string' && /^\d+$/.test(dateTime.trim())) { + date = new Date(Number(dateTime)) + } + // 处理平台性差异,在Safari/Webkit中,new Date仅支持/作为分割符的字符串时间 + // 处理 '2022-07-10 01:02:03',跳过 '2022-07-10T01:02:03' + else if (typeof dateTime === 'string' && dateTime.includes('-') && !dateTime.includes('T')) { + date = new Date(dateTime.replace(/-/g, '/')) + } + // 其他都认为符合 RFC 2822 规范 + else { + date = new Date(dateTime) + } + + const timeSource = { + 'y': date.getFullYear().toString(), // 年 + 'm': (date.getMonth() + 1).toString().padStart(2, '0'), // 月 + 'd': date.getDate().toString().padStart(2, '0'), // 日 + 'h': date.getHours().toString().padStart(2, '0'), // 时 + 'M': date.getMinutes().toString().padStart(2, '0'), // 分 + 's': date.getSeconds().toString().padStart(2, '0') // 秒 + // 有其他格式化字符需求可以继续添加,必须转化成字符串 + } + + for (const key in timeSource) { + const [ret] = new RegExp(`${key}+`).exec(formatStr) || [] + if (ret) { + // 年可能只需展示两位 + const beginIndex = key === 'y' && ret.length === 2 ? 2 : 0 + formatStr = formatStr.replace(ret, timeSource[key].slice(beginIndex)) + } + } + + return formatStr +} + +/** + * @description 时间戳转为多久之前 + * @param {String|Number} timestamp 时间戳 + * @param {String|Boolean} format + * 格式化规则如果为时间格式字符串,超出一定时间范围,返回固定的时间格式; + * 如果为布尔值false,无论什么时间,都返回多久以前的格式 + * @returns {string} 转化后的内容 + */ +function timeFrom(timestamp = null, format = 'yyyy-mm-dd') { + if (timestamp == null) timestamp = Number(new Date()) + timestamp = parseInt(timestamp) + // 判断用户输入的时间戳是秒还是毫秒,一般前端js获取的时间戳是毫秒(13位),后端传过来的为秒(10位) + if (timestamp.toString().length == 10) timestamp *= 1000 + let timer = (new Date()).getTime() - timestamp + timer = parseInt(timer / 1000) + // 如果小于5分钟,则返回"刚刚",其他以此类推 + let tips = '' + switch (true) { + case timer < 300: + tips = '刚刚' + break + case timer >= 300 && timer < 3600: + tips = `${parseInt(timer / 60)}分钟前` + break + case timer >= 3600 && timer < 86400: + tips = `${parseInt(timer / 3600)}小时前` + break + case timer >= 86400 && timer < 2592000: + tips = `${parseInt(timer / 86400)}天前` + break + default: + // 如果format为false,则无论什么时间戳,都显示xx之前 + if (format === false) { + if (timer >= 2592000 && timer < 365 * 86400) { + tips = `${parseInt(timer / (86400 * 30))}个月前` + } else { + tips = `${parseInt(timer / (86400 * 365))}年前` + } + } else { + tips = timeFormat(timestamp, format) + } + } + return tips +} + +/** + * @description 去除空格 + * @param String str 需要去除空格的字符串 + * @param String pos both(左右)|left|right|all 默认both + */ +function trim(str, pos = 'both') { + str = String(str) + if (pos == 'both') { + return str.replace(/^\s+|\s+$/g, '') + } + if (pos == 'left') { + return str.replace(/^\s*/, '') + } + if (pos == 'right') { + return str.replace(/(\s*$)/g, '') + } + if (pos == 'all') { + return str.replace(/\s+/g, '') + } + return str +} + +/** + * @description 对象转url参数 + * @param {object} data,对象 + * @param {Boolean} isPrefix,是否自动加上"?" + * @param {string} arrayFormat 规则 indices|brackets|repeat|comma + */ +function queryParams(data = {}, isPrefix = true, arrayFormat = 'brackets') { + const prefix = isPrefix ? '?' : '' + const _result = [] + if (['indices', 'brackets', 'repeat', 'comma'].indexOf(arrayFormat) == -1) arrayFormat = 'brackets' + for (const key in data) { + const value = data[key] + // 去掉为空的参数 + if (['', undefined, null].indexOf(value) >= 0) { + continue + } + // 如果值为数组,另行处理 + if (value.constructor === Array) { + // e.g. {ids: [1, 2, 3]} + switch (arrayFormat) { + case 'indices': + // 结果: ids[0]=1&ids[1]=2&ids[2]=3 + for (let i = 0; i < value.length; i++) { + _result.push(`${key}[${i}]=${value[i]}`) + } + break + case 'brackets': + // 结果: ids[]=1&ids[]=2&ids[]=3 + value.forEach((_value) => { + _result.push(`${key}[]=${_value}`) + }) + break + case 'repeat': + // 结果: ids=1&ids=2&ids=3 + value.forEach((_value) => { + _result.push(`${key}=${_value}`) + }) + break + case 'comma': + // 结果: ids=1,2,3 + let commaStr = '' + value.forEach((_value) => { + commaStr += (commaStr ? ',' : '') + _value + }) + _result.push(`${key}=${commaStr}`) + break + default: + value.forEach((_value) => { + _result.push(`${key}[]=${_value}`) + }) + } + } else { + _result.push(`${key}=${value}`) + } + } + return _result.length ? prefix + _result.join('&') : '' +} + +/** + * 显示消息提示框 + * @param {String} title 提示的内容,长度与 icon 取值有关。 + * @param {Number} duration 提示的延迟时间,单位毫秒,默认:2000 + */ +function toast(title, duration = 2000) { + uni.showToast({ + title: String(title), + icon: 'none', + duration + }) +} + +/** + * @description 根据主题type值,获取对应的图标 + * @param {String} type 主题名称,primary|info|error|warning|success + * @param {boolean} fill 是否使用fill填充实体的图标 + */ +function type2icon(type = 'success', fill = false) { + // 如果非预置值,默认为success + if (['primary', 'info', 'error', 'warning', 'success'].indexOf(type) == -1) type = 'success' + let iconName = '' + // 目前(2019-12-12),info和primary使用同一个图标 + switch (type) { + case 'primary': + iconName = 'info-circle' + break + case 'info': + iconName = 'info-circle' + break + case 'error': + iconName = 'close-circle' + break + case 'warning': + iconName = 'error-circle' + break + case 'success': + iconName = 'checkmark-circle' + break + default: + iconName = 'checkmark-circle' + } + // 是否是实体类型,加上-fill,在icon组件库中,实体的类名是后面加-fill的 + if (fill) iconName += '-fill' + return iconName +} + +/** + * @description 数字格式化 + * @param {number|string} number 要格式化的数字 + * @param {number} decimals 保留几位小数 + * @param {string} decimalPoint 小数点符号 + * @param {string} thousandsSeparator 千分位符号 + * @returns {string} 格式化后的数字 + */ +function priceFormat(number, decimals = 0, decimalPoint = '.', thousandsSeparator = ',') { + number = (`${number}`).replace(/[^0-9+-Ee.]/g, '') + const n = !isFinite(+number) ? 0 : +number + const prec = !isFinite(+decimals) ? 0 : Math.abs(decimals) + const sep = (typeof thousandsSeparator === 'undefined') ? ',' : thousandsSeparator + const dec = (typeof decimalPoint === 'undefined') ? '.' : decimalPoint + let s = '' + + s = (prec ? round(n, prec) + '' : `${Math.round(n)}`).split('.') + const re = /(-?\d+)(\d{3})/ + while (re.test(s[0])) { + s[0] = s[0].replace(re, `$1${sep}$2`) + } + + if ((s[1] || '').length < prec) { + s[1] = s[1] || '' + s[1] += new Array(prec - s[1].length + 1).join('0') + } + return s.join(dec) +} + +/** + * @description 获取duration值 + * 如果带有ms或者s直接返回,如果大于一定值,认为是ms单位,小于一定值,认为是s单位 + * 比如以30位阈值,那么300大于30,可以理解为用户想要的是300ms,而不是想花300s去执行一个动画 + * @param {String|number} value 比如: "1s"|"100ms"|1|100 + * @param {boolean} unit 提示: 如果是false 默认返回number + * @return {string|number} + */ +function getDuration(value, unit = true) { + const valueNum = parseInt(value) + if (unit) { + if (/s$/.test(value)) return value + return value > 30 ? `${value}ms` : `${value}s` + } + if (/ms$/.test(value)) return valueNum + if (/s$/.test(value)) return valueNum > 30 ? valueNum : valueNum * 1000 + return valueNum +} + +/** + * @description 日期的月或日补零操作 + * @param {String} value 需要补零的值 + */ +function padZero(value) { + return `00${value}`.slice(-2) +} + +/** + * @description 在u-form的子组件内容发生变化,或者失去焦点时,尝试通知u-form执行校验方法 + * @param {*} instance + * @param {*} event + */ +function formValidate(instance, event) { + const formItem = uni.$u.$parent.call(instance, 'u-form-item') + const form = uni.$u.$parent.call(instance, 'u-form') + // 如果发生变化的input或者textarea等,其父组件中有u-form-item或者u-form等,就执行form的validate方法 + // 同时将form-item的pros传递给form,让其进行精确对象验证 + if (formItem && form) { + form.validateField(formItem.prop, () => {}, event) + } +} + +/** + * @description 获取某个对象下的属性,用于通过类似'a.b.c'的形式去获取一个对象的的属性的形式 + * @param {object} obj 对象 + * @param {string} key 需要获取的属性字段 + * @returns {*} + */ +function getProperty(obj, key) { + if (!obj) { + return + } + if (typeof key !== 'string' || key === '') { + return '' + } + if (key.indexOf('.') !== -1) { + const keys = key.split('.') + let firstObj = obj[keys[0]] || {} + + for (let i = 1; i < keys.length; i++) { + if (firstObj) { + firstObj = firstObj[keys[i]] + } + } + return firstObj + } + return obj[key] +} + +/** + * @description 设置对象的属性值,如果'a.b.c'的形式进行设置 + * @param {object} obj 对象 + * @param {string} key 需要设置的属性 + * @param {string} value 设置的值 + */ +function setProperty(obj, key, value) { + if (!obj) { + return + } + // 递归赋值 + const inFn = function(_obj, keys, v) { + // 最后一个属性key + if (keys.length === 1) { + _obj[keys[0]] = v + return + } + // 0~length-1个key + while (keys.length > 1) { + const k = keys[0] + if (!_obj[k] || (typeof _obj[k] !== 'object')) { + _obj[k] = {} + } + const key = keys.shift() + // 自调用判断是否存在属性,不存在则自动创建对象 + inFn(_obj[k], keys, v) + } + } + + if (typeof key !== 'string' || key === '') { + + } else if (key.indexOf('.') !== -1) { // 支持多层级赋值操作 + const keys = key.split('.') + inFn(obj, keys, value) + } else { + obj[key] = value + } +} + +/** + * @description 获取当前页面路径 + */ +function page() { + const pages = getCurrentPages() + // 某些特殊情况下(比如页面进行redirectTo时的一些时机),pages可能为空数组 + return `/${pages[pages.length - 1]?.route ?? ''}` +} + +/** + * @description 获取当前路由栈实例数组 + */ +function pages() { + const pages = getCurrentPages() + return pages +} + +/** + * 获取页面历史栈指定层实例 + * @param back {number} [0] - 0或者负数,表示获取历史栈的哪一层,0表示获取当前页面实例,-1 表示获取上一个页面实例。默认0。 + */ +function getHistoryPage(back = 0) { + const pages = getCurrentPages() + const len = pages.length + return pages[len - 1 + back] +} + +/** + * @description 修改uView内置属性值 + * @param {object} props 修改内置props属性 + * @param {object} config 修改内置config属性 + * @param {object} color 修改内置color属性 + * @param {object} zIndex 修改内置zIndex属性 + */ +function setConfig({ + props = {}, + config = {}, + color = {}, + zIndex = {} +}) { + const { + deepMerge, + } = uni.$u + uni.$u.config = deepMerge(uni.$u.config, config) + uni.$u.props = deepMerge(uni.$u.props, props) + uni.$u.color = deepMerge(uni.$u.color, color) + uni.$u.zIndex = deepMerge(uni.$u.zIndex, zIndex) +} + +export default { + range, + getPx, + sleep, + os, + sys, + random, + guid, + $parent, + addStyle, + addUnit, + deepClone, + deepMerge, + error, + randomArray, + timeFormat, + timeFrom, + trim, + queryParams, + toast, + type2icon, + priceFormat, + getDuration, + padZero, + formValidate, + getProperty, + setProperty, + page, + pages, + getHistoryPage, + setConfig +} diff --git a/uni_modules/uview-ui/libs/function/platform.js b/uni_modules/uview-ui/libs/function/platform.js new file mode 100644 index 0000000..d6b926e --- /dev/null +++ b/uni_modules/uview-ui/libs/function/platform.js @@ -0,0 +1,75 @@ +/** + * 注意: + * 此部分内容,在vue-cli模式下,需要在vue.config.js加入如下内容才有效: + * module.exports = { + * transpileDependencies: ['uview-v2'] + * } + */ + +let platform = 'none' + +// #ifdef VUE3 +platform = 'vue3' +// #endif + +// #ifdef VUE2 +platform = 'vue2' +// #endif + +// #ifdef APP-PLUS +platform = 'plus' +// #endif + +// #ifdef APP-NVUE +platform = 'nvue' +// #endif + +// #ifdef H5 +platform = 'h5' +// #endif + +// #ifdef MP-WEIXIN +platform = 'weixin' +// #endif + +// #ifdef MP-ALIPAY +platform = 'alipay' +// #endif + +// #ifdef MP-BAIDU +platform = 'baidu' +// #endif + +// #ifdef MP-TOUTIAO +platform = 'toutiao' +// #endif + +// #ifdef MP-QQ +platform = 'qq' +// #endif + +// #ifdef MP-KUAISHOU +platform = 'kuaishou' +// #endif + +// #ifdef MP-360 +platform = '360' +// #endif + +// #ifdef MP +platform = 'mp' +// #endif + +// #ifdef QUICKAPP-WEBVIEW +platform = 'quickapp-webview' +// #endif + +// #ifdef QUICKAPP-WEBVIEW-HUAWEI +platform = 'quickapp-webview-huawei' +// #endif + +// #ifdef QUICKAPP-WEBVIEW-UNION +platform = 'quckapp-webview-union' +// #endif + +export default platform diff --git a/uni_modules/uview-ui/libs/function/test.js b/uni_modules/uview-ui/libs/function/test.js new file mode 100644 index 0000000..c776437 --- /dev/null +++ b/uni_modules/uview-ui/libs/function/test.js @@ -0,0 +1,288 @@ +/** + * 验证电子邮箱格式 + */ +function email(value) { + return /^\w+((-\w+)|(\.\w+))*\@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+$/.test(value) +} + +/** + * 验证手机格式 + */ +function mobile(value) { + return /^1([3589]\d|4[5-9]|6[1-2,4-7]|7[0-8])\d{8}$/.test(value) +} + +/** + * 验证URL格式 + */ +function url(value) { + return /^((https|http|ftp|rtsp|mms):\/\/)(([0-9a-zA-Z_!~*'().&=+$%-]+: )?[0-9a-zA-Z_!~*'().&=+$%-]+@)?(([0-9]{1,3}.){3}[0-9]{1,3}|([0-9a-zA-Z_!~*'()-]+.)*([0-9a-zA-Z][0-9a-zA-Z-]{0,61})?[0-9a-zA-Z].[a-zA-Z]{2,6})(:[0-9]{1,4})?((\/?)|(\/[0-9a-zA-Z_!~*'().;?:@&=+$,%#-]+)+\/?)$/ + .test(value) +} + +/** + * 验证日期格式 + */ +function date(value) { + if (!value) return false + // 判断是否数值或者字符串数值(意味着为时间戳),转为数值,否则new Date无法识别字符串时间戳 + if (number(value)) value = +value + return !/Invalid|NaN/.test(new Date(value).toString()) +} + +/** + * 验证ISO类型的日期格式 + */ +function dateISO(value) { + return /^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])$/.test(value) +} + +/** + * 验证十进制数字 + */ +function number(value) { + return /^[\+-]?(\d+\.?\d*|\.\d+|\d\.\d+e\+\d+)$/.test(value) +} + +/** + * 验证字符串 + */ +function string(value) { + return typeof value === 'string' +} + +/** + * 验证整数 + */ +function digits(value) { + return /^\d+$/.test(value) +} + +/** + * 验证身份证号码 + */ +function idCard(value) { + return /^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}([0-9]|X)$/.test( + value + ) +} + +/** + * 是否车牌号 + */ +function carNo(value) { + // 新能源车牌 + const xreg = /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}(([0-9]{5}[DF]$)|([DF][A-HJ-NP-Z0-9][0-9]{4}$))/ + // 旧车牌 + const creg = /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}[A-HJ-NP-Z0-9]{4}[A-HJ-NP-Z0-9挂学警港澳]{1}$/ + if (value.length === 7) { + return creg.test(value) + } if (value.length === 8) { + return xreg.test(value) + } + return false +} + +/** + * 金额,只允许2位小数 + */ +function amount(value) { + // 金额,只允许保留两位小数 + return /^[1-9]\d*(,\d{3})*(\.\d{1,2})?$|^0\.\d{1,2}$/.test(value) +} + +/** + * 中文 + */ +function chinese(value) { + const reg = /^[\u4e00-\u9fa5]+$/gi + return reg.test(value) +} + +/** + * 只能输入字母 + */ +function letter(value) { + return /^[a-zA-Z]*$/.test(value) +} + +/** + * 只能是字母或者数字 + */ +function enOrNum(value) { + // 英文或者数字 + const reg = /^[0-9a-zA-Z]*$/g + return reg.test(value) +} + +/** + * 验证是否包含某个值 + */ +function contains(value, param) { + return value.indexOf(param) >= 0 +} + +/** + * 验证一个值范围[min, max] + */ +function range(value, param) { + return value >= param[0] && value <= param[1] +} + +/** + * 验证一个长度范围[min, max] + */ +function rangeLength(value, param) { + return value.length >= param[0] && value.length <= param[1] +} + +/** + * 是否固定电话 + */ +function landline(value) { + const reg = /^\d{3,4}-\d{7,8}(-\d{3,4})?$/ + return reg.test(value) +} + +/** + * 判断是否为空 + */ +function empty(value) { + switch (typeof value) { + case 'undefined': + return true + case 'string': + if (value.replace(/(^[ \t\n\r]*)|([ \t\n\r]*$)/g, '').length == 0) return true + break + case 'boolean': + if (!value) return true + break + case 'number': + if (value === 0 || isNaN(value)) return true + break + case 'object': + if (value === null || value.length === 0) return true + for (const i in value) { + return false + } + return true + } + return false +} + +/** + * 是否json字符串 + */ +function jsonString(value) { + if (typeof value === 'string') { + try { + const obj = JSON.parse(value) + if (typeof obj === 'object' && obj) { + return true + } + return false + } catch (e) { + return false + } + } + return false +} + +/** + * 是否数组 + */ +function array(value) { + if (typeof Array.isArray === 'function') { + return Array.isArray(value) + } + return Object.prototype.toString.call(value) === '[object Array]' +} + +/** + * 是否对象 + */ +function object(value) { + return Object.prototype.toString.call(value) === '[object Object]' +} + +/** + * 是否短信验证码 + */ +function code(value, len = 6) { + return new RegExp(`^\\d{${len}}$`).test(value) +} + +/** + * 是否函数方法 + * @param {Object} value + */ +function func(value) { + return typeof value === 'function' +} + +/** + * 是否promise对象 + * @param {Object} value + */ +function promise(value) { + return object(value) && func(value.then) && func(value.catch) +} + +/** 是否图片格式 + * @param {Object} value + */ +function image(value) { + const newValue = value.split('?')[0] + const IMAGE_REGEXP = /\.(jpeg|jpg|gif|png|svg|webp|jfif|bmp|dpg)/i + return IMAGE_REGEXP.test(newValue) +} + +/** + * 是否视频格式 + * @param {Object} value + */ +function video(value) { + const VIDEO_REGEXP = /\.(mp4|mpg|mpeg|dat|asf|avi|rm|rmvb|mov|wmv|flv|mkv|m3u8)/i + return VIDEO_REGEXP.test(value) +} + +/** + * 是否为正则对象 + * @param {Object} + * @return {Boolean} + */ +function regExp(o) { + return o && Object.prototype.toString.call(o) === '[object RegExp]' +} + +export default { + email, + mobile, + url, + date, + dateISO, + number, + digits, + idCard, + carNo, + amount, + chinese, + letter, + enOrNum, + contains, + range, + rangeLength, + empty, + isEmpty: empty, + jsonString, + landline, + object, + array, + code, + func, + promise, + video, + image, + regExp, + string +} diff --git a/uni_modules/uview-ui/libs/function/throttle.js b/uni_modules/uview-ui/libs/function/throttle.js new file mode 100644 index 0000000..2f33611 --- /dev/null +++ b/uni_modules/uview-ui/libs/function/throttle.js @@ -0,0 +1,30 @@ +let timer; let + flag +/** + * 节流原理:在一定时间内,只能触发一次 + * + * @param {Function} func 要执行的回调函数 + * @param {Number} wait 延时的时间 + * @param {Boolean} immediate 是否立即执行 + * @return null + */ +function throttle(func, wait = 500, immediate = true) { + if (immediate) { + if (!flag) { + flag = true + // 如果是立即执行,则在wait毫秒内开始时执行 + typeof func === 'function' && func() + timer = setTimeout(() => { + flag = false + }, wait) + } + } else if (!flag) { + flag = true + // 如果是非立即执行,则在wait毫秒内的结束处执行 + timer = setTimeout(() => { + flag = false + typeof func === 'function' && func() + }, wait) + } +} +export default throttle diff --git a/uni_modules/uview-ui/libs/luch-request/adapters/index.js b/uni_modules/uview-ui/libs/luch-request/adapters/index.js new file mode 100644 index 0000000..e03cf5f --- /dev/null +++ b/uni_modules/uview-ui/libs/luch-request/adapters/index.js @@ -0,0 +1,97 @@ +import buildURL from '../helpers/buildURL' +import buildFullPath from '../core/buildFullPath' +import settle from '../core/settle' +import { isUndefined } from '../utils' + +/** + * 返回可选值存在的配置 + * @param {Array} keys - 可选值数组 + * @param {Object} config2 - 配置 + * @return {{}} - 存在的配置项 + */ +const mergeKeys = (keys, config2) => { + const config = {} + keys.forEach((prop) => { + if (!isUndefined(config2[prop])) { + config[prop] = config2[prop] + } + }) + return config +} +export default (config) => new Promise((resolve, reject) => { + const fullPath = buildURL(buildFullPath(config.baseURL, config.url), config.params) + const _config = { + url: fullPath, + header: config.header, + complete: (response) => { + config.fullPath = fullPath + response.config = config + try { + // 对可能字符串不是json 的情况容错 + if (typeof response.data === 'string') { + response.data = JSON.parse(response.data) + } + // eslint-disable-next-line no-empty + } catch (e) { + } + settle(resolve, reject, response) + } + } + let requestTask + if (config.method === 'UPLOAD') { + delete _config.header['content-type'] + delete _config.header['Content-Type'] + const otherConfig = { + // #ifdef MP-ALIPAY + fileType: config.fileType, + // #endif + filePath: config.filePath, + name: config.name + } + const optionalKeys = [ + // #ifdef APP-PLUS || H5 + 'files', + // #endif + // #ifdef H5 + 'file', + // #endif + // #ifdef H5 || APP-PLUS + 'timeout', + // #endif + 'formData' + ] + requestTask = uni.uploadFile({ ..._config, ...otherConfig, ...mergeKeys(optionalKeys, config) }) + } else if (config.method === 'DOWNLOAD') { + // #ifdef H5 || APP-PLUS + if (!isUndefined(config.timeout)) { + _config.timeout = config.timeout + } + // #endif + requestTask = uni.downloadFile(_config) + } else { + const optionalKeys = [ + 'data', + 'method', + // #ifdef H5 || APP-PLUS || MP-ALIPAY || MP-WEIXIN + 'timeout', + // #endif + 'dataType', + // #ifndef MP-ALIPAY + 'responseType', + // #endif + // #ifdef APP-PLUS + 'sslVerify', + // #endif + // #ifdef H5 + 'withCredentials', + // #endif + // #ifdef APP-PLUS + 'firstIpv4' + // #endif + ] + requestTask = uni.request({ ..._config, ...mergeKeys(optionalKeys, config) }) + } + if (config.getTask) { + config.getTask(requestTask, config) + } +}) diff --git a/uni_modules/uview-ui/libs/luch-request/core/InterceptorManager.js b/uni_modules/uview-ui/libs/luch-request/core/InterceptorManager.js new file mode 100644 index 0000000..3e8728d --- /dev/null +++ b/uni_modules/uview-ui/libs/luch-request/core/InterceptorManager.js @@ -0,0 +1,50 @@ +'use strict' + +function InterceptorManager() { + this.handlers = [] +} + +/** + * Add a new interceptor to the stack + * + * @param {Function} fulfilled The function to handle `then` for a `Promise` + * @param {Function} rejected The function to handle `reject` for a `Promise` + * + * @return {Number} An ID used to remove interceptor later + */ +InterceptorManager.prototype.use = function use(fulfilled, rejected) { + this.handlers.push({ + fulfilled, + rejected + }) + return this.handlers.length - 1 +} + +/** + * Remove an interceptor from the stack + * + * @param {Number} id The ID that was returned by `use` + */ +InterceptorManager.prototype.eject = function eject(id) { + if (this.handlers[id]) { + this.handlers[id] = null + } +} + +/** + * Iterate over all the registered interceptors + * + * This method is particularly useful for skipping over any + * interceptors that may have become `null` calling `eject`. + * + * @param {Function} fn The function to call for each interceptor + */ +InterceptorManager.prototype.forEach = function forEach(fn) { + this.handlers.forEach((h) => { + if (h !== null) { + fn(h) + } + }) +} + +export default InterceptorManager diff --git a/uni_modules/uview-ui/libs/luch-request/core/Request.js b/uni_modules/uview-ui/libs/luch-request/core/Request.js new file mode 100644 index 0000000..cc48566 --- /dev/null +++ b/uni_modules/uview-ui/libs/luch-request/core/Request.js @@ -0,0 +1,198 @@ +/** + * @Class Request + * @description luch-request http请求插件 + * @version 3.0.7 + * @Author lu-ch + * @Date 2021-09-04 + * @Email webwork.s@qq.com + * 文档: https://www.quanzhan.co/luch-request/ + * github: https://github.com/lei-mu/luch-request + * DCloud: http://ext.dcloud.net.cn/plugin?id=392 + * HBuilderX: beat-3.0.4 alpha-3.0.4 + */ + +import dispatchRequest from './dispatchRequest' +import InterceptorManager from './InterceptorManager' +import mergeConfig from './mergeConfig' +import defaults from './defaults' +import { isPlainObject } from '../utils' +import clone from '../utils/clone' + +export default class Request { + /** + * @param {Object} arg - 全局配置 + * @param {String} arg.baseURL - 全局根路径 + * @param {Object} arg.header - 全局header + * @param {String} arg.method = [GET|POST|PUT|DELETE|CONNECT|HEAD|OPTIONS|TRACE] - 全局默认请求方式 + * @param {String} arg.dataType = [json] - 全局默认的dataType + * @param {String} arg.responseType = [text|arraybuffer] - 全局默认的responseType。支付宝小程序不支持 + * @param {Object} arg.custom - 全局默认的自定义参数 + * @param {Number} arg.timeout - 全局默认的超时时间,单位 ms。默认60000。H5(HBuilderX 2.9.9+)、APP(HBuilderX 2.9.9+)、微信小程序(2.10.0)、支付宝小程序 + * @param {Boolean} arg.sslVerify - 全局默认的是否验证 ssl 证书。默认true.仅App安卓端支持(HBuilderX 2.3.3+) + * @param {Boolean} arg.withCredentials - 全局默认的跨域请求时是否携带凭证(cookies)。默认false。仅H5支持(HBuilderX 2.6.15+) + * @param {Boolean} arg.firstIpv4 - 全DNS解析时优先使用ipv4。默认false。仅 App-Android 支持 (HBuilderX 2.8.0+) + * @param {Function(statusCode):Boolean} arg.validateStatus - 全局默认的自定义验证器。默认statusCode >= 200 && statusCode < 300 + */ + constructor(arg = {}) { + if (!isPlainObject(arg)) { + arg = {} + console.warn('设置全局参数必须接收一个Object') + } + this.config = clone({ ...defaults, ...arg }) + this.interceptors = { + request: new InterceptorManager(), + response: new InterceptorManager() + } + } + + /** + * @Function + * @param {Request~setConfigCallback} f - 设置全局默认配置 + */ + setConfig(f) { + this.config = f(this.config) + } + + middleware(config) { + config = mergeConfig(this.config, config) + const chain = [dispatchRequest, undefined] + let promise = Promise.resolve(config) + + this.interceptors.request.forEach((interceptor) => { + chain.unshift(interceptor.fulfilled, interceptor.rejected) + }) + + this.interceptors.response.forEach((interceptor) => { + chain.push(interceptor.fulfilled, interceptor.rejected) + }) + + while (chain.length) { + promise = promise.then(chain.shift(), chain.shift()) + } + + return promise + } + + /** + * @Function + * @param {Object} config - 请求配置项 + * @prop {String} options.url - 请求路径 + * @prop {Object} options.data - 请求参数 + * @prop {Object} [options.responseType = config.responseType] [text|arraybuffer] - 响应的数据类型 + * @prop {Object} [options.dataType = config.dataType] - 如果设为 json,会尝试对返回的数据做一次 JSON.parse + * @prop {Object} [options.header = config.header] - 请求header + * @prop {Object} [options.method = config.method] - 请求方法 + * @returns {Promise} + */ + request(config = {}) { + return this.middleware(config) + } + + get(url, options = {}) { + return this.middleware({ + url, + method: 'GET', + ...options + }) + } + + post(url, data, options = {}) { + return this.middleware({ + url, + data, + method: 'POST', + ...options + }) + } + + // #ifndef MP-ALIPAY + put(url, data, options = {}) { + return this.middleware({ + url, + data, + method: 'PUT', + ...options + }) + } + + // #endif + + // #ifdef APP-PLUS || H5 || MP-WEIXIN || MP-BAIDU + delete(url, data, options = {}) { + return this.middleware({ + url, + data, + method: 'DELETE', + ...options + }) + } + + // #endif + + // #ifdef H5 || MP-WEIXIN + connect(url, data, options = {}) { + return this.middleware({ + url, + data, + method: 'CONNECT', + ...options + }) + } + + // #endif + + // #ifdef H5 || MP-WEIXIN || MP-BAIDU + head(url, data, options = {}) { + return this.middleware({ + url, + data, + method: 'HEAD', + ...options + }) + } + + // #endif + + // #ifdef APP-PLUS || H5 || MP-WEIXIN || MP-BAIDU + options(url, data, options = {}) { + return this.middleware({ + url, + data, + method: 'OPTIONS', + ...options + }) + } + + // #endif + + // #ifdef H5 || MP-WEIXIN + trace(url, data, options = {}) { + return this.middleware({ + url, + data, + method: 'TRACE', + ...options + }) + } + + // #endif + + upload(url, config = {}) { + config.url = url + config.method = 'UPLOAD' + return this.middleware(config) + } + + download(url, config = {}) { + config.url = url + config.method = 'DOWNLOAD' + return this.middleware(config) + } +} + +/** + * setConfig回调 + * @return {Object} - 返回操作后的config + * @callback Request~setConfigCallback + * @param {Object} config - 全局默认config + */ diff --git a/uni_modules/uview-ui/libs/luch-request/core/buildFullPath.js b/uni_modules/uview-ui/libs/luch-request/core/buildFullPath.js new file mode 100644 index 0000000..5eb8a17 --- /dev/null +++ b/uni_modules/uview-ui/libs/luch-request/core/buildFullPath.js @@ -0,0 +1,20 @@ +'use strict' + +import isAbsoluteURL from '../helpers/isAbsoluteURL' +import combineURLs from '../helpers/combineURLs' + +/** + * Creates a new URL by combining the baseURL with the requestedURL, + * only when the requestedURL is not already an absolute URL. + * If the requestURL is absolute, this function returns the requestedURL untouched. + * + * @param {string} baseURL The base URL + * @param {string} requestedURL Absolute or relative URL to combine + * @returns {string} The combined full path + */ +export default function buildFullPath(baseURL, requestedURL) { + if (baseURL && !isAbsoluteURL(requestedURL)) { + return combineURLs(baseURL, requestedURL) + } + return requestedURL +} diff --git a/uni_modules/uview-ui/libs/luch-request/core/defaults.js b/uni_modules/uview-ui/libs/luch-request/core/defaults.js new file mode 100644 index 0000000..be375a9 --- /dev/null +++ b/uni_modules/uview-ui/libs/luch-request/core/defaults.js @@ -0,0 +1,29 @@ +/** + * 默认的全局配置 + */ + +export default { + baseURL: '', + header: {}, + method: 'GET', + dataType: 'json', + // #ifndef MP-ALIPAY + responseType: 'text', + // #endif + custom: {}, + // #ifdef H5 || APP-PLUS || MP-ALIPAY || MP-WEIXIN + timeout: 60000, + // #endif + // #ifdef APP-PLUS + sslVerify: true, + // #endif + // #ifdef H5 + withCredentials: false, + // #endif + // #ifdef APP-PLUS + firstIpv4: false, + // #endif + validateStatus: function validateStatus(status) { + return status >= 200 && status < 300 + } +} diff --git a/uni_modules/uview-ui/libs/luch-request/core/dispatchRequest.js b/uni_modules/uview-ui/libs/luch-request/core/dispatchRequest.js new file mode 100644 index 0000000..724545c --- /dev/null +++ b/uni_modules/uview-ui/libs/luch-request/core/dispatchRequest.js @@ -0,0 +1,3 @@ +import adapter from '../adapters/index' + +export default (config) => adapter(config) diff --git a/uni_modules/uview-ui/libs/luch-request/core/mergeConfig.js b/uni_modules/uview-ui/libs/luch-request/core/mergeConfig.js new file mode 100644 index 0000000..08f8b9b --- /dev/null +++ b/uni_modules/uview-ui/libs/luch-request/core/mergeConfig.js @@ -0,0 +1,103 @@ +import { deepMerge, isUndefined } from '../utils' + +/** + * 合并局部配置优先的配置,如果局部有该配置项则用局部,如果全局有该配置项则用全局 + * @param {Array} keys - 配置项 + * @param {Object} globalsConfig - 当前的全局配置 + * @param {Object} config2 - 局部配置 + * @return {{}} + */ +const mergeKeys = (keys, globalsConfig, config2) => { + const config = {} + keys.forEach((prop) => { + if (!isUndefined(config2[prop])) { + config[prop] = config2[prop] + } else if (!isUndefined(globalsConfig[prop])) { + config[prop] = globalsConfig[prop] + } + }) + return config +} +/** + * + * @param globalsConfig - 当前实例的全局配置 + * @param config2 - 当前的局部配置 + * @return - 合并后的配置 + */ +export default (globalsConfig, config2 = {}) => { + const method = config2.method || globalsConfig.method || 'GET' + let config = { + baseURL: globalsConfig.baseURL || '', + method, + url: config2.url || '', + params: config2.params || {}, + custom: { ...(globalsConfig.custom || {}), ...(config2.custom || {}) }, + header: deepMerge(globalsConfig.header || {}, config2.header || {}) + } + const defaultToConfig2Keys = ['getTask', 'validateStatus'] + config = { ...config, ...mergeKeys(defaultToConfig2Keys, globalsConfig, config2) } + + // eslint-disable-next-line no-empty + if (method === 'DOWNLOAD') { + // #ifdef H5 || APP-PLUS + if (!isUndefined(config2.timeout)) { + config.timeout = config2.timeout + } else if (!isUndefined(globalsConfig.timeout)) { + config.timeout = globalsConfig.timeout + } + // #endif + } else if (method === 'UPLOAD') { + delete config.header['content-type'] + delete config.header['Content-Type'] + const uploadKeys = [ + // #ifdef APP-PLUS || H5 + 'files', + // #endif + // #ifdef MP-ALIPAY + 'fileType', + // #endif + // #ifdef H5 + 'file', + // #endif + 'filePath', + 'name', + // #ifdef H5 || APP-PLUS + 'timeout', + // #endif + 'formData' + ] + uploadKeys.forEach((prop) => { + if (!isUndefined(config2[prop])) { + config[prop] = config2[prop] + } + }) + // #ifdef H5 || APP-PLUS + if (isUndefined(config.timeout) && !isUndefined(globalsConfig.timeout)) { + config.timeout = globalsConfig.timeout + } + // #endif + } else { + const defaultsKeys = [ + 'data', + // #ifdef H5 || APP-PLUS || MP-ALIPAY || MP-WEIXIN + 'timeout', + // #endif + 'dataType', + // #ifndef MP-ALIPAY + 'responseType', + // #endif + // #ifdef APP-PLUS + 'sslVerify', + // #endif + // #ifdef H5 + 'withCredentials', + // #endif + // #ifdef APP-PLUS + 'firstIpv4' + // #endif + ] + config = { ...config, ...mergeKeys(defaultsKeys, globalsConfig, config2) } + } + + return config +} diff --git a/uni_modules/uview-ui/libs/luch-request/core/settle.js b/uni_modules/uview-ui/libs/luch-request/core/settle.js new file mode 100644 index 0000000..8d3638f --- /dev/null +++ b/uni_modules/uview-ui/libs/luch-request/core/settle.js @@ -0,0 +1,16 @@ +/** + * Resolve or reject a Promise based on response status. + * + * @param {Function} resolve A function that resolves the promise. + * @param {Function} reject A function that rejects the promise. + * @param {object} response The response. + */ +export default function settle(resolve, reject, response) { + const { validateStatus } = response.config + const status = response.statusCode + if (status && (!validateStatus || validateStatus(status))) { + resolve(response) + } else { + reject(response) + } +} diff --git a/uni_modules/uview-ui/libs/luch-request/helpers/buildURL.js b/uni_modules/uview-ui/libs/luch-request/helpers/buildURL.js new file mode 100644 index 0000000..472ad6a --- /dev/null +++ b/uni_modules/uview-ui/libs/luch-request/helpers/buildURL.js @@ -0,0 +1,69 @@ +'use strict' + +import * as utils from '../utils' + +function encode(val) { + return encodeURIComponent(val) + .replace(/%40/gi, '@') + .replace(/%3A/gi, ':') + .replace(/%24/g, '$') + .replace(/%2C/gi, ',') + .replace(/%20/g, '+') + .replace(/%5B/gi, '[') + .replace(/%5D/gi, ']') +} + +/** + * Build a URL by appending params to the end + * + * @param {string} url The base of the url (e.g., http://www.google.com) + * @param {object} [params] The params to be appended + * @returns {string} The formatted url + */ +export default function buildURL(url, params) { + /* eslint no-param-reassign:0 */ + if (!params) { + return url + } + + let serializedParams + if (utils.isURLSearchParams(params)) { + serializedParams = params.toString() + } else { + const parts = [] + + utils.forEach(params, (val, key) => { + if (val === null || typeof val === 'undefined') { + return + } + + if (utils.isArray(val)) { + key = `${key}[]` + } else { + val = [val] + } + + utils.forEach(val, (v) => { + if (utils.isDate(v)) { + v = v.toISOString() + } else if (utils.isObject(v)) { + v = JSON.stringify(v) + } + parts.push(`${encode(key)}=${encode(v)}`) + }) + }) + + serializedParams = parts.join('&') + } + + if (serializedParams) { + const hashmarkIndex = url.indexOf('#') + if (hashmarkIndex !== -1) { + url = url.slice(0, hashmarkIndex) + } + + url += (url.indexOf('?') === -1 ? '?' : '&') + serializedParams + } + + return url +} diff --git a/uni_modules/uview-ui/libs/luch-request/helpers/combineURLs.js b/uni_modules/uview-ui/libs/luch-request/helpers/combineURLs.js new file mode 100644 index 0000000..ac7c124 --- /dev/null +++ b/uni_modules/uview-ui/libs/luch-request/helpers/combineURLs.js @@ -0,0 +1,14 @@ +'use strict' + +/** + * Creates a new URL by combining the specified URLs + * + * @param {string} baseURL The base URL + * @param {string} relativeURL The relative URL + * @returns {string} The combined URL + */ +export default function combineURLs(baseURL, relativeURL) { + return relativeURL + ? `${baseURL.replace(/\/+$/, '')}/${relativeURL.replace(/^\/+/, '')}` + : baseURL +} diff --git a/uni_modules/uview-ui/libs/luch-request/helpers/isAbsoluteURL.js b/uni_modules/uview-ui/libs/luch-request/helpers/isAbsoluteURL.js new file mode 100644 index 0000000..63c6647 --- /dev/null +++ b/uni_modules/uview-ui/libs/luch-request/helpers/isAbsoluteURL.js @@ -0,0 +1,14 @@ +'use strict' + +/** + * Determines whether the specified URL is absolute + * + * @param {string} url The URL to test + * @returns {boolean} True if the specified URL is absolute, otherwise false + */ +export default function isAbsoluteURL(url) { + // A URL is considered absolute if it begins with "://" or "//" (protocol-relative URL). + // RFC 3986 defines scheme name as a sequence of characters beginning with a letter and followed + // by any combination of letters, digits, plus, period, or hyphen. + return /^([a-z][a-z\d+\-.]*:)?\/\//i.test(url) +} diff --git a/uni_modules/uview-ui/libs/luch-request/index.d.ts b/uni_modules/uview-ui/libs/luch-request/index.d.ts new file mode 100644 index 0000000..e939ce1 --- /dev/null +++ b/uni_modules/uview-ui/libs/luch-request/index.d.ts @@ -0,0 +1,116 @@ +type AnyObject = Record +type HttpPromise = Promise>; +type Tasks = UniApp.RequestTask | UniApp.UploadTask | UniApp.DownloadTask +export interface RequestTask { + abort: () => void; + offHeadersReceived: () => void; + onHeadersReceived: () => void; +} +export interface HttpRequestConfig { + /** 请求基地址 */ + baseURL?: string; + /** 请求服务器接口地址 */ + url?: string; + + /** 请求查询参数,自动拼接为查询字符串 */ + params?: AnyObject; + /** 请求体参数 */ + data?: AnyObject; + + /** 文件对应的 key */ + name?: string; + /** HTTP 请求中其他额外的 form data */ + formData?: AnyObject; + /** 要上传文件资源的路径。 */ + filePath?: string; + /** 需要上传的文件列表。使用 files 时,filePath 和 name 不生效,App、H5( 2.6.15+) */ + files?: Array<{ + name?: string; + file?: File; + uri: string; + }>; + /** 要上传的文件对象,仅H5(2.6.15+)支持 */ + file?: File; + + /** 请求头信息 */ + header?: AnyObject; + /** 请求方式 */ + method?: "GET" | "POST" | "PUT" | "DELETE" | "CONNECT" | "HEAD" | "OPTIONS" | "TRACE" | "UPLOAD" | "DOWNLOAD"; + /** 如果设为 json,会尝试对返回的数据做一次 JSON.parse */ + dataType?: string; + /** 设置响应的数据类型,支付宝小程序不支持 */ + responseType?: "text" | "arraybuffer"; + /** 自定义参数 */ + custom?: AnyObject; + /** 超时时间,仅微信小程序(2.10.0)、支付宝小程序支持 */ + timeout?: number; + /** DNS解析时优先使用ipv4,仅 App-Android 支持 (HBuilderX 2.8.0+) */ + firstIpv4?: boolean; + /** 验证 ssl 证书 仅5+App安卓端支持(HBuilderX 2.3.3+) */ + sslVerify?: boolean; + /** 跨域请求时是否携带凭证(cookies)仅H5支持(HBuilderX 2.6.15+) */ + withCredentials?: boolean; + + /** 返回当前请求的task, options。请勿在此处修改options。 */ + getTask?: (task: T, options: HttpRequestConfig) => void; + /** 全局自定义验证器 */ + validateStatus?: (statusCode: number) => boolean | void; +} +export interface HttpResponse { + config: HttpRequestConfig; + statusCode: number; + cookies: Array; + data: T; + errMsg: string; + header: AnyObject; +} +export interface HttpUploadResponse { + config: HttpRequestConfig; + statusCode: number; + data: T; + errMsg: string; +} +export interface HttpDownloadResponse extends HttpResponse { + tempFilePath: string; +} +export interface HttpError { + config: HttpRequestConfig; + statusCode?: number; + cookies?: Array; + data?: any; + errMsg: string; + header?: AnyObject; +} +export interface HttpInterceptorManager { + use( + onFulfilled?: (config: V) => Promise | V, + onRejected?: (config: E) => Promise | E + ): void; + eject(id: number): void; +} +export abstract class HttpRequestAbstract { + constructor(config?: HttpRequestConfig); + config: HttpRequestConfig; + interceptors: { + request: HttpInterceptorManager; + response: HttpInterceptorManager; + } + middleware(config: HttpRequestConfig): HttpPromise; + request(config: HttpRequestConfig): HttpPromise; + get(url: string, config?: HttpRequestConfig): HttpPromise; + upload(url: string, config?: HttpRequestConfig): HttpPromise; + delete(url: string, data?: AnyObject, config?: HttpRequestConfig): HttpPromise; + head(url: string, data?: AnyObject, config?: HttpRequestConfig): HttpPromise; + post(url: string, data?: AnyObject, config?: HttpRequestConfig): HttpPromise; + put(url: string, data?: AnyObject, config?: HttpRequestConfig): HttpPromise; + connect(url: string, data?: AnyObject, config?: HttpRequestConfig): HttpPromise; + options(url: string, data?: AnyObject, config?: HttpRequestConfig): HttpPromise; + trace(url: string, data?: AnyObject, config?: HttpRequestConfig): HttpPromise; + + download(url: string, config?: HttpRequestConfig): Promise; + + setConfig(onSend: (config: HttpRequestConfig) => HttpRequestConfig): void; +} + +declare class HttpRequest extends HttpRequestAbstract { } +export default HttpRequest; diff --git a/uni_modules/uview-ui/libs/luch-request/index.js b/uni_modules/uview-ui/libs/luch-request/index.js new file mode 100644 index 0000000..8fb2b44 --- /dev/null +++ b/uni_modules/uview-ui/libs/luch-request/index.js @@ -0,0 +1,3 @@ +import Request from './core/Request' + +export default Request diff --git a/uni_modules/uview-ui/libs/luch-request/utils.js b/uni_modules/uview-ui/libs/luch-request/utils.js new file mode 100644 index 0000000..847283d --- /dev/null +++ b/uni_modules/uview-ui/libs/luch-request/utils.js @@ -0,0 +1,131 @@ +'use strict' + +// utils is a library of generic helper functions non-specific to axios + +const { toString } = Object.prototype + +/** + * Determine if a value is an Array + * + * @param {Object} val The value to test + * @returns {boolean} True if value is an Array, otherwise false + */ +export function isArray(val) { + return toString.call(val) === '[object Array]' +} + +/** + * Determine if a value is an Object + * + * @param {Object} val The value to test + * @returns {boolean} True if value is an Object, otherwise false + */ +export function isObject(val) { + return val !== null && typeof val === 'object' +} + +/** + * Determine if a value is a Date + * + * @param {Object} val The value to test + * @returns {boolean} True if value is a Date, otherwise false + */ +export function isDate(val) { + return toString.call(val) === '[object Date]' +} + +/** + * Determine if a value is a URLSearchParams object + * + * @param {Object} val The value to test + * @returns {boolean} True if value is a URLSearchParams object, otherwise false + */ +export function isURLSearchParams(val) { + return typeof URLSearchParams !== 'undefined' && val instanceof URLSearchParams +} + +/** + * Iterate over an Array or an Object invoking a function for each item. + * + * If `obj` is an Array callback will be called passing + * the value, index, and complete array for each item. + * + * If 'obj' is an Object callback will be called passing + * the value, key, and complete object for each property. + * + * @param {Object|Array} obj The object to iterate + * @param {Function} fn The callback to invoke for each item + */ +export function forEach(obj, fn) { + // Don't bother if no value provided + if (obj === null || typeof obj === 'undefined') { + return + } + + // Force an array if not already something iterable + if (typeof obj !== 'object') { + /* eslint no-param-reassign:0 */ + obj = [obj] + } + + if (isArray(obj)) { + // Iterate over array values + for (let i = 0, l = obj.length; i < l; i++) { + fn.call(null, obj[i], i, obj) + } + } else { + // Iterate over object keys + for (const key in obj) { + if (Object.prototype.hasOwnProperty.call(obj, key)) { + fn.call(null, obj[key], key, obj) + } + } + } +} + +/** + * 是否为boolean 值 + * @param val + * @returns {boolean} + */ +export function isBoolean(val) { + return typeof val === 'boolean' +} + +/** + * 是否为真正的对象{} new Object + * @param {any} obj - 检测的对象 + * @returns {boolean} + */ +export function isPlainObject(obj) { + return Object.prototype.toString.call(obj) === '[object Object]' +} + +/** + * Function equal to merge with the difference being that no reference + * to original objects is kept. + * + * @see merge + * @param {Object} obj1 Object to merge + * @returns {Object} Result of all merge properties + */ +export function deepMerge(/* obj1, obj2, obj3, ... */) { + const result = {} + function assignValue(val, key) { + if (typeof result[key] === 'object' && typeof val === 'object') { + result[key] = deepMerge(result[key], val) + } else if (typeof val === 'object') { + result[key] = deepMerge({}, val) + } else { + result[key] = val + } + } + for (let i = 0, l = arguments.length; i < l; i++) { + forEach(arguments[i], assignValue) + } + return result +} + +export function isUndefined(val) { + return typeof val === 'undefined' +} diff --git a/uni_modules/uview-ui/libs/luch-request/utils/clone.js b/uni_modules/uview-ui/libs/luch-request/utils/clone.js new file mode 100644 index 0000000..2fee704 --- /dev/null +++ b/uni_modules/uview-ui/libs/luch-request/utils/clone.js @@ -0,0 +1,264 @@ +/* eslint-disable */ +var clone = (function() { + 'use strict'; + + function _instanceof(obj, type) { + return type != null && obj instanceof type; + } + + var nativeMap; + try { + nativeMap = Map; + } catch(_) { + // maybe a reference error because no `Map`. Give it a dummy value that no + // value will ever be an instanceof. + nativeMap = function() {}; + } + + var nativeSet; + try { + nativeSet = Set; + } catch(_) { + nativeSet = function() {}; + } + + var nativePromise; + try { + nativePromise = Promise; + } catch(_) { + nativePromise = function() {}; + } + + /** + * Clones (copies) an Object using deep copying. + * + * This function supports circular references by default, but if you are certain + * there are no circular references in your object, you can save some CPU time + * by calling clone(obj, false). + * + * Caution: if `circular` is false and `parent` contains circular references, + * your program may enter an infinite loop and crash. + * + * @param `parent` - the object to be cloned + * @param `circular` - set to true if the object to be cloned may contain + * circular references. (optional - true by default) + * @param `depth` - set to a number if the object is only to be cloned to + * a particular depth. (optional - defaults to Infinity) + * @param `prototype` - sets the prototype to be used when cloning an object. + * (optional - defaults to parent prototype). + * @param `includeNonEnumerable` - set to true if the non-enumerable properties + * should be cloned as well. Non-enumerable properties on the prototype + * chain will be ignored. (optional - false by default) + */ + function clone(parent, circular, depth, prototype, includeNonEnumerable) { + if (typeof circular === 'object') { + depth = circular.depth; + prototype = circular.prototype; + includeNonEnumerable = circular.includeNonEnumerable; + circular = circular.circular; + } + // maintain two arrays for circular references, where corresponding parents + // and children have the same index + var allParents = []; + var allChildren = []; + + var useBuffer = typeof Buffer != 'undefined'; + + if (typeof circular == 'undefined') + circular = true; + + if (typeof depth == 'undefined') + depth = Infinity; + + // recurse this function so we don't reset allParents and allChildren + function _clone(parent, depth) { + // cloning null always returns null + if (parent === null) + return null; + + if (depth === 0) + return parent; + + var child; + var proto; + if (typeof parent != 'object') { + return parent; + } + + if (_instanceof(parent, nativeMap)) { + child = new nativeMap(); + } else if (_instanceof(parent, nativeSet)) { + child = new nativeSet(); + } else if (_instanceof(parent, nativePromise)) { + child = new nativePromise(function (resolve, reject) { + parent.then(function(value) { + resolve(_clone(value, depth - 1)); + }, function(err) { + reject(_clone(err, depth - 1)); + }); + }); + } else if (clone.__isArray(parent)) { + child = []; + } else if (clone.__isRegExp(parent)) { + child = new RegExp(parent.source, __getRegExpFlags(parent)); + if (parent.lastIndex) child.lastIndex = parent.lastIndex; + } else if (clone.__isDate(parent)) { + child = new Date(parent.getTime()); + } else if (useBuffer && Buffer.isBuffer(parent)) { + if (Buffer.from) { + // Node.js >= 5.10.0 + child = Buffer.from(parent); + } else { + // Older Node.js versions + child = new Buffer(parent.length); + parent.copy(child); + } + return child; + } else if (_instanceof(parent, Error)) { + child = Object.create(parent); + } else { + if (typeof prototype == 'undefined') { + proto = Object.getPrototypeOf(parent); + child = Object.create(proto); + } + else { + child = Object.create(prototype); + proto = prototype; + } + } + + if (circular) { + var index = allParents.indexOf(parent); + + if (index != -1) { + return allChildren[index]; + } + allParents.push(parent); + allChildren.push(child); + } + + if (_instanceof(parent, nativeMap)) { + parent.forEach(function(value, key) { + var keyChild = _clone(key, depth - 1); + var valueChild = _clone(value, depth - 1); + child.set(keyChild, valueChild); + }); + } + if (_instanceof(parent, nativeSet)) { + parent.forEach(function(value) { + var entryChild = _clone(value, depth - 1); + child.add(entryChild); + }); + } + + for (var i in parent) { + var attrs = Object.getOwnPropertyDescriptor(parent, i); + if (attrs) { + child[i] = _clone(parent[i], depth - 1); + } + + try { + var objProperty = Object.getOwnPropertyDescriptor(parent, i); + if (objProperty.set === 'undefined') { + // no setter defined. Skip cloning this property + continue; + } + child[i] = _clone(parent[i], depth - 1); + } catch(e){ + if (e instanceof TypeError) { + // when in strict mode, TypeError will be thrown if child[i] property only has a getter + // we can't do anything about this, other than inform the user that this property cannot be set. + continue + } else if (e instanceof ReferenceError) { + //this may happen in non strict mode + continue + } + } + + } + + if (Object.getOwnPropertySymbols) { + var symbols = Object.getOwnPropertySymbols(parent); + for (var i = 0; i < symbols.length; i++) { + // Don't need to worry about cloning a symbol because it is a primitive, + // like a number or string. + var symbol = symbols[i]; + var descriptor = Object.getOwnPropertyDescriptor(parent, symbol); + if (descriptor && !descriptor.enumerable && !includeNonEnumerable) { + continue; + } + child[symbol] = _clone(parent[symbol], depth - 1); + Object.defineProperty(child, symbol, descriptor); + } + } + + if (includeNonEnumerable) { + var allPropertyNames = Object.getOwnPropertyNames(parent); + for (var i = 0; i < allPropertyNames.length; i++) { + var propertyName = allPropertyNames[i]; + var descriptor = Object.getOwnPropertyDescriptor(parent, propertyName); + if (descriptor && descriptor.enumerable) { + continue; + } + child[propertyName] = _clone(parent[propertyName], depth - 1); + Object.defineProperty(child, propertyName, descriptor); + } + } + + return child; + } + + return _clone(parent, depth); + } + + /** + * Simple flat clone using prototype, accepts only objects, usefull for property + * override on FLAT configuration object (no nested props). + * + * USE WITH CAUTION! This may not behave as you wish if you do not know how this + * works. + */ + clone.clonePrototype = function clonePrototype(parent) { + if (parent === null) + return null; + + var c = function () {}; + c.prototype = parent; + return new c(); + }; + +// private utility functions + + function __objToStr(o) { + return Object.prototype.toString.call(o); + } + clone.__objToStr = __objToStr; + + function __isDate(o) { + return typeof o === 'object' && __objToStr(o) === '[object Date]'; + } + clone.__isDate = __isDate; + + function __isArray(o) { + return typeof o === 'object' && __objToStr(o) === '[object Array]'; + } + clone.__isArray = __isArray; + + function __isRegExp(o) { + return typeof o === 'object' && __objToStr(o) === '[object RegExp]'; + } + clone.__isRegExp = __isRegExp; + + function __getRegExpFlags(re) { + var flags = ''; + if (re.global) flags += 'g'; + if (re.ignoreCase) flags += 'i'; + if (re.multiline) flags += 'm'; + return flags; + } + clone.__getRegExpFlags = __getRegExpFlags; + + return clone; +})(); + +export default clone diff --git a/uni_modules/uview-ui/libs/mixin/button.js b/uni_modules/uview-ui/libs/mixin/button.js new file mode 100644 index 0000000..0c019c2 --- /dev/null +++ b/uni_modules/uview-ui/libs/mixin/button.js @@ -0,0 +1,13 @@ +export default { + props: { + lang: String, + sessionFrom: String, + sendMessageTitle: String, + sendMessagePath: String, + sendMessageImg: String, + showMessageCard: Boolean, + appParameter: String, + formType: String, + openType: String + } +} diff --git a/uni_modules/uview-ui/libs/mixin/mixin.js b/uni_modules/uview-ui/libs/mixin/mixin.js new file mode 100644 index 0000000..f41a178 --- /dev/null +++ b/uni_modules/uview-ui/libs/mixin/mixin.js @@ -0,0 +1,160 @@ +module.exports = { + // 定义每个组件都可能需要用到的外部样式以及类名 + props: { + // 每个组件都有的父组件传递的样式,可以为字符串或者对象形式 + customStyle: { + type: [Object, String], + default: () => ({}) + }, + customClass: { + type: String, + default: '' + }, + // 跳转的页面路径 + url: { + type: String, + default: '' + }, + // 页面跳转的类型 + linkType: { + type: String, + default: 'navigateTo' + } + }, + data() { + return {} + }, + onLoad() { + // getRect挂载到$u上,因为这方法需要使用in(this),所以无法把它独立成一个单独的文件导出 + this.$u.getRect = this.$uGetRect + }, + created() { + // 组件当中,只有created声明周期,为了能在组件使用,故也在created中将方法挂载到$u + this.$u.getRect = this.$uGetRect + }, + computed: { + // 在2.x版本中,将会把$u挂载到uni对象下,导致在模板中无法使用uni.$u.xxx形式 + // 所以这里通过computed计算属性将其附加到this.$u上,就可以在模板或者js中使用uni.$u.xxx + // 只在nvue环境通过此方式引入完整的$u,其他平台会出现性能问题,非nvue则按需引入(主要原因是props过大) + $u() { + // #ifndef APP-NVUE + // 在非nvue端,移除props,http,mixin等对象,避免在小程序setData时数据过大影响性能 + return uni.$u.deepMerge(uni.$u, { + props: undefined, + http: undefined, + mixin: undefined + }) + // #endif + // #ifdef APP-NVUE + return uni.$u + // #endif + }, + /** + * 生成bem规则类名 + * 由于微信小程序,H5,nvue之间绑定class的差异,无法通过:class="[bem()]"的形式进行同用 + * 故采用如下折中做法,最后返回的是数组(一般平台)或字符串(支付宝和字节跳动平台),类似['a', 'b', 'c']或'a b c'的形式 + * @param {String} name 组件名称 + * @param {Array} fixed 一直会存在的类名 + * @param {Array} change 会根据变量值为true或者false而出现或者隐藏的类名 + * @returns {Array|string} + */ + bem() { + return function (name, fixed, change) { + // 类名前缀 + const prefix = `u-${name}--` + const classes = {} + if (fixed) { + fixed.map((item) => { + // 这里的类名,会一直存在 + classes[prefix + this[item]] = true + }) + } + if (change) { + change.map((item) => { + // 这里的类名,会根据this[item]的值为true或者false,而进行添加或者移除某一个类 + this[item] ? (classes[prefix + item] = this[item]) : (delete classes[prefix + item]) + }) + } + return Object.keys(classes) + // 支付宝,头条小程序无法动态绑定一个数组类名,否则解析出来的结果会带有",",而导致失效 + // #ifdef MP-ALIPAY || MP-TOUTIAO || MP-LARK + .join(' ') + // #endif + } + } + }, + methods: { + // 跳转某一个页面 + openPage(urlKey = 'url') { + const url = this[urlKey] + if (url) { + // 执行类似uni.navigateTo的方法 + uni[this.linkType]({ + url + }) + } + }, + // 查询节点信息 + // 目前此方法在支付宝小程序中无法获取组件跟接点的尺寸,为支付宝的bug(2020-07-21) + // 解决办法为在组件根部再套一个没有任何作用的view元素 + $uGetRect(selector, all) { + return new Promise((resolve) => { + uni.createSelectorQuery() + .in(this)[all ? 'selectAll' : 'select'](selector) + .boundingClientRect((rect) => { + if (all && Array.isArray(rect) && rect.length) { + resolve(rect) + } + if (!all && rect) { + resolve(rect) + } + }) + .exec() + }) + }, + getParentData(parentName = '') { + // 避免在created中去定义parent变量 + if (!this.parent) this.parent = {} + // 这里的本质原理是,通过获取父组件实例(也即类似u-radio的父组件u-radio-group的this) + // 将父组件this中对应的参数,赋值给本组件(u-radio的this)的parentData对象中对应的属性 + // 之所以需要这么做,是因为所有端中,头条小程序不支持通过this.parent.xxx去监听父组件参数的变化 + // 此处并不会自动更新子组件的数据,而是依赖父组件u-radio-group去监听data的变化,手动调用更新子组件的方法去重新获取 + this.parent = uni.$u.$parent.call(this, parentName) + if (this.parent.children) { + // 如果父组件的children不存在本组件的实例,才将本实例添加到父组件的children中 + this.parent.children.indexOf(this) === -1 && this.parent.children.push(this) + } + if (this.parent && this.parentData) { + // 历遍parentData中的属性,将parent中的同名属性赋值给parentData + Object.keys(this.parentData).map((key) => { + this.parentData[key] = this.parent[key] + }) + } + }, + // 阻止事件冒泡 + preventEvent(e) { + e && typeof (e.stopPropagation) === 'function' && e.stopPropagation() + }, + // 空操作 + noop(e) { + this.preventEvent(e) + } + }, + onReachBottom() { + uni.$emit('uOnReachBottom') + }, + beforeDestroy() { + // 判断当前页面是否存在parent和chldren,一般在checkbox和checkbox-group父子联动的场景会有此情况 + // 组件销毁时,移除子组件在父组件children数组中的实例,释放资源,避免数据混乱 + if (this.parent && uni.$u.test.array(this.parent.children)) { + // 组件销毁时,移除父组件中的children数组中对应的实例 + const childrenList = this.parent.children + childrenList.map((child, index) => { + // 如果相等,则移除 + if (child === this) { + childrenList.splice(index, 1) + } + }) + } + } +} diff --git a/uni_modules/uview-ui/libs/mixin/mpMixin.js b/uni_modules/uview-ui/libs/mixin/mpMixin.js new file mode 100644 index 0000000..29e7e65 --- /dev/null +++ b/uni_modules/uview-ui/libs/mixin/mpMixin.js @@ -0,0 +1,8 @@ +export default { + // #ifdef MP-WEIXIN + // 将自定义节点设置成虚拟的,更加接近Vue组件的表现,能更好的使用flex属性 + options: { + virtualHost: true + } + // #endif +} diff --git a/uni_modules/uview-ui/libs/mixin/mpShare.js b/uni_modules/uview-ui/libs/mixin/mpShare.js new file mode 100644 index 0000000..b07bbd3 --- /dev/null +++ b/uni_modules/uview-ui/libs/mixin/mpShare.js @@ -0,0 +1,13 @@ +module.exports = { + onLoad() { + // 设置默认的转发参数 + uni.$u.mpShare = { + title: '', // 默认为小程序名称 + path: '', // 默认为当前页面路径 + imageUrl: '' // 默认为当前页面的截图 + } + }, + onShareAppMessage() { + return uni.$u.mpShare + } +} diff --git a/uni_modules/uview-ui/libs/mixin/openType.js b/uni_modules/uview-ui/libs/mixin/openType.js new file mode 100644 index 0000000..1216181 --- /dev/null +++ b/uni_modules/uview-ui/libs/mixin/openType.js @@ -0,0 +1,25 @@ +export default { + props: { + openType: String + }, + methods: { + onGetUserInfo(event) { + this.$emit('getuserinfo', event.detail) + }, + onContact(event) { + this.$emit('contact', event.detail) + }, + onGetPhoneNumber(event) { + this.$emit('getphonenumber', event.detail) + }, + onError(event) { + this.$emit('error', event.detail) + }, + onLaunchApp(event) { + this.$emit('launchapp', event.detail) + }, + onOpenSetting(event) { + this.$emit('opensetting', event.detail) + } + } +} diff --git a/uni_modules/uview-ui/libs/mixin/style.js b/uni_modules/uview-ui/libs/mixin/style.js new file mode 100644 index 0000000..2660180 --- /dev/null +++ b/uni_modules/uview-ui/libs/mixin/style.js @@ -0,0 +1,228 @@ +export default { + props: { + // flex排列方式 + flexDirection: { + type: String, + default: '' + }, + // flex-direction的简写 + fd: { + type: String, + default: '' + }, + // 展示类型 + display: { + type: String, + default: '' + }, + // display简写 + d: { + type: String, + default: '' + }, + // 主轴排列方式 + justifyContent: { + type: String, + default: '' + }, + // justifyContent的简写 + jc: { + type: String, + default: '' + }, + // 纵轴排列方式 + alignItems: { + type: String, + default: '' + }, + // align-items的简写 + ai: { + type: String, + default: '' + }, + color: { + type: String, + default: '' + }, + // color简写 + c: { + type: String, + default: '' + }, + // 字体大小 + fontSize: { + type: [String, Number], + default: 0 + }, + // font-size简写 + fs: { + type: [String, Number], + default: '' + }, + margin: { + type: [String, Number], + default: 0 + }, + // margin简写 + m: { + type: [String, Number], + default: 0 + }, + // margin-top + marginTop: { + type: [String, Number], + default: 0 + }, + // margin-top简写 + mt: { + type: [String, Number], + default: 0 + }, + // margin-right + marginRight: { + type: [String, Number], + default: 0 + }, + // margin-right简写 + mr: { + type: [String, Number], + default: 0 + }, + // margin-bottom + marginBottom: { + type: [String, Number], + default: 0 + }, + // margin-bottom简写 + mb: { + type: [String, Number], + default: 0 + }, + // margin-left + marginLeft: { + type: [String, Number], + default: 0 + }, + // margin-left简写 + ml: { + type: [String, Number], + default: 0 + }, + // padding-left + paddingLeft: { + type: [String, Number], + default: 0 + }, + // padding-left简写 + pl: { + type: [String, Number], + default: 0 + }, + // padding-top + paddingTop: { + type: [String, Number], + default: 0 + }, + // padding-top简写 + pt: { + type: [String, Number], + default: 0 + }, + // padding-right + paddingRight: { + type: [String, Number], + default: 0 + }, + // padding-right简写 + pr: { + type: [String, Number], + default: 0 + }, + // padding-bottom + paddingBottom: { + type: [String, Number], + default: 0 + }, + // padding-bottom简写 + pb: { + type: [String, Number], + default: 0 + }, + // border-radius + borderRadius: { + type: [String, Number], + default: 0 + }, + // border-radius简写 + radius: { + type: [String, Number], + default: 0 + }, + // transform + transform: { + type: String, + default: '' + }, + // 定位 + position: { + type: String, + default: '' + }, + // position简写 + pos: { + type: String, + default: '' + }, + // 宽度 + width: { + type: [String, Number], + default: null + }, + // width简写 + w: { + type: [String, Number], + default: null + }, + // 高度 + height: { + type: [String, Number], + default: null + }, + // height简写 + h: { + type: [String, Number], + default: null + }, + top: { + type: [String, Number], + default: 0 + }, + right: { + type: [String, Number], + default: 0 + }, + bottom: { + type: [String, Number], + default: 0 + }, + left: { + type: [String, Number], + default: 0 + } + }, + computed: { + viewStyle() { + const style = {} + const addStyle = uni.$u.addStyle(this.width || this.w) && (style.width = addStyle(this.width || this.w))(this.height || this.h) && (style.height = addStyle(this.height || this.h))(this.margin || this.m) && (style.margin = addStyle(this.margin || this.m))(this.marginTop || this.mt) && (style.marginTop = addStyle(this.marginTop || this.mt))(this.marginRight || this.mr) && (style.marginRight = addStyle(this.marginRight || this.mr))(this.marginBottom || this.mb) && (style.marginBottom = addStyle(this.marginBottom || this.mb))(this.marginLeft || this.ml) && (style.marginLeft = addStyle(this.marginLeft || this.ml))(this.padding || this.p) && (style.padding = addStyle(this.padding || this.p))(this.paddingTop || this.pt) && (style.paddingTop = addStyle(this.paddingTop || this.pt))(this.paddingRight || this.pr) && (style.paddingRight = addStyle(this.paddingRight || this.pr))(this.paddingBottom || this.pb) && (style.paddingBottom = addStyle(this.paddingBottom || this.pb))(this.paddingLeft || this.pl) && (style.paddingLeft = addStyle(this.paddingLeft || this.pl))(this.color || this.c) && (style.color = this.color || this.c)(this.fontSize || this.fs) && (style.fontSize = this.fontSize || this.fs)(this.borderRadius || this.radius) && (style.borderRadius = this.borderRadius || this.radius)(this.position || this.pos) && (this.position = this.position || this.pos)(this.flexDirection || this.fd) && (this.flexDirection = this.flexDirection || this.fd)(this.justifyContent || jc) && (this.justifyContent = this.justifyContent || jc)(this.alignItems || ai) && (this.alignItems = this.alignItems || ai) + + return uni.$u.deepMerge(style, uni.$u.addStyle(this.customStyle)) + } + }, + methods: { + // 获取margin或者padding的单位,比如padding: 0 20转为padding: 0 20px + getUnit(unit = '') { + // 取出两端空格,分隔成数组,再对数组的每个元素添加单位,最后再合并成字符串 + return uni.$u.trim(unit).split(' ').map((item) => uni.$u.addUnit(item)).join(' ') + } + } +} diff --git a/uni_modules/uview-ui/libs/mixin/touch.js b/uni_modules/uview-ui/libs/mixin/touch.js new file mode 100644 index 0000000..0ecbd88 --- /dev/null +++ b/uni_modules/uview-ui/libs/mixin/touch.js @@ -0,0 +1,59 @@ +const MIN_DISTANCE = 10 + +function getDirection(x, y) { + if (x > y && x > MIN_DISTANCE) { + return 'horizontal' + } + if (y > x && y > MIN_DISTANCE) { + return 'vertical' + } + return '' +} + +export default { + methods: { + getTouchPoint(e) { + if (!e) { + return { + x: 0, + y: 0 + } + } if (e.touches && e.touches[0]) { + return { + x: e.touches[0].pageX, + y: e.touches[0].pageY + } + } if (e.changedTouches && e.changedTouches[0]) { + return { + x: e.changedTouches[0].pageX, + y: e.changedTouches[0].pageY + } + } + return { + x: e.clientX || 0, + y: e.clientY || 0 + } + }, + resetTouchStatus() { + this.direction = '' + this.deltaX = 0 + this.deltaY = 0 + this.offsetX = 0 + this.offsetY = 0 + }, + touchStart(event) { + this.resetTouchStatus() + const touch = this.getTouchPoint(event) + this.startX = touch.x + this.startY = touch.y + }, + touchMove(event) { + const touch = this.getTouchPoint(event) + this.deltaX = touch.x - this.startX + this.deltaY = touch.y - this.startY + this.offsetX = Math.abs(this.deltaX) + this.offsetY = Math.abs(this.deltaY) + this.direction = this.direction || getDirection(this.offsetX, this.offsetY) + } + } +} diff --git a/uni_modules/uview-ui/libs/util/async-validator.js b/uni_modules/uview-ui/libs/util/async-validator.js new file mode 100644 index 0000000..9e114df --- /dev/null +++ b/uni_modules/uview-ui/libs/util/async-validator.js @@ -0,0 +1,1343 @@ +function _extends() { + _extends = Object.assign || function (target) { + for (let i = 1; i < arguments.length; i++) { + const source = arguments[i] + + for (const key in source) { + if (Object.prototype.hasOwnProperty.call(source, key)) { + target[key] = source[key] + } + } + } + + return target + } + + return _extends.apply(this, arguments) +} + +/* eslint no-console:0 */ +const formatRegExp = /%[sdj%]/g +let warning = function warning() {} // don't print warning message when in production env or node runtime + +if (typeof process !== 'undefined' && process.env && process.env.NODE_ENV !== 'production' && typeof window + !== 'undefined' && typeof document !== 'undefined') { + warning = function warning(type, errors) { + if (typeof console !== 'undefined' && console.warn) { + if (errors.every((e) => typeof e === 'string')) { + console.warn(type, errors) + } + } + } +} + +function convertFieldsError(errors) { + if (!errors || !errors.length) return null + const fields = {} + errors.forEach((error) => { + const { field } = error + fields[field] = fields[field] || [] + fields[field].push(error) + }) + return fields +} + +function format() { + for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { + args[_key] = arguments[_key] + } + + let i = 1 + const f = args[0] + const len = args.length + + if (typeof f === 'function') { + return f.apply(null, args.slice(1)) + } + + if (typeof f === 'string') { + let str = String(f).replace(formatRegExp, (x) => { + if (x === '%%') { + return '%' + } + + if (i >= len) { + return x + } + + switch (x) { + case '%s': + return String(args[i++]) + + case '%d': + return Number(args[i++]) + + case '%j': + try { + return JSON.stringify(args[i++]) + } catch (_) { + return '[Circular]' + } + + break + + default: + return x + } + }) + + for (let arg = args[i]; i < len; arg = args[++i]) { + str += ` ${arg}` + } + + return str + } + + return f +} + +function isNativeStringType(type) { + return type === 'string' || type === 'url' || type === 'hex' || type === 'email' || type === 'pattern' +} + +function isEmptyValue(value, type) { + if (value === undefined || value === null) { + return true + } + + if (type === 'array' && Array.isArray(value) && !value.length) { + return true + } + + if (isNativeStringType(type) && typeof value === 'string' && !value) { + return true + } + + return false +} + +function asyncParallelArray(arr, func, callback) { + const results = [] + let total = 0 + const arrLength = arr.length + + function count(errors) { + results.push.apply(results, errors) + total++ + + if (total === arrLength) { + callback(results) + } + } + + arr.forEach((a) => { + func(a, count) + }) +} + +function asyncSerialArray(arr, func, callback) { + let index = 0 + const arrLength = arr.length + + function next(errors) { + if (errors && errors.length) { + callback(errors) + return + } + + const original = index + index += 1 + + if (original < arrLength) { + func(arr[original], next) + } else { + callback([]) + } + } + + next([]) +} + +function flattenObjArr(objArr) { + const ret = [] + Object.keys(objArr).forEach((k) => { + ret.push.apply(ret, objArr[k]) + }) + return ret +} + +function asyncMap(objArr, option, func, callback) { + if (option.first) { + const _pending = new Promise((resolve, reject) => { + const next = function next(errors) { + callback(errors) + return errors.length ? reject({ + errors, + fields: convertFieldsError(errors) + }) : resolve() + } + + const flattenArr = flattenObjArr(objArr) + asyncSerialArray(flattenArr, func, next) + }) + + _pending.catch((e) => e) + + return _pending + } + + let firstFields = option.firstFields || [] + + if (firstFields === true) { + firstFields = Object.keys(objArr) + } + + const objArrKeys = Object.keys(objArr) + const objArrLength = objArrKeys.length + let total = 0 + const results = [] + const pending = new Promise((resolve, reject) => { + const next = function next(errors) { + results.push.apply(results, errors) + total++ + + if (total === objArrLength) { + callback(results) + return results.length ? reject({ + errors: results, + fields: convertFieldsError(results) + }) : resolve() + } + } + + if (!objArrKeys.length) { + callback(results) + resolve() + } + + objArrKeys.forEach((key) => { + const arr = objArr[key] + + if (firstFields.indexOf(key) !== -1) { + asyncSerialArray(arr, func, next) + } else { + asyncParallelArray(arr, func, next) + } + }) + }) + pending.catch((e) => e) + return pending +} + +function complementError(rule) { + return function (oe) { + if (oe && oe.message) { + oe.field = oe.field || rule.fullField + return oe + } + + return { + message: typeof oe === 'function' ? oe() : oe, + field: oe.field || rule.fullField + } + } +} + +function deepMerge(target, source) { + if (source) { + for (const s in source) { + if (source.hasOwnProperty(s)) { + const value = source[s] + + if (typeof value === 'object' && typeof target[s] === 'object') { + target[s] = { ...target[s], ...value } + } else { + target[s] = value + } + } + } + } + + return target +} + +/** + * Rule for validating required fields. + * + * @param rule The validation rule. + * @param value The value of the field on the source object. + * @param source The source object being validated. + * @param errors An array of errors that this rule may add + * validation errors to. + * @param options The validation options. + * @param options.messages The validation messages. + */ + +function required(rule, value, source, errors, options, type) { + if (rule.required && (!source.hasOwnProperty(rule.field) || isEmptyValue(value, type || rule.type))) { + errors.push(format(options.messages.required, rule.fullField)) + } +} + +/** + * Rule for validating whitespace. + * + * @param rule The validation rule. + * @param value The value of the field on the source object. + * @param source The source object being validated. + * @param errors An array of errors that this rule may add + * validation errors to. + * @param options The validation options. + * @param options.messages The validation messages. + */ + +function whitespace(rule, value, source, errors, options) { + if (/^\s+$/.test(value) || value === '') { + errors.push(format(options.messages.whitespace, rule.fullField)) + } +} + +/* eslint max-len:0 */ + +const pattern = { + // http://emailregex.com/ + email: /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/, + url: new RegExp( + '^(?!mailto:)(?:(?:http|https|ftp)://|//)(?:\\S+(?::\\S*)?@)?(?:(?:(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}(?:\\.(?:[0-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))|(?:(?:[a-z\\u00a1-\\uffff0-9]+-*)*[a-z\\u00a1-\\uffff0-9]+)(?:\\.(?:[a-z\\u00a1-\\uffff0-9]+-*)*[a-z\\u00a1-\\uffff0-9]+)*(?:\\.(?:[a-z\\u00a1-\\uffff]{2,})))|localhost)(?::\\d{2,5})?(?:(/|\\?|#)[^\\s]*)?$', + 'i' + ), + hex: /^#?([a-f0-9]{6}|[a-f0-9]{3})$/i +} +var types = { + integer: function integer(value) { + return /^(-)?\d+$/.test(value); + }, + float: function float(value) { + return /^(-)?\d+(\.\d+)?$/.test(value); + }, + array: function array(value) { + return Array.isArray(value) + }, + regexp: function regexp(value) { + if (value instanceof RegExp) { + return true + } + + try { + return !!new RegExp(value) + } catch (e) { + return false + } + }, + date: function date(value) { + return typeof value.getTime === 'function' && typeof value.getMonth === 'function' && typeof value.getYear + === 'function' + }, + number: function number(value) { + if (isNaN(value)) { + return false + } + + // 修改源码,将字符串数值先转为数值 + return typeof +value === 'number' + }, + object: function object(value) { + return typeof value === 'object' && !types.array(value) + }, + method: function method(value) { + return typeof value === 'function' + }, + email: function email(value) { + return typeof value === 'string' && !!value.match(pattern.email) && value.length < 255 + }, + url: function url(value) { + return typeof value === 'string' && !!value.match(pattern.url) + }, + hex: function hex(value) { + return typeof value === 'string' && !!value.match(pattern.hex) + } +} +/** + * Rule for validating the type of a value. + * + * @param rule The validation rule. + * @param value The value of the field on the source object. + * @param source The source object being validated. + * @param errors An array of errors that this rule may add + * validation errors to. + * @param options The validation options. + * @param options.messages The validation messages. + */ + +function type(rule, value, source, errors, options) { + if (rule.required && value === undefined) { + required(rule, value, source, errors, options) + return + } + + const custom = ['integer', 'float', 'array', 'regexp', 'object', 'method', 'email', 'number', 'date', 'url', 'hex'] + const ruleType = rule.type + + if (custom.indexOf(ruleType) > -1) { + if (!types[ruleType](value)) { + errors.push(format(options.messages.types[ruleType], rule.fullField, rule.type)) + } // straight typeof check + } else if (ruleType && typeof value !== rule.type) { + errors.push(format(options.messages.types[ruleType], rule.fullField, rule.type)) + } +} + +/** + * Rule for validating minimum and maximum allowed values. + * + * @param rule The validation rule. + * @param value The value of the field on the source object. + * @param source The source object being validated. + * @param errors An array of errors that this rule may add + * validation errors to. + * @param options The validation options. + * @param options.messages The validation messages. + */ + +function range(rule, value, source, errors, options) { + const len = typeof rule.len === 'number' + const min = typeof rule.min === 'number' + const max = typeof rule.max === 'number' // 正则匹配码点范围从U+010000一直到U+10FFFF的文字(补充平面Supplementary Plane) + + const spRegexp = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g + let val = value + let key = null + const num = typeof value === 'number' + const str = typeof value === 'string' + const arr = Array.isArray(value) + + if (num) { + key = 'number' + } else if (str) { + key = 'string' + } else if (arr) { + key = 'array' + } // if the value is not of a supported type for range validation + // the validation rule rule should use the + // type property to also test for a particular type + + if (!key) { + return false + } + + if (arr) { + val = value.length + } + + if (str) { + // 处理码点大于U+010000的文字length属性不准确的bug,如"𠮷𠮷𠮷".lenght !== 3 + val = value.replace(spRegexp, '_').length + } + + if (len) { + if (val !== rule.len) { + errors.push(format(options.messages[key].len, rule.fullField, rule.len)) + } + } else if (min && !max && val < rule.min) { + errors.push(format(options.messages[key].min, rule.fullField, rule.min)) + } else if (max && !min && val > rule.max) { + errors.push(format(options.messages[key].max, rule.fullField, rule.max)) + } else if (min && max && (val < rule.min || val > rule.max)) { + errors.push(format(options.messages[key].range, rule.fullField, rule.min, rule.max)) + } +} + +const ENUM = 'enum' +/** + * Rule for validating a value exists in an enumerable list. + * + * @param rule The validation rule. + * @param value The value of the field on the source object. + * @param source The source object being validated. + * @param errors An array of errors that this rule may add + * validation errors to. + * @param options The validation options. + * @param options.messages The validation messages. + */ + +function enumerable(rule, value, source, errors, options) { + rule[ENUM] = Array.isArray(rule[ENUM]) ? rule[ENUM] : [] + + if (rule[ENUM].indexOf(value) === -1) { + errors.push(format(options.messages[ENUM], rule.fullField, rule[ENUM].join(', '))) + } +} + +/** + * Rule for validating a regular expression pattern. + * + * @param rule The validation rule. + * @param value The value of the field on the source object. + * @param source The source object being validated. + * @param errors An array of errors that this rule may add + * validation errors to. + * @param options The validation options. + * @param options.messages The validation messages. + */ + +function pattern$1(rule, value, source, errors, options) { + if (rule.pattern) { + if (rule.pattern instanceof RegExp) { + // if a RegExp instance is passed, reset `lastIndex` in case its `global` + // flag is accidentally set to `true`, which in a validation scenario + // is not necessary and the result might be misleading + rule.pattern.lastIndex = 0 + + if (!rule.pattern.test(value)) { + errors.push(format(options.messages.pattern.mismatch, rule.fullField, value, rule.pattern)) + } + } else if (typeof rule.pattern === 'string') { + const _pattern = new RegExp(rule.pattern) + + if (!_pattern.test(value)) { + errors.push(format(options.messages.pattern.mismatch, rule.fullField, value, rule.pattern)) + } + } + } +} + +const rules = { + required, + whitespace, + type, + range, + enum: enumerable, + pattern: pattern$1 +} + +/** + * Performs validation for string types. + * + * @param rule The validation rule. + * @param value The value of the field on the source object. + * @param callback The callback function. + * @param source The source object being validated. + * @param options The validation options. + * @param options.messages The validation messages. + */ + +function string(rule, value, callback, source, options) { + const errors = [] + const validate = rule.required || !rule.required && source.hasOwnProperty(rule.field) + + if (validate) { + if (isEmptyValue(value, 'string') && !rule.required) { + return callback() + } + + rules.required(rule, value, source, errors, options, 'string') + + if (!isEmptyValue(value, 'string')) { + rules.type(rule, value, source, errors, options) + rules.range(rule, value, source, errors, options) + rules.pattern(rule, value, source, errors, options) + + if (rule.whitespace === true) { + rules.whitespace(rule, value, source, errors, options) + } + } + } + + callback(errors) +} + +/** + * Validates a function. + * + * @param rule The validation rule. + * @param value The value of the field on the source object. + * @param callback The callback function. + * @param source The source object being validated. + * @param options The validation options. + * @param options.messages The validation messages. + */ + +function method(rule, value, callback, source, options) { + const errors = [] + const validate = rule.required || !rule.required && source.hasOwnProperty(rule.field) + + if (validate) { + if (isEmptyValue(value) && !rule.required) { + return callback() + } + + rules.required(rule, value, source, errors, options) + + if (value !== undefined) { + rules.type(rule, value, source, errors, options) + } + } + + callback(errors) +} + +/** + * Validates a number. + * + * @param rule The validation rule. + * @param value The value of the field on the source object. + * @param callback The callback function. + * @param source The source object being validated. + * @param options The validation options. + * @param options.messages The validation messages. + */ + +function number(rule, value, callback, source, options) { + const errors = [] + const validate = rule.required || !rule.required && source.hasOwnProperty(rule.field) + + if (validate) { + if (value === '') { + value = undefined + } + + if (isEmptyValue(value) && !rule.required) { + return callback() + } + + rules.required(rule, value, source, errors, options) + + if (value !== undefined) { + rules.type(rule, value, source, errors, options) + rules.range(rule, value, source, errors, options) + } + } + + callback(errors) +} + +/** + * Validates a boolean. + * + * @param rule The validation rule. + * @param value The value of the field on the source object. + * @param callback The callback function. + * @param source The source object being validated. + * @param options The validation options. + * @param options.messages The validation messages. + */ + +function _boolean(rule, value, callback, source, options) { + const errors = [] + const validate = rule.required || !rule.required && source.hasOwnProperty(rule.field) + + if (validate) { + if (isEmptyValue(value) && !rule.required) { + return callback() + } + + rules.required(rule, value, source, errors, options) + + if (value !== undefined) { + rules.type(rule, value, source, errors, options) + } + } + + callback(errors) +} + +/** + * Validates the regular expression type. + * + * @param rule The validation rule. + * @param value The value of the field on the source object. + * @param callback The callback function. + * @param source The source object being validated. + * @param options The validation options. + * @param options.messages The validation messages. + */ + +function regexp(rule, value, callback, source, options) { + const errors = [] + const validate = rule.required || !rule.required && source.hasOwnProperty(rule.field) + + if (validate) { + if (isEmptyValue(value) && !rule.required) { + return callback() + } + + rules.required(rule, value, source, errors, options) + + if (!isEmptyValue(value)) { + rules.type(rule, value, source, errors, options) + } + } + + callback(errors) +} + +/** + * Validates a number is an integer. + * + * @param rule The validation rule. + * @param value The value of the field on the source object. + * @param callback The callback function. + * @param source The source object being validated. + * @param options The validation options. + * @param options.messages The validation messages. + */ + +function integer(rule, value, callback, source, options) { + const errors = [] + const validate = rule.required || !rule.required && source.hasOwnProperty(rule.field) + + if (validate) { + if (isEmptyValue(value) && !rule.required) { + return callback() + } + + rules.required(rule, value, source, errors, options) + + if (value !== undefined) { + rules.type(rule, value, source, errors, options) + rules.range(rule, value, source, errors, options) + } + } + + callback(errors) +} + +/** + * Validates a number is a floating point number. + * + * @param rule The validation rule. + * @param value The value of the field on the source object. + * @param callback The callback function. + * @param source The source object being validated. + * @param options The validation options. + * @param options.messages The validation messages. + */ + +function floatFn(rule, value, callback, source, options) { + const errors = [] + const validate = rule.required || !rule.required && source.hasOwnProperty(rule.field) + + if (validate) { + if (isEmptyValue(value) && !rule.required) { + return callback() + } + + rules.required(rule, value, source, errors, options) + + if (value !== undefined) { + rules.type(rule, value, source, errors, options) + rules.range(rule, value, source, errors, options) + } + } + + callback(errors) +} + +/** + * Validates an array. + * + * @param rule The validation rule. + * @param value The value of the field on the source object. + * @param callback The callback function. + * @param source The source object being validated. + * @param options The validation options. + * @param options.messages The validation messages. + */ + +function array(rule, value, callback, source, options) { + const errors = [] + const validate = rule.required || !rule.required && source.hasOwnProperty(rule.field) + + if (validate) { + if (isEmptyValue(value, 'array') && !rule.required) { + return callback() + } + + rules.required(rule, value, source, errors, options, 'array') + + if (!isEmptyValue(value, 'array')) { + rules.type(rule, value, source, errors, options) + rules.range(rule, value, source, errors, options) + } + } + + callback(errors) +} + +/** + * Validates an object. + * + * @param rule The validation rule. + * @param value The value of the field on the source object. + * @param callback The callback function. + * @param source The source object being validated. + * @param options The validation options. + * @param options.messages The validation messages. + */ + +function object(rule, value, callback, source, options) { + const errors = [] + const validate = rule.required || !rule.required && source.hasOwnProperty(rule.field) + + if (validate) { + if (isEmptyValue(value) && !rule.required) { + return callback() + } + + rules.required(rule, value, source, errors, options) + + if (value !== undefined) { + rules.type(rule, value, source, errors, options) + } + } + + callback(errors) +} + +const ENUM$1 = 'enum' +/** + * Validates an enumerable list. + * + * @param rule The validation rule. + * @param value The value of the field on the source object. + * @param callback The callback function. + * @param source The source object being validated. + * @param options The validation options. + * @param options.messages The validation messages. + */ + +function enumerable$1(rule, value, callback, source, options) { + const errors = [] + const validate = rule.required || !rule.required && source.hasOwnProperty(rule.field) + + if (validate) { + if (isEmptyValue(value) && !rule.required) { + return callback() + } + + rules.required(rule, value, source, errors, options) + + if (value !== undefined) { + rules[ENUM$1](rule, value, source, errors, options) + } + } + + callback(errors) +} + +/** + * Validates a regular expression pattern. + * + * Performs validation when a rule only contains + * a pattern property but is not declared as a string type. + * + * @param rule The validation rule. + * @param value The value of the field on the source object. + * @param callback The callback function. + * @param source The source object being validated. + * @param options The validation options. + * @param options.messages The validation messages. + */ + +function pattern$2(rule, value, callback, source, options) { + const errors = [] + const validate = rule.required || !rule.required && source.hasOwnProperty(rule.field) + + if (validate) { + if (isEmptyValue(value, 'string') && !rule.required) { + return callback() + } + + rules.required(rule, value, source, errors, options) + + if (!isEmptyValue(value, 'string')) { + rules.pattern(rule, value, source, errors, options) + } + } + + callback(errors) +} + +function date(rule, value, callback, source, options) { + const errors = [] + const validate = rule.required || !rule.required && source.hasOwnProperty(rule.field) + + if (validate) { + if (isEmptyValue(value) && !rule.required) { + return callback() + } + + rules.required(rule, value, source, errors, options) + + if (!isEmptyValue(value)) { + let dateObject + + if (typeof value === 'number') { + dateObject = new Date(value) + } else { + dateObject = value + } + + rules.type(rule, dateObject, source, errors, options) + + if (dateObject) { + rules.range(rule, dateObject.getTime(), source, errors, options) + } + } + } + + callback(errors) +} + +function required$1(rule, value, callback, source, options) { + const errors = [] + const type = Array.isArray(value) ? 'array' : typeof value + rules.required(rule, value, source, errors, options, type) + callback(errors) +} + +function type$1(rule, value, callback, source, options) { + const ruleType = rule.type + const errors = [] + const validate = rule.required || !rule.required && source.hasOwnProperty(rule.field) + + if (validate) { + if (isEmptyValue(value, ruleType) && !rule.required) { + return callback() + } + + rules.required(rule, value, source, errors, options, ruleType) + + if (!isEmptyValue(value, ruleType)) { + rules.type(rule, value, source, errors, options) + } + } + + callback(errors) +} + +/** + * Performs validation for any type. + * + * @param rule The validation rule. + * @param value The value of the field on the source object. + * @param callback The callback function. + * @param source The source object being validated. + * @param options The validation options. + * @param options.messages The validation messages. + */ + +function any(rule, value, callback, source, options) { + const errors = [] + const validate = rule.required || !rule.required && source.hasOwnProperty(rule.field) + + if (validate) { + if (isEmptyValue(value) && !rule.required) { + return callback() + } + + rules.required(rule, value, source, errors, options) + } + + callback(errors) +} + +const validators = { + string, + method, + number, + boolean: _boolean, + regexp, + integer, + float: floatFn, + array, + object, + enum: enumerable$1, + pattern: pattern$2, + date, + url: type$1, + hex: type$1, + email: type$1, + required: required$1, + any +} + +function newMessages() { + return { + default: 'Validation error on field %s', + required: '%s is required', + enum: '%s must be one of %s', + whitespace: '%s cannot be empty', + date: { + format: '%s date %s is invalid for format %s', + parse: '%s date could not be parsed, %s is invalid ', + invalid: '%s date %s is invalid' + }, + types: { + string: '%s is not a %s', + method: '%s is not a %s (function)', + array: '%s is not an %s', + object: '%s is not an %s', + number: '%s is not a %s', + date: '%s is not a %s', + boolean: '%s is not a %s', + integer: '%s is not an %s', + float: '%s is not a %s', + regexp: '%s is not a valid %s', + email: '%s is not a valid %s', + url: '%s is not a valid %s', + hex: '%s is not a valid %s' + }, + string: { + len: '%s must be exactly %s characters', + min: '%s must be at least %s characters', + max: '%s cannot be longer than %s characters', + range: '%s must be between %s and %s characters' + }, + number: { + len: '%s must equal %s', + min: '%s cannot be less than %s', + max: '%s cannot be greater than %s', + range: '%s must be between %s and %s' + }, + array: { + len: '%s must be exactly %s in length', + min: '%s cannot be less than %s in length', + max: '%s cannot be greater than %s in length', + range: '%s must be between %s and %s in length' + }, + pattern: { + mismatch: '%s value %s does not match pattern %s' + }, + clone: function clone() { + const cloned = JSON.parse(JSON.stringify(this)) + cloned.clone = this.clone + return cloned + } + } +} +const messages = newMessages() + +/** + * Encapsulates a validation schema. + * + * @param descriptor An object declaring validation rules + * for this schema. + */ + +function Schema(descriptor) { + this.rules = null + this._messages = messages + this.define(descriptor) +} + +Schema.prototype = { + messages: function messages(_messages) { + if (_messages) { + this._messages = deepMerge(newMessages(), _messages) + } + + return this._messages + }, + define: function define(rules) { + if (!rules) { + throw new Error('Cannot configure a schema with no rules') + } + + if (typeof rules !== 'object' || Array.isArray(rules)) { + throw new Error('Rules must be an object') + } + + this.rules = {} + let z + let item + + for (z in rules) { + if (rules.hasOwnProperty(z)) { + item = rules[z] + this.rules[z] = Array.isArray(item) ? item : [item] + } + } + }, + validate: function validate(source_, o, oc) { + const _this = this + + if (o === void 0) { + o = {} + } + + if (oc === void 0) { + oc = function oc() {} + } + + let source = source_ + let options = o + let callback = oc + + if (typeof options === 'function') { + callback = options + options = {} + } + + if (!this.rules || Object.keys(this.rules).length === 0) { + if (callback) { + callback() + } + + return Promise.resolve() + } + + function complete(results) { + let i + let errors = [] + let fields = {} + + function add(e) { + if (Array.isArray(e)) { + let _errors + + errors = (_errors = errors).concat.apply(_errors, e) + } else { + errors.push(e) + } + } + + for (i = 0; i < results.length; i++) { + add(results[i]) + } + + if (!errors.length) { + errors = null + fields = null + } else { + fields = convertFieldsError(errors) + } + + callback(errors, fields) + } + + if (options.messages) { + let messages$1 = this.messages() + + if (messages$1 === messages) { + messages$1 = newMessages() + } + + deepMerge(messages$1, options.messages) + options.messages = messages$1 + } else { + options.messages = this.messages() + } + + let arr + let value + const series = {} + const keys = options.keys || Object.keys(this.rules) + keys.forEach((z) => { + arr = _this.rules[z] + value = source[z] + arr.forEach((r) => { + let rule = r + + if (typeof rule.transform === 'function') { + if (source === source_) { + source = { ...source } + } + + value = source[z] = rule.transform(value) + } + + if (typeof rule === 'function') { + rule = { + validator: rule + } + } else { + rule = { ...rule } + } + + rule.validator = _this.getValidationMethod(rule) + rule.field = z + rule.fullField = rule.fullField || z + rule.type = _this.getType(rule) + + if (!rule.validator) { + return + } + + series[z] = series[z] || [] + series[z].push({ + rule, + value, + source, + field: z + }) + }) + }) + const errorFields = {} + return asyncMap(series, options, (data, doIt) => { + const { rule } = data + let deep = (rule.type === 'object' || rule.type === 'array') && (typeof rule.fields === 'object' || typeof rule.defaultField + === 'object') + deep = deep && (rule.required || !rule.required && data.value) + rule.field = data.field + + function addFullfield(key, schema) { + return { ...schema, fullField: `${rule.fullField}.${key}` } + } + + function cb(e) { + if (e === void 0) { + e = [] + } + + let errors = e + + if (!Array.isArray(errors)) { + errors = [errors] + } + + if (!options.suppressWarning && errors.length) { + Schema.warning('async-validator:', errors) + } + + if (errors.length && rule.message) { + errors = [].concat(rule.message) + } + + errors = errors.map(complementError(rule)) + + if (options.first && errors.length) { + errorFields[rule.field] = 1 + return doIt(errors) + } + + if (!deep) { + doIt(errors) + } else { + // if rule is required but the target object + // does not exist fail at the rule level and don't + // go deeper + if (rule.required && !data.value) { + if (rule.message) { + errors = [].concat(rule.message).map(complementError(rule)) + } else if (options.error) { + errors = [options.error(rule, format(options.messages.required, rule.field))] + } else { + errors = [] + } + + return doIt(errors) + } + + let fieldsSchema = {} + + if (rule.defaultField) { + for (const k in data.value) { + if (data.value.hasOwnProperty(k)) { + fieldsSchema[k] = rule.defaultField + } + } + } + + fieldsSchema = { ...fieldsSchema, ...data.rule.fields } + + for (const f in fieldsSchema) { + if (fieldsSchema.hasOwnProperty(f)) { + const fieldSchema = Array.isArray(fieldsSchema[f]) ? fieldsSchema[f] : [fieldsSchema[f]] + fieldsSchema[f] = fieldSchema.map(addFullfield.bind(null, f)) + } + } + + const schema = new Schema(fieldsSchema) + schema.messages(options.messages) + + if (data.rule.options) { + data.rule.options.messages = options.messages + data.rule.options.error = options.error + } + + schema.validate(data.value, data.rule.options || options, (errs) => { + const finalErrors = [] + + if (errors && errors.length) { + finalErrors.push.apply(finalErrors, errors) + } + + if (errs && errs.length) { + finalErrors.push.apply(finalErrors, errs) + } + + doIt(finalErrors.length ? finalErrors : null) + }) + } + } + + let res + + if (rule.asyncValidator) { + res = rule.asyncValidator(rule, data.value, cb, data.source, options) + } else if (rule.validator) { + res = rule.validator(rule, data.value, cb, data.source, options) + + if (res === true) { + cb() + } else if (res === false) { + cb(rule.message || `${rule.field} fails`) + } else if (res instanceof Array) { + cb(res) + } else if (res instanceof Error) { + cb(res.message) + } + } + + if (res && res.then) { + res.then(() => cb(), (e) => cb(e)) + } + }, (results) => { + complete(results) + }) + }, + getType: function getType(rule) { + if (rule.type === undefined && rule.pattern instanceof RegExp) { + rule.type = 'pattern' + } + + if (typeof rule.validator !== 'function' && rule.type && !validators.hasOwnProperty(rule.type)) { + throw new Error(format('Unknown rule type %s', rule.type)) + } + + return rule.type || 'string' + }, + getValidationMethod: function getValidationMethod(rule) { + if (typeof rule.validator === 'function') { + return rule.validator + } + + const keys = Object.keys(rule) + const messageIndex = keys.indexOf('message') + + if (messageIndex !== -1) { + keys.splice(messageIndex, 1) + } + + if (keys.length === 1 && keys[0] === 'required') { + return validators.required + } + + return validators[this.getType(rule)] || false + } +} + +Schema.register = function register(type, validator) { + if (typeof validator !== 'function') { + throw new Error('Cannot register a validator by type, validator is not a function') + } + + validators[type] = validator +} + +Schema.warning = warning +Schema.messages = messages + +export default Schema +// # sourceMappingURL=index.js.map diff --git a/uni_modules/uview-ui/libs/util/calendar.js b/uni_modules/uview-ui/libs/util/calendar.js new file mode 100644 index 0000000..e006dea --- /dev/null +++ b/uni_modules/uview-ui/libs/util/calendar.js @@ -0,0 +1,546 @@ +/** +* @1900-2100区间内的公历、农历互转 +* @charset UTF-8 +* @github https://github.com/jjonline/calendar.js +* @Author Jea杨(JJonline@JJonline.Cn) +* @Time 2014-7-21 +* @Time 2016-8-13 Fixed 2033hex、Attribution Annals +* @Time 2016-9-25 Fixed lunar LeapMonth Param Bug +* @Time 2017-7-24 Fixed use getTerm Func Param Error.use solar year,NOT lunar year +* @Version 1.0.3 +* @公历转农历:calendar.solar2lunar(1987,11,01); //[you can ignore params of prefix 0] +* @农历转公历:calendar.lunar2solar(1987,09,10); //[you can ignore params of prefix 0] +*/ +/* eslint-disable */ +var calendar = { + + /** + * 农历1900-2100的润大小信息表 + * @Array Of Property + * @return Hex + */ + lunarInfo: [0x04bd8, 0x04ae0, 0x0a570, 0x054d5, 0x0d260, 0x0d950, 0x16554, 0x056a0, 0x09ad0, 0x055d2, // 1900-1909 + 0x04ae0, 0x0a5b6, 0x0a4d0, 0x0d250, 0x1d255, 0x0b540, 0x0d6a0, 0x0ada2, 0x095b0, 0x14977, // 1910-1919 + 0x04970, 0x0a4b0, 0x0b4b5, 0x06a50, 0x06d40, 0x1ab54, 0x02b60, 0x09570, 0x052f2, 0x04970, // 1920-1929 + 0x06566, 0x0d4a0, 0x0ea50, 0x06e95, 0x05ad0, 0x02b60, 0x186e3, 0x092e0, 0x1c8d7, 0x0c950, // 1930-1939 + 0x0d4a0, 0x1d8a6, 0x0b550, 0x056a0, 0x1a5b4, 0x025d0, 0x092d0, 0x0d2b2, 0x0a950, 0x0b557, // 1940-1949 + 0x06ca0, 0x0b550, 0x15355, 0x04da0, 0x0a5b0, 0x14573, 0x052b0, 0x0a9a8, 0x0e950, 0x06aa0, // 1950-1959 + 0x0aea6, 0x0ab50, 0x04b60, 0x0aae4, 0x0a570, 0x05260, 0x0f263, 0x0d950, 0x05b57, 0x056a0, // 1960-1969 + 0x096d0, 0x04dd5, 0x04ad0, 0x0a4d0, 0x0d4d4, 0x0d250, 0x0d558, 0x0b540, 0x0b6a0, 0x195a6, // 1970-1979 + 0x095b0, 0x049b0, 0x0a974, 0x0a4b0, 0x0b27a, 0x06a50, 0x06d40, 0x0af46, 0x0ab60, 0x09570, // 1980-1989 + 0x04af5, 0x04970, 0x064b0, 0x074a3, 0x0ea50, 0x06b58, 0x05ac0, 0x0ab60, 0x096d5, 0x092e0, // 1990-1999 + 0x0c960, 0x0d954, 0x0d4a0, 0x0da50, 0x07552, 0x056a0, 0x0abb7, 0x025d0, 0x092d0, 0x0cab5, // 2000-2009 + 0x0a950, 0x0b4a0, 0x0baa4, 0x0ad50, 0x055d9, 0x04ba0, 0x0a5b0, 0x15176, 0x052b0, 0x0a930, // 2010-2019 + 0x07954, 0x06aa0, 0x0ad50, 0x05b52, 0x04b60, 0x0a6e6, 0x0a4e0, 0x0d260, 0x0ea65, 0x0d530, // 2020-2029 + 0x05aa0, 0x076a3, 0x096d0, 0x04afb, 0x04ad0, 0x0a4d0, 0x1d0b6, 0x0d250, 0x0d520, 0x0dd45, // 2030-2039 + 0x0b5a0, 0x056d0, 0x055b2, 0x049b0, 0x0a577, 0x0a4b0, 0x0aa50, 0x1b255, 0x06d20, 0x0ada0, // 2040-2049 + /** Add By JJonline@JJonline.Cn**/ + 0x14b63, 0x09370, 0x049f8, 0x04970, 0x064b0, 0x168a6, 0x0ea50, 0x06b20, 0x1a6c4, 0x0aae0, // 2050-2059 + 0x0a2e0, 0x0d2e3, 0x0c960, 0x0d557, 0x0d4a0, 0x0da50, 0x05d55, 0x056a0, 0x0a6d0, 0x055d4, // 2060-2069 + 0x052d0, 0x0a9b8, 0x0a950, 0x0b4a0, 0x0b6a6, 0x0ad50, 0x055a0, 0x0aba4, 0x0a5b0, 0x052b0, // 2070-2079 + 0x0b273, 0x06930, 0x07337, 0x06aa0, 0x0ad50, 0x14b55, 0x04b60, 0x0a570, 0x054e4, 0x0d160, // 2080-2089 + 0x0e968, 0x0d520, 0x0daa0, 0x16aa6, 0x056d0, 0x04ae0, 0x0a9d4, 0x0a2d0, 0x0d150, 0x0f252, // 2090-2099 + 0x0d520], // 2100 + + /** + * 公历每个月份的天数普通表 + * @Array Of Property + * @return Number + */ + solarMonth: [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31], + + /** + * 天干地支之天干速查表 + * @Array Of Property trans["甲","乙","丙","丁","戊","己","庚","辛","壬","癸"] + * @return Cn string + */ + Gan: ['\u7532', '\u4e59', '\u4e19', '\u4e01', '\u620a', '\u5df1', '\u5e9a', '\u8f9b', '\u58ec', '\u7678'], + + /** + * 天干地支之地支速查表 + * @Array Of Property + * @trans["子","丑","寅","卯","辰","巳","午","未","申","酉","戌","亥"] + * @return Cn string + */ + Zhi: ['\u5b50', '\u4e11', '\u5bc5', '\u536f', '\u8fb0', '\u5df3', '\u5348', '\u672a', '\u7533', '\u9149', '\u620c', '\u4ea5'], + + /** + * 天干地支之地支速查表<=>生肖 + * @Array Of Property + * @trans["鼠","牛","虎","兔","龙","蛇","马","羊","猴","鸡","狗","猪"] + * @return Cn string + */ + Animals: ['\u9f20', '\u725b', '\u864e', '\u5154', '\u9f99', '\u86c7', '\u9a6c', '\u7f8a', '\u7334', '\u9e21', '\u72d7', '\u732a'], + + /** + * 24节气速查表 + * @Array Of Property + * @trans["小寒","大寒","立春","雨水","惊蛰","春分","清明","谷雨","立夏","小满","芒种","夏至","小暑","大暑","立秋","处暑","白露","秋分","寒露","霜降","立冬","小雪","大雪","冬至"] + * @return Cn string + */ + solarTerm: ['\u5c0f\u5bd2', '\u5927\u5bd2', '\u7acb\u6625', '\u96e8\u6c34', '\u60ca\u86f0', '\u6625\u5206', '\u6e05\u660e', '\u8c37\u96e8', '\u7acb\u590f', '\u5c0f\u6ee1', '\u8292\u79cd', '\u590f\u81f3', '\u5c0f\u6691', '\u5927\u6691', '\u7acb\u79cb', '\u5904\u6691', '\u767d\u9732', '\u79cb\u5206', '\u5bd2\u9732', '\u971c\u964d', '\u7acb\u51ac', '\u5c0f\u96ea', '\u5927\u96ea', '\u51ac\u81f3'], + + /** + * 1900-2100各年的24节气日期速查表 + * @Array Of Property + * @return 0x string For splice + */ + sTermInfo: ['9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf97c3598082c95f8c965cc920f', + '97bd0b06bdb0722c965ce1cfcc920f', 'b027097bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', + '97bcf97c359801ec95f8c965cc920f', '97bd0b06bdb0722c965ce1cfcc920f', 'b027097bd097c36b0b6fc9274c91aa', + '97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f', '97bd0b06bdb0722c965ce1cfcc920f', + 'b027097bd097c36b0b6fc9274c91aa', '9778397bd19801ec9210c965cc920e', '97b6b97bd19801ec95f8c965cc920f', + '97bd09801d98082c95f8e1cfcc920f', '97bd097bd097c36b0b6fc9210c8dc2', '9778397bd197c36c9210c9274c91aa', + '97b6b97bd19801ec95f8c965cc920e', '97bd09801d98082c95f8e1cfcc920f', '97bd097bd097c36b0b6fc9210c8dc2', + '9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec95f8c965cc920e', '97bcf97c3598082c95f8e1cfcc920f', + '97bd097bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec9210c965cc920e', + '97bcf97c3598082c95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', + '97b6b97bd19801ec9210c965cc920e', '97bcf97c3598082c95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722', + '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f', + '97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', + '97bcf97c359801ec95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', + '97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f', '97bd097bd07f595b0b6fc920fb0722', + '9778397bd097c36b0b6fc9210c8dc2', '9778397bd19801ec9210c9274c920e', '97b6b97bd19801ec95f8c965cc920f', + '97bd07f5307f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c920e', + '97b6b97bd19801ec95f8c965cc920f', '97bd07f5307f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2', + '9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bd07f1487f595b0b0bc920fb0722', + '7f0e397bd097c36b0b6fc9210c8dc2', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', + '97bcf7f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', + '97b6b97bd19801ec9210c965cc920e', '97bcf7f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', + '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf7f1487f531b0b0bb0b6fb0722', + '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', + '97bcf7f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', + '97b6b97bd19801ec9210c9274c920e', '97bcf7f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722', + '9778397bd097c36b0b6fc9210c91aa', '97b6b97bd197c36c9210c9274c920e', '97bcf7f0e47f531b0b0bb0b6fb0722', + '7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c920e', + '97b6b7f0e47f531b0723b0b6fb0722', '7f0e37f5307f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2', + '9778397bd097c36b0b70c9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721', '7f0e37f1487f595b0b0bb0b6fb0722', + '7f0e397bd097c35b0b6fc9210c8dc2', '9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721', + '7f0e27f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', + '97b6b7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', + '9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', + '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721', + '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9274c91aa', + '97b6b7f0e47f531b0723b0787b0721', '7f0e27f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722', + '9778397bd097c36b0b6fc9210c91aa', '97b6b7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722', + '7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9210c8dc2', '977837f0e37f149b0723b0787b0721', + '7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f5307f595b0b0bc920fb0722', '7f0e397bd097c35b0b6fc9210c8dc2', + '977837f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e37f1487f595b0b0bb0b6fb0722', + '7f0e397bd097c35b0b6fc9210c8dc2', '977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', + '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '977837f0e37f14998082b0787b06bd', + '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', + '977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', + '7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', + '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14998082b0787b06bd', + '7f07e7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722', + '977837f0e37f14998082b0723b06bd', '7f07e7f0e37f149b0723b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722', + '7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b0721', + '7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f1487f595b0b0bb0b6fb0722', '7f0e37f0e37f14898082b0723b02d5', + '7ec967f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f1487f531b0b0bb0b6fb0722', + '7f0e37f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', + '7f0e37f1487f531b0b0bb0b6fb0722', '7f0e37f0e37f14898082b072297c35', '7ec967f0e37f14998082b0787b06bd', + '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e37f0e37f14898082b072297c35', + '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', + '7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f149b0723b0787b0721', + '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14998082b0723b06bd', + '7f07e7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722', '7f0e37f0e366aa89801eb072297c35', + '7ec967f0e37f14998082b0723b06bd', '7f07e7f0e37f14998083b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722', + '7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14898082b0723b02d5', '7f07e7f0e37f14998082b0787b0721', + '7f07e7f0e47f531b0723b0b6fb0722', '7f0e36665b66aa89801e9808297c35', '665f67f0e37f14898082b0723b02d5', + '7ec967f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0722', '7f0e36665b66a449801e9808297c35', + '665f67f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', + '7f0e36665b66a449801e9808297c35', '665f67f0e37f14898082b072297c35', '7ec967f0e37f14998082b0787b06bd', + '7f07e7f0e47f531b0723b0b6fb0721', '7f0e26665b66a449801e9808297c35', '665f67f0e37f1489801eb072297c35', + '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722'], + + /** + * 数字转中文速查表 + * @Array Of Property + * @trans ['日','一','二','三','四','五','六','七','八','九','十'] + * @return Cn string + */ + nStr1: ['\u65e5', '\u4e00', '\u4e8c', '\u4e09', '\u56db', '\u4e94', '\u516d', '\u4e03', '\u516b', '\u4e5d', '\u5341'], + + /** + * 日期转农历称呼速查表 + * @Array Of Property + * @trans ['初','十','廿','卅'] + * @return Cn string + */ + nStr2: ['\u521d', '\u5341', '\u5eff', '\u5345'], + + /** + * 月份转农历称呼速查表 + * @Array Of Property + * @trans ['正','一','二','三','四','五','六','七','八','九','十','冬','腊'] + * @return Cn string + */ + nStr3: ['\u6b63', '\u4e8c', '\u4e09', '\u56db', '\u4e94', '\u516d', '\u4e03', '\u516b', '\u4e5d', '\u5341', '\u51ac', '\u814a'], + + /** + * 返回农历y年一整年的总天数 + * @param lunar Year + * @return Number + * @eg:var count = calendar.lYearDays(1987) ;//count=387 + */ + lYearDays: function (y) { + var i; var sum = 348 + for (i = 0x8000; i > 0x8; i >>= 1) { sum += (this.lunarInfo[y - 1900] & i) ? 1 : 0 } + return (sum + this.leapDays(y)) + }, + + /** + * 返回农历y年闰月是哪个月;若y年没有闰月 则返回0 + * @param lunar Year + * @return Number (0-12) + * @eg:var leapMonth = calendar.leapMonth(1987) ;//leapMonth=6 + */ + leapMonth: function (y) { // 闰字编码 \u95f0 + return (this.lunarInfo[y - 1900] & 0xf) + }, + + /** + * 返回农历y年闰月的天数 若该年没有闰月则返回0 + * @param lunar Year + * @return Number (0、29、30) + * @eg:var leapMonthDay = calendar.leapDays(1987) ;//leapMonthDay=29 + */ + leapDays: function (y) { + if (this.leapMonth(y)) { + return ((this.lunarInfo[y - 1900] & 0x10000) ? 30 : 29) + } + return (0) + }, + + /** + * 返回农历y年m月(非闰月)的总天数,计算m为闰月时的天数请使用leapDays方法 + * @param lunar Year + * @return Number (-1、29、30) + * @eg:var MonthDay = calendar.monthDays(1987,9) ;//MonthDay=29 + */ + monthDays: function (y, m) { + if (m > 12 || m < 1) { return -1 }// 月份参数从1至12,参数错误返回-1 + return ((this.lunarInfo[y - 1900] & (0x10000 >> m)) ? 30 : 29) + }, + + /** + * 返回公历(!)y年m月的天数 + * @param solar Year + * @return Number (-1、28、29、30、31) + * @eg:var solarMonthDay = calendar.leapDays(1987) ;//solarMonthDay=30 + */ + solarDays: function (y, m) { + if (m > 12 || m < 1) { return -1 } // 若参数错误 返回-1 + var ms = m - 1 + if (ms == 1) { // 2月份的闰平规律测算后确认返回28或29 + return (((y % 4 == 0) && (y % 100 != 0) || (y % 400 == 0)) ? 29 : 28) + } else { + return (this.solarMonth[ms]) + } + }, + + /** + * 农历年份转换为干支纪年 + * @param lYear 农历年的年份数 + * @return Cn string + */ + toGanZhiYear: function (lYear) { + var ganKey = (lYear - 3) % 10 + var zhiKey = (lYear - 3) % 12 + if (ganKey == 0) ganKey = 10// 如果余数为0则为最后一个天干 + if (zhiKey == 0) zhiKey = 12// 如果余数为0则为最后一个地支 + return this.Gan[ganKey - 1] + this.Zhi[zhiKey - 1] + }, + + /** + * 公历月、日判断所属星座 + * @param cMonth [description] + * @param cDay [description] + * @return Cn string + */ + toAstro: function (cMonth, cDay) { + var s = '\u9b54\u7faf\u6c34\u74f6\u53cc\u9c7c\u767d\u7f8a\u91d1\u725b\u53cc\u5b50\u5de8\u87f9\u72ee\u5b50\u5904\u5973\u5929\u79e4\u5929\u874e\u5c04\u624b\u9b54\u7faf' + var arr = [20, 19, 21, 21, 21, 22, 23, 23, 23, 23, 22, 22] + return s.substr(cMonth * 2 - (cDay < arr[cMonth - 1] ? 2 : 0), 2) + '\u5ea7'// 座 + }, + + /** + * 传入offset偏移量返回干支 + * @param offset 相对甲子的偏移量 + * @return Cn string + */ + toGanZhi: function (offset) { + return this.Gan[offset % 10] + this.Zhi[offset % 12] + }, + + /** + * 传入公历(!)y年获得该年第n个节气的公历日期 + * @param y公历年(1900-2100);n二十四节气中的第几个节气(1~24);从n=1(小寒)算起 + * @return day Number + * @eg:var _24 = calendar.getTerm(1987,3) ;//_24=4;意即1987年2月4日立春 + */ + getTerm: function (y, n) { + if (y < 1900 || y > 2100) { return -1 } + if (n < 1 || n > 24) { return -1 } + var _table = this.sTermInfo[y - 1900] + var _info = [ + parseInt('0x' + _table.substr(0, 5)).toString(), + parseInt('0x' + _table.substr(5, 5)).toString(), + parseInt('0x' + _table.substr(10, 5)).toString(), + parseInt('0x' + _table.substr(15, 5)).toString(), + parseInt('0x' + _table.substr(20, 5)).toString(), + parseInt('0x' + _table.substr(25, 5)).toString() + ] + var _calday = [ + _info[0].substr(0, 1), + _info[0].substr(1, 2), + _info[0].substr(3, 1), + _info[0].substr(4, 2), + + _info[1].substr(0, 1), + _info[1].substr(1, 2), + _info[1].substr(3, 1), + _info[1].substr(4, 2), + + _info[2].substr(0, 1), + _info[2].substr(1, 2), + _info[2].substr(3, 1), + _info[2].substr(4, 2), + + _info[3].substr(0, 1), + _info[3].substr(1, 2), + _info[3].substr(3, 1), + _info[3].substr(4, 2), + + _info[4].substr(0, 1), + _info[4].substr(1, 2), + _info[4].substr(3, 1), + _info[4].substr(4, 2), + + _info[5].substr(0, 1), + _info[5].substr(1, 2), + _info[5].substr(3, 1), + _info[5].substr(4, 2) + ] + return parseInt(_calday[n - 1]) + }, + + /** + * 传入农历数字月份返回汉语通俗表示法 + * @param lunar month + * @return Cn string + * @eg:var cnMonth = calendar.toChinaMonth(12) ;//cnMonth='腊月' + */ + toChinaMonth: function (m) { // 月 => \u6708 + if (m > 12 || m < 1) { return -1 } // 若参数错误 返回-1 + var s = this.nStr3[m - 1] + s += '\u6708'// 加上月字 + return s + }, + + /** + * 传入农历日期数字返回汉字表示法 + * @param lunar day + * @return Cn string + * @eg:var cnDay = calendar.toChinaDay(21) ;//cnMonth='廿一' + */ + toChinaDay: function (d) { // 日 => \u65e5 + var s + switch (d) { + case 10: + s = '\u521d\u5341'; break + case 20: + s = '\u4e8c\u5341'; break + break + case 30: + s = '\u4e09\u5341'; break + break + default: + s = this.nStr2[Math.floor(d / 10)] + s += this.nStr1[d % 10] + } + return (s) + }, + + /** + * 年份转生肖[!仅能大致转换] => 精确划分生肖分界线是“立春” + * @param y year + * @return Cn string + * @eg:var animal = calendar.getAnimal(1987) ;//animal='兔' + */ + getAnimal: function (y) { + return this.Animals[(y - 4) % 12] + }, + + /** + * 传入阳历年月日获得详细的公历、农历object信息 <=>JSON + * @param y solar year + * @param m solar month + * @param d solar day + * @return JSON object + * @eg:console.log(calendar.solar2lunar(1987,11,01)); + */ + solar2lunar: function (y, m, d) { // 参数区间1900.1.31~2100.12.31 + // 年份限定、上限 + if (y < 1900 || y > 2100) { + return -1// undefined转换为数字变为NaN + } + // 公历传参最下限 + if (y == 1900 && m == 1 && d < 31) { + return -1 + } + // 未传参 获得当天 + if (!y) { + var objDate = new Date() + } else { + var objDate = new Date(y, parseInt(m) - 1, d) + } + var i; var leap = 0; var temp = 0 + // 修正ymd参数 + var y = objDate.getFullYear() + var m = objDate.getMonth() + 1 + var d = objDate.getDate() + var offset = (Date.UTC(objDate.getFullYear(), objDate.getMonth(), objDate.getDate()) - Date.UTC(1900, 0, 31)) / 86400000 + for (i = 1900; i < 2101 && offset > 0; i++) { + temp = this.lYearDays(i) + offset -= temp + } + if (offset < 0) { + offset += temp; i-- + } + + // 是否今天 + var isTodayObj = new Date() + var isToday = false + if (isTodayObj.getFullYear() == y && isTodayObj.getMonth() + 1 == m && isTodayObj.getDate() == d) { + isToday = true + } + // 星期几 + var nWeek = objDate.getDay() + var cWeek = this.nStr1[nWeek] + // 数字表示周几顺应天朝周一开始的惯例 + if (nWeek == 0) { + nWeek = 7 + } + // 农历年 + var year = i + var leap = this.leapMonth(i) // 闰哪个月 + var isLeap = false + + // 效验闰月 + for (i = 1; i < 13 && offset > 0; i++) { + // 闰月 + if (leap > 0 && i == (leap + 1) && isLeap == false) { + --i + isLeap = true; temp = this.leapDays(year) // 计算农历闰月天数 + } else { + temp = this.monthDays(year, i)// 计算农历普通月天数 + } + // 解除闰月 + if (isLeap == true && i == (leap + 1)) { isLeap = false } + offset -= temp + } + // 闰月导致数组下标重叠取反 + if (offset == 0 && leap > 0 && i == leap + 1) { + if (isLeap) { + isLeap = false + } else { + isLeap = true; --i + } + } + if (offset < 0) { + offset += temp; --i + } + // 农历月 + var month = i + // 农历日 + var day = offset + 1 + // 天干地支处理 + var sm = m - 1 + var gzY = this.toGanZhiYear(year) + + // 当月的两个节气 + // bugfix-2017-7-24 11:03:38 use lunar Year Param `y` Not `year` + var firstNode = this.getTerm(y, (m * 2 - 1))// 返回当月「节」为几日开始 + var secondNode = this.getTerm(y, (m * 2))// 返回当月「节」为几日开始 + + // 依据12节气修正干支月 + var gzM = this.toGanZhi((y - 1900) * 12 + m + 11) + if (d >= firstNode) { + gzM = this.toGanZhi((y - 1900) * 12 + m + 12) + } + + // 传入的日期的节气与否 + var isTerm = false + var Term = null + if (firstNode == d) { + isTerm = true + Term = this.solarTerm[m * 2 - 2] + } + if (secondNode == d) { + isTerm = true + Term = this.solarTerm[m * 2 - 1] + } + // 日柱 当月一日与 1900/1/1 相差天数 + var dayCyclical = Date.UTC(y, sm, 1, 0, 0, 0, 0) / 86400000 + 25567 + 10 + var gzD = this.toGanZhi(dayCyclical + d - 1) + // 该日期所属的星座 + var astro = this.toAstro(m, d) + + return { 'lYear': year, 'lMonth': month, 'lDay': day, 'Animal': this.getAnimal(year), 'IMonthCn': (isLeap ? '\u95f0' : '') + this.toChinaMonth(month), 'IDayCn': this.toChinaDay(day), 'cYear': y, 'cMonth': m, 'cDay': d, 'gzYear': gzY, 'gzMonth': gzM, 'gzDay': gzD, 'isToday': isToday, 'isLeap': isLeap, 'nWeek': nWeek, 'ncWeek': '\u661f\u671f' + cWeek, 'isTerm': isTerm, 'Term': Term, 'astro': astro } + }, + + /** + * 传入农历年月日以及传入的月份是否闰月获得详细的公历、农历object信息 <=>JSON + * @param y lunar year + * @param m lunar month + * @param d lunar day + * @param isLeapMonth lunar month is leap or not.[如果是农历闰月第四个参数赋值true即可] + * @return JSON object + * @eg:console.log(calendar.lunar2solar(1987,9,10)); + */ + lunar2solar: function (y, m, d, isLeapMonth) { // 参数区间1900.1.31~2100.12.1 + var isLeapMonth = !!isLeapMonth + var leapOffset = 0 + var leapMonth = this.leapMonth(y) + var leapDay = this.leapDays(y) + if (isLeapMonth && (leapMonth != m)) { return -1 }// 传参要求计算该闰月公历 但该年得出的闰月与传参的月份并不同 + if (y == 2100 && m == 12 && d > 1 || y == 1900 && m == 1 && d < 31) { return -1 }// 超出了最大极限值 + var day = this.monthDays(y, m) + var _day = day + // bugFix 2016-9-25 + // if month is leap, _day use leapDays method + if (isLeapMonth) { + _day = this.leapDays(y, m) + } + if (y < 1900 || y > 2100 || d > _day) { return -1 }// 参数合法性效验 + + // 计算农历的时间差 + var offset = 0 + for (var i = 1900; i < y; i++) { + offset += this.lYearDays(i) + } + var leap = 0; var isAdd = false + for (var i = 1; i < m; i++) { + leap = this.leapMonth(y) + if (!isAdd) { // 处理闰月 + if (leap <= i && leap > 0) { + offset += this.leapDays(y); isAdd = true + } + } + offset += this.monthDays(y, i) + } + // 转换闰月农历 需补充该年闰月的前一个月的时差 + if (isLeapMonth) { offset += day } + // 1900年农历正月一日的公历时间为1900年1月30日0时0分0秒(该时间也是本农历的最开始起始点) + var stmap = Date.UTC(1900, 1, 30, 0, 0, 0) + var calObj = new Date((offset + d - 31) * 86400000 + stmap) + var cY = calObj.getUTCFullYear() + var cM = calObj.getUTCMonth() + 1 + var cD = calObj.getUTCDate() + + return this.solar2lunar(cY, cM, cD) + } +} + +export default calendar diff --git a/uni_modules/uview-ui/libs/util/dayjs.js b/uni_modules/uview-ui/libs/util/dayjs.js new file mode 100644 index 0000000..c4efea0 --- /dev/null +++ b/uni_modules/uview-ui/libs/util/dayjs.js @@ -0,0 +1,308 @@ +!(function (t, e) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = e() : typeof define === 'function' + && define.amd ? define(e) : t.dayjs = e() +}(this, () => { + 'use strict' + + const t = 'millisecond' + const e = 'second' + const n = 'minute' + const r = 'hour' + const i = 'day' + const s = 'week' + const u = 'month' + const a = 'quarter' + const o = 'year' + const f = 'date' + const h = /^(\d{4})[-/]?(\d{1,2})?[-/]?(\d{0,2})[^0-9]*(\d{1,2})?:?(\d{1,2})?:?(\d{1,2})?.?(\d+)?$/ + const c = /\[([^\]]+)]|Y{1,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a|A|m{1,2}|s{1,2}|Z{1,2}|SSS/g + const d = { + name: 'en', + weekdays: 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_'), + months: 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_') + } + const $ = function (t, e, n) { + const r = String(t) + return !r || r.length >= e ? t : `${Array(e + 1 - r.length).join(n)}${t}` + } + const l = { + s: $, + z(t) { + const e = -t.utcOffset() + const n = Math.abs(e) + const r = Math.floor(n / 60) + const i = n % 60 + return `${(e <= 0 ? '+' : '-') + $(r, 2, '0')}:${$(i, 2, '0')}` + }, + m: function t(e, n) { + if (e.date() < n.date()) return -t(n, e) + const r = 12 * (n.year() - e.year()) + (n.month() - e.month()) + const i = e.clone().add(r, u) + const s = n - i < 0 + const a = e.clone().add(r + (s ? -1 : 1), u) + return +(-(r + (n - i) / (s ? i - a : a - i)) || 0) + }, + a(t) { + return t < 0 ? Math.ceil(t) || 0 : Math.floor(t) + }, + p(h) { + return { + M: u, + y: o, + w: s, + d: i, + D: f, + h: r, + m: n, + s: e, + ms: t, + Q: a + }[h] || String(h || '').toLowerCase().replace(/s$/, '') + }, + u(t) { + return void 0 === t + } + } + let y = 'en' + const M = {} + M[y] = d + const m = function (t) { + return t instanceof S + } + const D = function (t, e, n) { + let r + if (!t) return y + if (typeof t === 'string') M[t] && (r = t), e && (M[t] = e, r = t) + else { + const i = t.name + M[i] = t, r = i + } + return !n && r && (y = r), r || !n && y + } + const v = function (t, e) { + if (m(t)) return t.clone() + const n = typeof e === 'object' ? e : {} + return n.date = t, n.args = arguments, new S(n) + } + const g = l + g.l = D, g.i = m, g.w = function (t, e) { + return v(t, { + locale: e.$L, + utc: e.$u, + x: e.$x, + $offset: e.$offset + }) + } + var S = (function () { + function d(t) { + this.$L = D(t.locale, null, !0), this.parse(t) + } + const $ = d.prototype + return $.parse = function (t) { + this.$d = (function (t) { + const e = t.date + const n = t.utc + if (e === null) return new Date(NaN) + if (g.u(e)) return new Date() + if (e instanceof Date) return new Date(e) + if (typeof e === 'string' && !/Z$/i.test(e)) { + const r = e.match(h) + if (r) { + const i = r[2] - 1 || 0 + const s = (r[7] || '0').substring(0, 3) + return n ? new Date(Date.UTC(r[1], i, r[3] || 1, r[4] || 0, r[5] || 0, r[6] || 0, s)) : new Date(r[1], i, r[3] + || 1, r[4] || 0, r[5] || 0, r[6] || 0, s) + } + } + return new Date(e) + }(t)), this.$x = t.x || {}, this.init() + }, $.init = function () { + const t = this.$d + this.$y = t.getFullYear(), this.$M = t.getMonth(), this.$D = t.getDate(), this.$W = t.getDay(), this.$H = t.getHours(), + this.$m = t.getMinutes(), this.$s = t.getSeconds(), this.$ms = t.getMilliseconds() + }, $.$utils = function () { + return g + }, $.isValid = function () { + return !(this.$d.toString() === 'Invalid Date') + }, $.isSame = function (t, e) { + const n = v(t) + return this.startOf(e) <= n && n <= this.endOf(e) + }, $.isAfter = function (t, e) { + return v(t) < this.startOf(e) + }, $.isBefore = function (t, e) { + return this.endOf(e) < v(t) + }, $.$g = function (t, e, n) { + return g.u(t) ? this[e] : this.set(n, t) + }, $.unix = function () { + return Math.floor(this.valueOf() / 1e3) + }, $.valueOf = function () { + return this.$d.getTime() + }, $.startOf = function (t, a) { + const h = this + const c = !!g.u(a) || a + const d = g.p(t) + const $ = function (t, e) { + const n = g.w(h.$u ? Date.UTC(h.$y, e, t) : new Date(h.$y, e, t), h) + return c ? n : n.endOf(i) + } + const l = function (t, e) { + return g.w(h.toDate()[t].apply(h.toDate('s'), (c ? [0, 0, 0, 0] : [23, 59, 59, 999]).slice(e)), h) + } + const y = this.$W + const M = this.$M + const m = this.$D + const D = `set${this.$u ? 'UTC' : ''}` + switch (d) { + case o: + return c ? $(1, 0) : $(31, 11) + case u: + return c ? $(1, M) : $(0, M + 1) + case s: + var v = this.$locale().weekStart || 0 + var S = (y < v ? y + 7 : y) - v + return $(c ? m - S : m + (6 - S), M) + case i: + case f: + return l(`${D}Hours`, 0) + case r: + return l(`${D}Minutes`, 1) + case n: + return l(`${D}Seconds`, 2) + case e: + return l(`${D}Milliseconds`, 3) + default: + return this.clone() + } + }, $.endOf = function (t) { + return this.startOf(t, !1) + }, $.$set = function (s, a) { + let h; const c = g.p(s) + const d = `set${this.$u ? 'UTC' : ''}` + const $ = (h = {}, h[i] = `${d}Date`, h[f] = `${d}Date`, h[u] = `${d}Month`, h[o] = `${d}FullYear`, h[r] = `${d}Hours`, + h[n] = `${d}Minutes`, h[e] = `${d}Seconds`, h[t] = `${d}Milliseconds`, h)[c] + const l = c === i ? this.$D + (a - this.$W) : a + if (c === u || c === o) { + const y = this.clone().set(f, 1) + y.$d[$](l), y.init(), this.$d = y.set(f, Math.min(this.$D, y.daysInMonth())).$d + } else $ && this.$d[$](l) + return this.init(), this + }, $.set = function (t, e) { + return this.clone().$set(t, e) + }, $.get = function (t) { + return this[g.p(t)]() + }, $.add = function (t, a) { + let f; const + h = this + t = Number(t) + const c = g.p(a) + const d = function (e) { + const n = v(h) + return g.w(n.date(n.date() + Math.round(e * t)), h) + } + if (c === u) return this.set(u, this.$M + t) + if (c === o) return this.set(o, this.$y + t) + if (c === i) return d(1) + if (c === s) return d(7) + const $ = (f = {}, f[n] = 6e4, f[r] = 36e5, f[e] = 1e3, f)[c] || 1 + const l = this.$d.getTime() + t * $ + return g.w(l, this) + }, $.subtract = function (t, e) { + return this.add(-1 * t, e) + }, $.format = function (t) { + const e = this + if (!this.isValid()) return 'Invalid Date' + const n = t || 'YYYY-MM-DDTHH:mm:ssZ' + const r = g.z(this) + const i = this.$locale() + const s = this.$H + const u = this.$m + const a = this.$M + const o = i.weekdays + const f = i.months + const h = function (t, r, i, s) { + return t && (t[r] || t(e, n)) || i[r].substr(0, s) + } + const d = function (t) { + return g.s(s % 12 || 12, t, '0') + } + const $ = i.meridiem || function (t, e, n) { + const r = t < 12 ? 'AM' : 'PM' + return n ? r.toLowerCase() : r + } + const l = { + YY: String(this.$y).slice(-2), + YYYY: this.$y, + M: a + 1, + MM: g.s(a + 1, 2, '0'), + MMM: h(i.monthsShort, a, f, 3), + MMMM: h(f, a), + D: this.$D, + DD: g.s(this.$D, 2, '0'), + d: String(this.$W), + dd: h(i.weekdaysMin, this.$W, o, 2), + ddd: h(i.weekdaysShort, this.$W, o, 3), + dddd: o[this.$W], + H: String(s), + HH: g.s(s, 2, '0'), + h: d(1), + hh: d(2), + a: $(s, u, !0), + A: $(s, u, !1), + m: String(u), + mm: g.s(u, 2, '0'), + s: String(this.$s), + ss: g.s(this.$s, 2, '0'), + SSS: g.s(this.$ms, 3, '0'), + Z: r + } + return n.replace(c, (t, e) => e || l[t] || r.replace(':', '')) + }, $.utcOffset = function () { + return 15 * -Math.round(this.$d.getTimezoneOffset() / 15) + }, $.diff = function (t, f, h) { + let c; const d = g.p(f) + const $ = v(t) + const l = 6e4 * ($.utcOffset() - this.utcOffset()) + const y = this - $ + let M = g.m(this, $) + return M = (c = {}, c[o] = M / 12, c[u] = M, c[a] = M / 3, c[s] = (y - l) / 6048e5, c[i] = (y - l) / 864e5, c[r] = y / 36e5, c[n] = y / 6e4, c[e] = y / 1e3, c)[d] || y, h ? M : g.a(M) + }, $.daysInMonth = function () { + return this.endOf(u).$D + }, $.$locale = function () { + return M[this.$L] + }, $.locale = function (t, e) { + if (!t) return this.$L + const n = this.clone() + const r = D(t, e, !0) + return r && (n.$L = r), n + }, $.clone = function () { + return g.w(this.$d, this) + }, $.toDate = function () { + return new Date(this.valueOf()) + }, $.toJSON = function () { + return this.isValid() ? this.toISOString() : null + }, $.toISOString = function () { + return this.$d.toISOString() + }, $.toString = function () { + return this.$d.toUTCString() + }, d + }()) + const p = S.prototype + return v.prototype = p, [ + ['$ms', t], + ['$s', e], + ['$m', n], + ['$H', r], + ['$W', i], + ['$M', u], + ['$y', o], + ['$D', f] + ].forEach((t) => { + p[t[1]] = function (e) { + return this.$g(e, t[0], t[1]) + } + }), v.extend = function (t, e) { + return t.$i || (t(e, S, v), t.$i = !0), v + }, v.locale = D, v.isDayjs = m, v.unix = function (t) { + return v(1e3 * t) + }, v.en = M[y], v.Ls = M, v.p = {}, v +})) diff --git a/uni_modules/uview-ui/libs/util/emitter.js b/uni_modules/uview-ui/libs/util/emitter.js new file mode 100644 index 0000000..1e64044 --- /dev/null +++ b/uni_modules/uview-ui/libs/util/emitter.js @@ -0,0 +1,51 @@ +/** + * 递归使用 call 方式this指向 + * @param componentName // 需要找的组件的名称 + * @param eventName // 事件名称 + * @param params // 需要传递的参数 + */ +function broadcast(componentName, eventName, params) { + // 循环子节点找到名称一样的子节点 否则 递归 当前子节点 + this.$children.map((child) => { + if (componentName === child.$options.name) { + child.$emit.apply(child, [eventName].concat(params)) + } else { + broadcast.apply(child, [componentName, eventName].concat(params)) + } + }) +} +export default { + methods: { + /** + * 派发 (向上查找) (一个) + * @param componentName // 需要找的组件的名称 + * @param eventName // 事件名称 + * @param params // 需要传递的参数 + */ + dispatch(componentName, eventName, params) { + let parent = this.$parent || this.$root// $parent 找到最近的父节点 $root 根节点 + let { name } = parent.$options // 获取当前组件实例的name + // 如果当前有节点 && 当前没名称 且 当前名称等于需要传进来的名称的时候就去查找当前的节点 + // 循环出当前名称的一样的组件实例 + while (parent && (!name || name !== componentName)) { + parent = parent.$parent + if (parent) { + name = parent.$options.name + } + } + // 有节点表示当前找到了name一样的实例 + if (parent) { + parent.$emit.apply(parent, [eventName].concat(params)) + } + }, + /** + * 广播 (向下查找) (广播多个) + * @param componentName // 需要找的组件的名称 + * @param eventName // 事件名称 + * @param params // 需要传递的参数 + */ + broadcast(componentName, eventName, params) { + broadcast.call(this, componentName, eventName, params) + } + } +} diff --git a/uni_modules/uview-ui/libs/util/route.js b/uni_modules/uview-ui/libs/util/route.js new file mode 100644 index 0000000..2afeea5 --- /dev/null +++ b/uni_modules/uview-ui/libs/util/route.js @@ -0,0 +1,124 @@ +/** + * 路由跳转方法,该方法相对于直接使用uni.xxx的好处是使用更加简单快捷 + * 并且带有路由拦截功能 + */ + +class Router { + constructor() { + // 原始属性定义 + this.config = { + type: 'navigateTo', + url: '', + delta: 1, // navigateBack页面后退时,回退的层数 + params: {}, // 传递的参数 + animationType: 'pop-in', // 窗口动画,只在APP有效 + animationDuration: 300, // 窗口动画持续时间,单位毫秒,只在APP有效 + intercept: false // 是否需要拦截 + } + // 因为route方法是需要对外赋值给另外的对象使用,同时route内部有使用this,会导致route失去上下文 + // 这里在构造函数中进行this绑定 + this.route = this.route.bind(this) + } + + // 判断url前面是否有"/",如果没有则加上,否则无法跳转 + addRootPath(url) { + return url[0] === '/' ? url : `/${url}` + } + + // 整合路由参数 + mixinParam(url, params) { + url = url && this.addRootPath(url) + + // 使用正则匹配,主要依据是判断是否有"/","?","="等,如“/page/index/index?name=mary" + // 如果有url中有get参数,转换后无需带上"?" + let query = '' + if (/.*\/.*\?.*=.*/.test(url)) { + // object对象转为get类型的参数 + query = uni.$u.queryParams(params, false) + // 因为已有get参数,所以后面拼接的参数需要带上"&"隔开 + return url += `&${query}` + } + // 直接拼接参数,因为此处url中没有后面的query参数,也就没有"?/&"之类的符号 + query = uni.$u.queryParams(params) + return url += query + } + + // 对外的方法名称 + async route(options = {}, params = {}) { + // 合并用户的配置和内部的默认配置 + let mergeConfig = {} + + if (typeof options === 'string') { + // 如果options为字符串,则为route(url, params)的形式 + mergeConfig.url = this.mixinParam(options, params) + mergeConfig.type = 'navigateTo' + } else { + mergeConfig = uni.$u.deepMerge(this.config, options) + // 否则正常使用mergeConfig中的url和params进行拼接 + mergeConfig.url = this.mixinParam(options.url, options.params) + } + + // 如果本次跳转的路径和本页面路径一致,不执行跳转,防止用户快速点击跳转按钮,造成多次跳转同一个页面的问题 + if (mergeConfig.url === uni.$u.page()) return + + if (params.intercept) { + this.config.intercept = params.intercept + } + // params参数也带给拦截器 + mergeConfig.params = params + // 合并内外部参数 + mergeConfig = uni.$u.deepMerge(this.config, mergeConfig) + // 判断用户是否定义了拦截器 + if (typeof uni.$u.routeIntercept === 'function') { + // 定一个promise,根据用户执行resolve(true)或者resolve(false)来决定是否进行路由跳转 + const isNext = await new Promise((resolve, reject) => { + uni.$u.routeIntercept(mergeConfig, resolve) + }) + // 如果isNext为true,则执行路由跳转 + isNext && this.openPage(mergeConfig) + } else { + this.openPage(mergeConfig) + } + } + + // 执行路由跳转 + openPage(config) { + // 解构参数 + const { + url, + type, + delta, + animationType, + animationDuration + } = config + if (config.type == 'navigateTo' || config.type == 'to') { + uni.navigateTo({ + url, + animationType, + animationDuration + }) + } + if (config.type == 'redirectTo' || config.type == 'redirect') { + uni.redirectTo({ + url + }) + } + if (config.type == 'switchTab' || config.type == 'tab') { + uni.switchTab({ + url + }) + } + if (config.type == 'reLaunch' || config.type == 'launch') { + uni.reLaunch({ + url + }) + } + if (config.type == 'navigateBack' || config.type == 'back') { + uni.navigateBack({ + delta + }) + } + } +} + +export default (new Router()).route diff --git a/uni_modules/uview-ui/package.json b/uni_modules/uview-ui/package.json new file mode 100644 index 0000000..e1169e5 --- /dev/null +++ b/uni_modules/uview-ui/package.json @@ -0,0 +1,84 @@ +{ + "id": "uview-ui", + "name": "uview-ui", + "displayName": "uView2.0重磅发布,利剑出鞘,一统江湖", + "version": "2.0.36", + "description": "uView UI已完美兼容nvue,全面的组件和便捷的工具会让您信手拈来,如鱼得水", + "keywords": [ + "uview", + "uview", + "ui", + "ui", + "uni-app", + "uni-app", + "ui" + ], + "repository": "https://github.com/umicro/uView2.0", + "engines": { + "HBuilderX": "^3.1.0" + }, + "dcloudext": { + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "1416956117" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/uview-ui", + "type": "component-vue" + }, + "uni_modules": { + "dependencies": [], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "Vue": { + "vue2": "y", + "vue3": "n" + }, + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "y", + "联盟": "y" + } + } + } + } +} diff --git a/uni_modules/uview-ui/theme.scss b/uni_modules/uview-ui/theme.scss new file mode 100644 index 0000000..331b30f --- /dev/null +++ b/uni_modules/uview-ui/theme.scss @@ -0,0 +1,44 @@ +// 此文件为uView的主题变量,这些变量目前只能通过uni.scss引入才有效,另外由于 +// uni.scss中引入的样式会同时混入到全局样式文件和单独每一个页面的样式中,造成微信程序包太大, +// 故uni.scss只建议放scss变量名相关样式,其他的样式可以通过main.js或者App.vue引入 + +$u-main-color: #303133; +$u-content-color: #606266; +$u-tips-color: #909193; +$u-light-color: #c0c4cc; +$u-border-color: #dadbde; +$u-bg-color: #f3f4f6; +$u-disabled-color: #c8c9cc; + +$u-primary: #3c9cff; +$u-primary-dark: #398ade; +$u-primary-disabled: #9acafc; +$u-primary-light: #ecf5ff; + +$u-warning: #f9ae3d; +$u-warning-dark: #f1a532; +$u-warning-disabled: #f9d39b; +$u-warning-light: #fdf6ec; + +$u-success: #5ac725; +$u-success-dark: #53c21d; +$u-success-disabled: #a9e08f; +$u-success-light: #f5fff0; + +$u-error: #f56c6c; +$u-error-dark: #e45656; +$u-error-disabled: #f7b2b2; +$u-error-light: #fef0f0; + +$u-info: #909399; +$u-info-dark: #767a82; +$u-info-disabled: #c4c6c9; +$u-info-light: #f4f4f5; + +// scss混入,为了少写几行#ifndef +@mixin flex($direction: row) { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: $direction; +} diff --git a/uni_modules/z-paging/changelog.md b/uni_modules/z-paging/changelog.md new file mode 100644 index 0000000..8f734fb --- /dev/null +++ b/uni_modules/z-paging/changelog.md @@ -0,0 +1,83 @@ +## 2.7.12(2024-09-22) +1.`新增` 虚拟列表+吸顶演示(一般写法)演示&`@virtualPlaceholderTopHeight`,用于监听虚拟列表顶部占位高度改变。 +2.`新增` 聊天记录模式+虚拟列表演示&虚拟列表兼容说明。 +3.`新增` 聊天记录模式中长按cell显示操作pop的示例和说明。 +4.`修复` nvue中底部固定加载更多高度未跟随unit适配的问题。 +5.`修复` 在iOS中可能出现的本地分页空白的问题。 +6.`修复` `to-bottom-loading-more-enabled` 无效的问题。 +7.`优化` 在聊天记录模式中,点击返回顶部按钮调整为点击返回底部。 +8.`优化` `refresher-enabled`设置为false时调用`reload(true)`也允许显示下拉刷新view。 +## 2.7.11(2024-06-28) +1.`新增` 方法`updateVirtualListRender`,支持手动触发虚拟列表渲染更新。 +2.`新增` 延迟加载列表演示。 +3.`修复` v2.7.8引出的vue3+npm+微信小程序中,`uni.$zp`配置失效的问题。 +4.`修复` 在本地分页+虚拟列表情况下,虚拟列表cell未被正常销毁的问题。 +5.`修复` 打开调试模式下无法获取getApp导致的`cannot read property 'zp_handleQueryCallback' of undefined`的报错。 +6.`修复` 极小概率出现的分页请求较快且快速滚动到底部时未能加载更多的问题。 +## 2.7.10(2024-05-10) +1.`修复` v2.7.8引出的vue3+npm+微信小程序中,uni.$zp配置失效的问题。 +## 2.7.9(2024-05-10) +1.`修复` 在新版HbuilderX+vue3+微信小程序中可能出现的加载第二页数据后返回顶部无法下拉的问题。 +2.`修复` 在vue3+抖音小程序中可能出现的首次加载reload没有触发的问题。 +3.`优化` ts类型中,`ZPagingInstance`泛型不必填,默认为`any`。 +## 2.7.8(2024-05-09) +1.`新增` typescript类型声明文件,感谢小何同学提供。 +2.`新增` 添加极简写法fetch相关配置及示例。 +3.`新增` `fixed-cellHeight`配置,支持在虚拟列表中自定义固定的cell高度。 +4.`新增` `refresher-refreshing-scrollable`配置,支持自定义下拉刷新中是否允许列表滚动。 +5.`修复` 在新版HubilderX+vue3+h5中,`swiper-demo`模式`slot=top`被导航栏遮挡的问题。 +6.`修复` 下拉进入二楼偶现的二楼高度未铺满全屏的问题。 +7.`修复` 虚拟列表中complete若传相同对象会导致key重复的问题。 +8.`修复` 在虚拟列表中调用refresh后缓存高度原始数据未清空的问题。 +9.`修复` 聊天记录模式删除记录时顶部显示loading的问题。 +9.`优化` `scrollIntoViewByIndex`支持在虚拟列表中滚动到指定cell。 +10.`优化` `content-z-index`默认值修改为1。 +## 2.7.7(2024-04-01) +1.`新增` 下拉进入二楼功能及相关配置&demo。 +2.`新增` 虚拟列表写法添加【非内置列表】写法,可良好兼容vue3中的各平台并有较优的性能表现。 +3.`新增` `z-paging-cell`补充`@touchstart`事件。 +4.`修复` 页面滚动模式设置了`auto-full-height`后可能出现的依然有异常空白占位的问题和下拉刷新时列表数据被切割的问题。 +## 2.7.6(2024-02-29) +1.`新增` `max-width`,支持设置`z-paging`的最大宽度,默认`z-paging`宽度铺满窗口。 +2.`新增` `chat-adjust-position-offset`,支持设置使用聊天记录模式中键盘弹出时占位高度偏移距离。 +3.`修复` 由于renderjs中聊天记录模式判断不准确导致的可能出现的从聊天记录页面跳转到其他页面后返回页面无法滚动的问题。 +4.`修复` 聊天记录模式首次加载失败后,发送消息顶部会显示加载失败文字的问题。 +5.`修复` 聊天记录模式nvue可能出现的键盘弹出无法滚动到底部的问题。 +6.`修复` 聊天记录模式+nvue滚动条无法展示的问题&底部会显示加载中的问题。 +7.`修复` 聊天记录模式监听键盘弹出可能出现的无法正常销毁的问题。 +8.`修复` 直接修改dataList的值组件内部的值未更新的问题。 +## 2.7.5(2024-01-23) +1.`新增` props:`chat-loading-more-default-as-loading`,支持设置在聊天记录模式中滑动到顶部状态为默认状态时,以加载中的状态展示。 +2.`新增` slots:`chatNoMore`,支持自定义聊天记录模式没有更多数据view。 +3.`修复` 固定在底部view可能出现默认黄色的问题。 +4.`优化` 聊天记录加载更多样式,与普通模式对齐,支持点击加载更多&点击重试,并支持加载更多相关配置。 +5.`优化` 微调下拉刷新和底部加载更多样式。 +6.`优化` 聊天记录模式自动滚动到底部添加延时以避免可能出现的滚动到底部位置不正确的问题。 +7.`优化` 使用新的判断滚动到顶部算法以解决在安卓设备中可能出现的因滚动到顶部时scrollTop不为0导致的下拉刷新被禁用的问题。 +## 2.7.4(2024-01-14) +1.`新增` props:`auto-adjust-position-when-chat`,支持设置使用聊天记录模式中键盘弹出时是否自动调整slot="bottom"高度。 +2.`新增` props:`auto-to-bottom-when-chat`,支持设置使用聊天记录模式中键盘弹出时是否自动滚动到底部。 +3.`新增` props:`show-chat-loading-when-reload`,支持设置使用聊天记录模式中reload时是否显示chatLoading。 +4.`修复` 在聊天记录模式中`scrollIntoViewById`和`scrollIntoViewByNodeTop`无效的问题。 +5.`优化` 聊天记录模式底部安全区域针对键盘开启/关闭兼容处理。 +6.`优化` 更新内置的空数据图&加载失败图,感谢图鸟UI提供的免费可商用的空数据图和加载失败图! +## 2.7.3(2024-01-10) +1.`新增` 聊天记录模式支持虚拟列表&添加相关demo。 +2.`新增` nvue中list添加`@scrollend`监听。 +3.`优化` 聊天记录模式+vue第一页并且没有更多时不倒置列表。 +4.`优化` 聊天记录模式+nvue中数据不满屏时默认从顶部开始,不进行列表倒置。 +## 2.7.2(2024-01-09) +1.`修复` `vue3+h5`中报错`uni.onKeyboardHeightChange is not a function`的问题。 +2.`优化` 聊天记录模式细节:表情面板在触摸列表时隐藏&添加隐藏动画。 +## 2.7.1(2024-01-08) +1.`新增` `keyboardHeightChange` event,支持监听键盘高度改变。 +2.`新增` 聊天记录模式新增切换表情面板/键盘demo。 +3.`优化` 键盘弹出占位添加动画效果。 +## 2.7.0(2024-01-07) +2024新年快乐!!祝大家在新的一年里工作顺利,事事顺心! +1.`新增` 全新的聊天记录模式设计!将vue中的聊天记录模式与nvue中对齐,完全解决了聊天记录模式滚动到顶部加载更多在vue中抖动的问题,同时将聊天记录模式键盘自动弹出自动上推页面交由`z-paging`处理,解决了由此引发的各种问题,尤其是在微信小程序中导航栏被键盘顶出屏幕外的问题。如果您使用了`z-paging`的聊天记录模式,强烈建议更新,写法有一定变更,具体请参见demo。 +2.`新增` `swiper-demo`新增`onShow`时候调用reload演示。 +3.`修复` 修复滚动相关方法在微信小程序中首次滚动动画无效的问题。 +4.`修复` props设置单位,单位为px时报错的问题。 +5.`修复` 在某些情况下`z-paging`加载了但是未渲染时,reload无效的问题。 +6.`修复` 底部loading动画未生效的问题。 diff --git a/uni_modules/z-paging/components/z-paging-cell/z-paging-cell.vue b/uni_modules/z-paging/components/z-paging-cell/z-paging-cell.vue new file mode 100644 index 0000000..78ac633 --- /dev/null +++ b/uni_modules/z-paging/components/z-paging-cell/z-paging-cell.vue @@ -0,0 +1,39 @@ + + + + + + + + + + diff --git a/uni_modules/z-paging/components/z-paging-empty-view/z-paging-empty-view.vue b/uni_modules/z-paging/components/z-paging-empty-view/z-paging-empty-view.vue new file mode 100644 index 0000000..47f5c13 --- /dev/null +++ b/uni_modules/z-paging/components/z-paging-empty-view/z-paging-empty-view.vue @@ -0,0 +1,192 @@ + + + + + + + + + + + diff --git a/uni_modules/z-paging/components/z-paging-swiper-item/z-paging-swiper-item.vue b/uni_modules/z-paging/components/z-paging-swiper-item/z-paging-swiper-item.vue new file mode 100644 index 0000000..64a7bb0 --- /dev/null +++ b/uni_modules/z-paging/components/z-paging-swiper-item/z-paging-swiper-item.vue @@ -0,0 +1,143 @@ + + + + + + + + + + + diff --git a/uni_modules/z-paging/components/z-paging-swiper/z-paging-swiper.vue b/uni_modules/z-paging/components/z-paging-swiper/z-paging-swiper.vue new file mode 100644 index 0000000..e965bed --- /dev/null +++ b/uni_modules/z-paging/components/z-paging-swiper/z-paging-swiper.vue @@ -0,0 +1,167 @@ + + + + + + + + + + + diff --git a/uni_modules/z-paging/components/z-paging/components/z-paging-load-more.vue b/uni_modules/z-paging/components/z-paging/components/z-paging-load-more.vue new file mode 100644 index 0000000..04ad799 --- /dev/null +++ b/uni_modules/z-paging/components/z-paging/components/z-paging-load-more.vue @@ -0,0 +1,178 @@ + + + + + diff --git a/uni_modules/z-paging/components/z-paging/components/z-paging-refresh.vue b/uni_modules/z-paging/components/z-paging/components/z-paging-refresh.vue new file mode 100644 index 0000000..4a7ca61 --- /dev/null +++ b/uni_modules/z-paging/components/z-paging/components/z-paging-refresh.vue @@ -0,0 +1,208 @@ + + + + + diff --git a/uni_modules/z-paging/components/z-paging/config/index.js b/uni_modules/z-paging/components/z-paging/config/index.js new file mode 100644 index 0000000..15a37e2 --- /dev/null +++ b/uni_modules/z-paging/components/z-paging/config/index.js @@ -0,0 +1,3 @@ +// z-paging全局配置文件,注意避免更新时此文件被覆盖,若被覆盖,可在此文件中右键->点击本地历史记录,找回覆盖前的配置 + +export default {} \ No newline at end of file diff --git a/uni_modules/z-paging/components/z-paging/css/z-paging-main.css b/uni_modules/z-paging/components/z-paging/css/z-paging-main.css new file mode 100644 index 0000000..b0a6393 --- /dev/null +++ b/uni_modules/z-paging/components/z-paging/css/z-paging-main.css @@ -0,0 +1,244 @@ +/* [z-paging]公共css*/ + +.z-paging-content { + position: relative; + flex-direction: column; + /* #ifndef APP-NVUE */ + overflow: hidden; + /* #endif */ +} + +.z-paging-content-full { + /* #ifndef APP-NVUE */ + display: flex; + width: 100%; + height: 100%; + /* #endif */ +} + +.z-paging-content-fixed, .zp-loading-fixed { + position: fixed; + /* #ifndef APP-NVUE */ + height: auto; + width: auto; + /* #endif */ + top: 0; + left: 0; + bottom: 0; + right: 0; +} + +.zp-f2-content { + width: 100%; + position: fixed; + top: 0; + left: 0; + background-color: white; +} + +.zp-page-top, .zp-page-bottom { + /* #ifndef APP-NVUE */ + width: auto; + /* #endif */ + position: fixed; + left: 0; + right: 0; + z-index: 999; +} + +.zp-page-left, .zp-page-right { + /* #ifndef APP-NVUE */ + height: 100%; + /* #endif */ +} + +.zp-scroll-view-super { + flex: 1; + overflow: hidden; + position: relative; +} + +.zp-view-super { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: row; +} + +.zp-scroll-view-container, .zp-scroll-view { + position: relative; + /* #ifndef APP-NVUE */ + height: 100%; + width: 100%; + /* #endif */ +} + +.zp-absoulte { + /* #ifndef APP-NVUE */ + position: absolute; + top: 0; + width: auto; + /* #endif */ +} + +.zp-scroll-view-absolute { + position: absolute; + top: 0; + left: 0; +} + +/* #ifndef APP-NVUE */ +.zp-scroll-view-hide-scrollbar ::-webkit-scrollbar { + display: none; + -webkit-appearance: none; + width: 0 !important; + height: 0 !important; + background: transparent; +} +/* #endif */ + +.zp-paging-touch-view { + width: 100%; + height: 100%; + position: relative; +} + +.zp-fixed-bac-view { + position: absolute; + width: 100%; + top: 0; + left: 0; + height: 200px; +} + +.zp-paging-main { + height: 100%; + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: column; +} + +.zp-paging-container { + flex: 1; + position: relative; + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: column; +} + +.zp-chat-record-loading-custom-image { + width: 35rpx; + height: 35rpx; + /* #ifndef APP-NVUE */ + animation: loading-flower 1s linear infinite; + /* #endif */ +} + +.zp-page-bottom-keyboard-placeholder-animate { + transition-property: height; + transition-duration: 0.15s; + /* #ifndef APP-NVUE */ + will-change: height; + /* #endif */ +} + +.zp-custom-refresher-container { + overflow: hidden; +} + +.zp-custom-refresher-refresh { + /* #ifndef APP-NVUE */ + display: block; + /* #endif */ +} + +.zp-back-to-top { + z-index: 999; + position: absolute; + bottom: 0rpx; + transition-duration: .3s; + transition-property: opacity; +} +.zp-back-to-top-rpx { + width: 76rpx; + height: 76rpx; + bottom: 0rpx; + right: 25rpx; +} +.zp-back-to-top-px { + width: 38px; + height: 38px; + bottom: 0px; + right: 13px; +} + +.zp-back-to-top-show { + opacity: 1; +} + +.zp-back-to-top-hide { + opacity: 0; +} + +.zp-back-to-top-img { + /* #ifndef APP-NVUE */ + width: 100%; + height: 100%; + /* #endif */ + /* #ifdef APP-NVUE */ + flex: 1; + /* #endif */ + z-index: 999; +} + +.zp-back-to-top-img-inversion { + transform: rotate(180deg); +} + +.zp-empty-view { + /* #ifdef APP-NVUE */ + height: 100%; + /* #endif */ + flex: 1; + margin: 0 24rpx 24rpx 24rpx; + background-color: #fff; + border-bottom-left-radius: 12rpx;border-bottom-right-radius: 12rpx; +} + +.zp-empty-view-center { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: column; + align-items: center; + justify-content: center; +} + +.zp-loading-fixed { + z-index: 9999; +} + +.zp-safe-area-inset-bottom { + position: absolute; + /* #ifndef APP-PLUS */ + height: env(safe-area-inset-bottom); + /* #endif */ +} + +.zp-n-refresh-container { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + justify-content: center; + width: 750rpx; +} + +.zp-n-list-container{ + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: row; + flex: 1; +} diff --git a/uni_modules/z-paging/components/z-paging/css/z-paging-static.css b/uni_modules/z-paging/components/z-paging/css/z-paging-static.css new file mode 100644 index 0000000..de445c2 --- /dev/null +++ b/uni_modules/z-paging/components/z-paging/css/z-paging-static.css @@ -0,0 +1,50 @@ +/* [z-paging]公用的静态css资源 */ + +.zp-line-loading-image { + /* #ifndef APP-NVUE */ + animation: loading-flower 1s steps(12) infinite; + /* #endif */ + color: #666666; +} +.zp-line-loading-image-rpx { + margin-right: 8rpx; + width: 34rpx; + height: 34rpx; +} +.zp-line-loading-image-px { + margin-right: 4px; + width: 17px; + height: 17px; +} + +.zp-loading-image-ios-rpx { + width: 40rpx; + height: 40rpx; +} +.zp-loading-image-ios-px { + width: 20px; + height: 20px; +} + +.zp-loading-image-android-rpx { + width: 34rpx; + height: 34rpx; +} +.zp-loading-image-android-px { + width: 17px; + height: 17px; +} + +/* #ifndef APP-NVUE */ +@keyframes loading-flower { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + to { + -webkit-transform: rotate(1turn); + transform: rotate(1turn); + } +} +/* #endif */ + diff --git a/uni_modules/z-paging/components/z-paging/i18n/en.json b/uni_modules/z-paging/components/z-paging/i18n/en.json new file mode 100644 index 0000000..48fbe60 --- /dev/null +++ b/uni_modules/z-paging/components/z-paging/i18n/en.json @@ -0,0 +1,23 @@ +{ + "zp.refresher.default": "Pull down to refresh", + "zp.refresher.pulling": "Release to refresh", + "zp.refresher.refreshing": "Refreshing...", + "zp.refresher.complete": "Refresh succeeded", + "zp.refresher.f2": "Refresh to enter 2f", + + "zp.loadingMore.default": "Click to load more", + "zp.loadingMore.loading": "Loading...", + "zp.loadingMore.noMore": "No more data", + "zp.loadingMore.fail": "Load failed,click to reload", + + "zp.emptyView.title": "No data", + "zp.emptyView.reload": "Reload", + "zp.emptyView.error": "Sorry,load failed", + + "zp.refresherUpdateTime.title": "Last update: ", + "zp.refresherUpdateTime.none": "None", + "zp.refresherUpdateTime.today": "Today", + "zp.refresherUpdateTime.yesterday": "Yesterday", + + "zp.systemLoading.title": "Loading..." +} diff --git a/uni_modules/z-paging/components/z-paging/i18n/index.js b/uni_modules/z-paging/components/z-paging/i18n/index.js new file mode 100644 index 0000000..de7509c --- /dev/null +++ b/uni_modules/z-paging/components/z-paging/i18n/index.js @@ -0,0 +1,8 @@ +import en from './en.json' +import zhHans from './zh-Hans.json' +import zhHant from './zh-Hant.json' +export default { + en, + 'zh-Hans': zhHans, + 'zh-Hant': zhHant +} diff --git a/uni_modules/z-paging/components/z-paging/i18n/zh-Hans.json b/uni_modules/z-paging/components/z-paging/i18n/zh-Hans.json new file mode 100644 index 0000000..708becc --- /dev/null +++ b/uni_modules/z-paging/components/z-paging/i18n/zh-Hans.json @@ -0,0 +1,23 @@ +{ + "zp.refresher.default": "继续下拉刷新", + "zp.refresher.pulling": "松开立即刷新", + "zp.refresher.refreshing": "正在刷新...", + "zp.refresher.complete": "刷新成功", + "zp.refresher.f2": "松手进入二楼", + + "zp.loadingMore.default": "点击加载更多", + "zp.loadingMore.loading": "正在加载...", + "zp.loadingMore.noMore": "没有更多了", + "zp.loadingMore.fail": "加载失败,点击重新加载", + + "zp.emptyView.title": "暂无数据", + "zp.emptyView.reload": "重新加载", + "zp.emptyView.error": "很抱歉,加载失败", + + "zp.refresherUpdateTime.title": "最后更新:", + "zp.refresherUpdateTime.none": "无", + "zp.refresherUpdateTime.today": "今天", + "zp.refresherUpdateTime.yesterday": "昨天", + + "zp.systemLoading.title": "加载中..." +} diff --git a/uni_modules/z-paging/components/z-paging/i18n/zh-Hant.json b/uni_modules/z-paging/components/z-paging/i18n/zh-Hant.json new file mode 100644 index 0000000..b3d5502 --- /dev/null +++ b/uni_modules/z-paging/components/z-paging/i18n/zh-Hant.json @@ -0,0 +1,23 @@ +{ + "zp.refresher.default": "繼續下拉重繪", + "zp.refresher.pulling": "鬆開立即重繪", + "zp.refresher.refreshing": "正在重繪...", + "zp.refresher.complete": "重繪成功", + "zp.refresher.f2": "鬆手進入二樓", + + "zp.loadingMore.default": "點擊加載更多", + "zp.loadingMore.loading": "正在加載...", + "zp.loadingMore.noMore": "沒有更多了", + "zp.loadingMore.fail": "加載失敗,點擊重新加載", + + "zp.emptyView.title": "沒有數據哦~", + "zp.emptyView.reload": "重新加載", + "zp.emptyView.error": "很抱歉,加載失敗", + + "zp.refresherUpdateTime.title": "最後更新:", + "zp.refresherUpdateTime.none": "無", + "zp.refresherUpdateTime.today": "今天", + "zp.refresherUpdateTime.yesterday": "昨天", + + "zp.systemLoading.title": "加載中..." +} diff --git a/uni_modules/z-paging/components/z-paging/js/hooks/useZPaging.js b/uni_modules/z-paging/components/z-paging/js/hooks/useZPaging.js new file mode 100644 index 0000000..adb6e1b --- /dev/null +++ b/uni_modules/z-paging/components/z-paging/js/hooks/useZPaging.js @@ -0,0 +1,25 @@ +// [z-paging]useZPaging hooks + +import { onPageScroll, onReachBottom, onPullDownRefresh } from '@dcloudio/uni-app'; + +function useZPaging(paging) { + const cPaging = !!paging ? paging.value || paging : null; + + onPullDownRefresh(() => { + if (!cPaging || !cPaging.value) return; + cPaging.value.reload().catch(() => {}); + }) + + onPageScroll(e => { + if (!cPaging || !cPaging.value) return; + cPaging.value.updatePageScrollTop(e.scrollTop); + e.scrollTop < 10 && cPaging.value.doChatRecordLoadMore(); + }) + + onReachBottom(() => { + if (!cPaging || !cPaging.value) return; + cPaging.value.pageReachBottom(); + }) +} + +export default useZPaging \ No newline at end of file diff --git a/uni_modules/z-paging/components/z-paging/js/hooks/useZPagingComp.js b/uni_modules/z-paging/components/z-paging/js/hooks/useZPagingComp.js new file mode 100644 index 0000000..f9bc5ab --- /dev/null +++ b/uni_modules/z-paging/components/z-paging/js/hooks/useZPagingComp.js @@ -0,0 +1,25 @@ +// [z-paging]useZPagingComp hooks + +function useZPagingComp(paging) { + const cPaging = !!paging ? paging.value || paging : null; + + const reload = () => { + if (!cPaging || !cPaging.value) return; + cPaging.value.reload().catch(() => {}); + } + const updatePageScrollTop = scrollTop => { + if (!cPaging || !cPaging.value) return; + cPaging.value.updatePageScrollTop(scrollTop); + } + const doChatRecordLoadMore = () => { + if (!cPaging || !cPaging.value) return; + cPaging.value.doChatRecordLoadMore(); + } + const pageReachBottom = () => { + if (!cPaging || !cPaging.value) return; + cPaging.value.pageReachBottom(); + } + return { reload, updatePageScrollTop, doChatRecordLoadMore, pageReachBottom }; +} + +export default useZPagingComp \ No newline at end of file diff --git a/uni_modules/z-paging/components/z-paging/js/modules/back-to-top.js b/uni_modules/z-paging/components/z-paging/js/modules/back-to-top.js new file mode 100644 index 0000000..4e484d0 --- /dev/null +++ b/uni_modules/z-paging/components/z-paging/js/modules/back-to-top.js @@ -0,0 +1,125 @@ +// [z-paging]点击返回顶部view模块 +import u from '.././z-paging-utils' + +export default { + props: { + // 自动显示点击返回顶部按钮,默认为否 + autoShowBackToTop: { + type: Boolean, + default: u.gc('autoShowBackToTop', false) + }, + // 点击返回顶部按钮显示/隐藏的阈值(滚动距离),单位为px,默认为400rpx + backToTopThreshold: { + type: [Number, String], + default: u.gc('backToTopThreshold', '400rpx') + }, + // 点击返回顶部按钮的自定义图片地址,默认使用z-paging内置的图片 + backToTopImg: { + type: String, + default: u.gc('backToTopImg', '') + }, + // 点击返回顶部按钮返回到顶部时是否展示过渡动画,默认为是 + backToTopWithAnimate: { + type: Boolean, + default: u.gc('backToTopWithAnimate', true) + }, + // 点击返回顶部按钮与底部的距离,注意添加单位px或rpx,默认为160rpx + backToTopBottom: { + type: [Number, String], + default: u.gc('backToTopBottom', '160rpx') + }, + // 点击返回顶部按钮的自定义样式 + backToTopStyle: { + type: Object, + default: u.gc('backToTopStyle', {}), + }, + // iOS点击顶部状态栏、安卓双击标题栏时,滚动条返回顶部,只支持竖向,默认为是 + enableBackToTop: { + type: Boolean, + default: u.gc('enableBackToTop', true) + }, + }, + data() { + return { + // 点击返回顶部的class + backToTopClass: 'zp-back-to-top zp-back-to-top-hide', + // 上次点击返回顶部的时间 + lastBackToTopShowTime: 0, + // 点击返回顶部显示的class是否在展示中,使得按钮展示/隐藏过度效果更自然 + showBackToTopClass: false, + } + }, + computed: { + backToTopThresholdUnitConverted() { + return u.addUnit(this.backToTopThreshold, this.unit); + }, + backToTopBottomUnitConverted() { + return u.addUnit(this.backToTopBottom, this.unit); + }, + finalEnableBackToTop() { + return this.usePageScroll ? false : this.enableBackToTop; + }, + finalBackToTopThreshold() { + return u.convertToPx(this.backToTopThresholdUnitConverted); + }, + finalBackToTopStyle() { + const backToTopStyle = this.backToTopStyle; + if (!backToTopStyle.bottom) { + backToTopStyle.bottom = this.windowBottom + u.convertToPx(this.backToTopBottomUnitConverted) + 'px'; + } + if(!backToTopStyle.position){ + backToTopStyle.position = this.usePageScroll ? 'fixed': 'absolute'; + } + return backToTopStyle; + }, + finalBackToTopClass() { + return `${this.backToTopClass} zp-back-to-top-${this.unit}`; + } + }, + methods: { + // 点击了返回顶部 + _backToTopClick() { + let callbacked = false; + this.$emit('backToTopClick', toTop => { + (toTop === undefined || toTop === true) && this._handleToTop(); + callbacked = true; + }); + // 如果用户没有禁止默认的返回顶部事件,则触发滚动到顶部 + this.$nextTick(() => { + !callbacked && this._handleToTop(); + }) + }, + // 处理滚动到顶部(聊天记录模式中为滚动到底部) + _handleToTop() { + !this.backToTopWithAnimate && this._checkShouldShowBackToTop(0); + !this.useChatRecordMode ? this.scrollToTop(this.backToTopWithAnimate) : this.scrollToBottom(this.backToTopWithAnimate); + }, + // 判断是否要显示返回顶部按钮 + _checkShouldShowBackToTop(scrollTop) { + if (!this.autoShowBackToTop) { + this.showBackToTopClass = false; + return; + } + if (scrollTop > this.finalBackToTopThreshold) { + if (!this.showBackToTopClass) { + // 记录当前点击返回顶部按钮显示的class生效了 + this.showBackToTopClass = true; + this.lastBackToTopShowTime = new Date().getTime(); + // 当滚动到需要展示返回顶部的阈值内,则延迟300毫秒展示返回到顶部按钮 + u.delay(() => { + this.backToTopClass = 'zp-back-to-top zp-back-to-top-show'; + }, 300) + } + } else { + // 如果当前点击返回顶部按钮显示的class是生效状态并且滚动小于触发阈值,则隐藏返回顶部按钮 + if (this.showBackToTopClass) { + this.backToTopClass = 'zp-back-to-top zp-back-to-top-hide'; + u.delay(() => { + this.showBackToTopClass = false; + }, new Date().getTime() - this.lastBackToTopShowTime < 500 ? 0 : 300) + } + } + }, + } +} + diff --git a/uni_modules/z-paging/components/z-paging/js/modules/chat-record-mode.js b/uni_modules/z-paging/components/z-paging/js/modules/chat-record-mode.js new file mode 100644 index 0000000..daacf0c --- /dev/null +++ b/uni_modules/z-paging/components/z-paging/js/modules/chat-record-mode.js @@ -0,0 +1,149 @@ +// [z-paging]聊天记录模式模块 +import u from '.././z-paging-utils' + +export default { + props: { + // 使用聊天记录模式,默认为否 + useChatRecordMode: { + type: Boolean, + default: u.gc('useChatRecordMode', false) + }, + // 使用聊天记录模式时滚动到顶部后,列表垂直移动偏移距离。默认0rpx。单位px(暂时无效) + chatRecordMoreOffset: { + type: [Number, String], + default: u.gc('chatRecordMoreOffset', '0rpx') + }, + // 使用聊天记录模式时是否自动隐藏键盘:在用户触摸列表时候自动隐藏键盘,默认为是 + autoHideKeyboardWhenChat: { + type: Boolean, + default: u.gc('autoHideKeyboardWhenChat', true) + }, + // 使用聊天记录模式中键盘弹出时是否自动调整slot="bottom"高度,默认为是 + autoAdjustPositionWhenChat: { + type: Boolean, + default: u.gc('autoAdjustPositionWhenChat', true) + }, + // 使用聊天记录模式中键盘弹出时占位高度偏移距离。默认0rpx。单位px + chatAdjustPositionOffset: { + type: [Number, String], + default: u.gc('chatAdjustPositionOffset', '0rpx') + }, + // 使用聊天记录模式中键盘弹出时是否自动滚动到底部,默认为否 + autoToBottomWhenChat: { + type: Boolean, + default: u.gc('autoToBottomWhenChat', false) + }, + // 使用聊天记录模式中reload时是否显示chatLoading,默认为否 + showChatLoadingWhenReload: { + type: Boolean, + default: u.gc('showChatLoadingWhenReload', false) + }, + // 在聊天记录模式中滑动到顶部状态为默认状态时,以加载中的状态展示,默认为是。若设置为否,则默认会显示【点击加载更多】,然后才会显示loading + chatLoadingMoreDefaultAsLoading: { + type: Boolean, + default: u.gc('chatLoadingMoreDefaultAsLoading', true) + }, + }, + data() { + return { + // 键盘高度 + keyboardHeight: 0, + // 键盘高度是否未改变,此时占位高度变化不需要动画效果 + isKeyboardHeightChanged: false, + } + }, + computed: { + finalChatRecordMoreOffset() { + return u.convertToPx(this.chatRecordMoreOffset); + }, + finalChatAdjustPositionOffset() { + return u.convertToPx(this.chatAdjustPositionOffset); + }, + // 聊天记录模式旋转180度style + chatRecordRotateStyle() { + let cellStyle; + // 在vue中,直接将列表倒置,因此在vue的cell中,也直接写style="transform: scaleY(-1)"转回来即可。 + // #ifndef APP-NVUE + cellStyle = this.useChatRecordMode ? { transform: 'scaleY(-1)' } : {}; + // #endif + + // 在nvue中,需要考虑数据量不满一页的情况,因为nvue中的list无法通过flex-end修改不满一页的起始位置,会导致不满一页时列表数据从底部开始,因此需要特别判断 + // 当数据不满一屏的时候,不进行列表倒置 + // #ifdef APP-NVUE + cellStyle = this.useChatRecordMode ? { transform: this.isFirstPageAndNoMore ? 'scaleY(1)' : 'scaleY(-1)' } : {}; + // #endif + + this.$emit('update:cellStyle', cellStyle); + this.$emit('cellStyleChange', cellStyle); + + // 在聊天记录模式中,如果列表没有倒置并且当前是第一页,则需要自动滚动到最底部 + this.$nextTick(() => { + if (this.isFirstPage && this.isChatRecordModeAndNotInversion) { + this.$nextTick(() => { + // 这里多次触发滚动到底部是为了避免在某些情况下,即使是在nextTick但是cell未渲染完毕导致滚动到底部位置不正确的问题 + this._scrollToBottom(false); + u.delay(() => { + this._scrollToBottom(false); + u.delay(() => { + this._scrollToBottom(false); + }, 50) + }, 50) + }) + } + }) + return cellStyle; + }, + // 是否是聊天记录列表并且有配置transform + isChatRecordModeHasTransform() { + return this.useChatRecordMode && this.chatRecordRotateStyle && this.chatRecordRotateStyle.transform; + }, + // 是否是聊天记录列表并且列表未倒置 + isChatRecordModeAndNotInversion() { + return this.isChatRecordModeHasTransform && this.chatRecordRotateStyle.transform === 'scaleY(1)'; + }, + // 是否是聊天记录列表并且列表倒置 + isChatRecordModeAndInversion() { + return this.isChatRecordModeHasTransform && this.chatRecordRotateStyle.transform === 'scaleY(-1)'; + }, + // 最终的聊天记录模式中底部安全区域的高度,如果开启了底部安全区域并且键盘未弹出,则添加底部区域高度 + chatRecordModeSafeAreaBottom() { + return this.safeAreaInsetBottom && !this.keyboardHeight ? this.safeAreaBottom : 0; + } + }, + mounted() { + // 监听键盘高度变化(H5、百度小程序、抖音小程序、飞书小程序不支持) + // #ifndef H5 || MP-BAIDU || MP-TOUTIAO + if (this.useChatRecordMode) { + uni.onKeyboardHeightChange(this._handleKeyboardHeightChange); + } + // #endif + }, + methods: { + // 添加聊天记录 + addChatRecordData(data, toBottom = true, toBottomWithAnimate = true) { + if (!this.useChatRecordMode) return; + this.isTotalChangeFromAddData = true; + this.addDataFromTop(data, toBottom, toBottomWithAnimate); + }, + // 手动触发滚动到顶部加载更多,聊天记录模式时有效 + doChatRecordLoadMore() { + this.useChatRecordMode && this._onLoadingMore('click'); + }, + // 处理键盘高度变化 + _handleKeyboardHeightChange(res) { + this.$emit('keyboardHeightChange', res); + if (this.autoAdjustPositionWhenChat) { + this.isKeyboardHeightChanged = true; + this.keyboardHeight = res.height > 0 ? res.height + this.finalChatAdjustPositionOffset : res.height; + } + if (this.autoToBottomWhenChat && this.keyboardHeight > 0) { + u.delay(() => { + this.scrollToBottom(false); + u.delay(() => { + this.scrollToBottom(false); + }) + }) + } + } + } +} diff --git a/uni_modules/z-paging/components/z-paging/js/modules/common-layout.js b/uni_modules/z-paging/components/z-paging/js/modules/common-layout.js new file mode 100644 index 0000000..c4933ed --- /dev/null +++ b/uni_modules/z-paging/components/z-paging/js/modules/common-layout.js @@ -0,0 +1,141 @@ +// [z-paging]通用布局相关模块 + +// #ifdef APP-NVUE +const weexDom = weex.requireModule('dom'); +// #endif + +export default { + data() { + return { + systemInfo: null, + cssSafeAreaInsetBottom: -1, + isReadyDestroy: false, + } + }, + computed: { + // 顶部可用距离 + windowTop() { + if (!this.systemInfo) return 0; + // 暂时修复vue3中隐藏系统导航栏后windowTop获取不正确的问题,具体bug详见https://ask.dcloud.net.cn/question/141634 + // 感谢litangyu!!https://github.com/SmileZXLee/uni-z-paging/issues/25 + // #ifdef VUE3 && H5 + const pageHeadNode = document.getElementsByTagName("uni-page-head"); + if (!pageHeadNode.length) return 0; + // #endif + return this.systemInfo.windowTop || 0; + }, + // 底部安全区域高度 + safeAreaBottom() { + if (!this.systemInfo) return 0; + let safeAreaBottom = 0; + // #ifdef APP-PLUS + safeAreaBottom = this.systemInfo.safeAreaInsets.bottom || 0 ; + // #endif + // #ifndef APP-PLUS + safeAreaBottom = Math.max(this.cssSafeAreaInsetBottom, 0); + // #endif + return safeAreaBottom; + }, + // 是否是比较老的webview,在一些老的webview中,需要进行一些特殊处理 + isOldWebView() { + // #ifndef APP-NVUE || MP-KUAISHOU + try { + const systemInfos = uni.getSystemInfoSync().system.split(' '); + const deviceType = systemInfos[0]; + const version = parseInt(systemInfos[1]); + if ((deviceType === 'iOS' && version <= 10) || (deviceType === 'Android' && version <= 6)) { + return true; + } + } catch(e) { + return false; + } + // #endif + return false; + }, + // 当前组件的$slots,兼容不同平台 + zSlots() { + // #ifdef VUE2 + + // #ifdef MP-ALIPAY + return this.$slots; + // #endif + + return this.$scopedSlots || this.$slots; + // #endif + + return this.$slots; + }, + }, + beforeDestroy() { + this.isReadyDestroy = true; + }, + // #ifdef VUE3 + unmounted() { + this.isReadyDestroy = true; + }, + // #endif + methods: { + // 更新fixed模式下z-paging的布局 + updateFixedLayout() { + this.fixed && this.$nextTick(() => { + this.systemInfo = uni.getSystemInfoSync(); + }) + }, + // 获取节点尺寸 + _getNodeClientRect(select, inDom = true, scrollOffset = false) { + if (this.isReadyDestroy) { + return Promise.resolve(false); + }; + // nvue中获取节点信息 + // #ifdef APP-NVUE + select = select.replace(/[.|#]/g, ''); + const ref = this.$refs[select]; + return new Promise((resolve, reject) => { + if (ref) { + weexDom.getComponentRect(ref, option => { + resolve(option && option.result ? [option.size] : false); + }) + } else { + resolve(false); + } + }); + return; + // #endif + + // vue中获取节点信息 + //#ifdef MP-ALIPAY + inDom = false; + //#endif + let res = !!inDom ? uni.createSelectorQuery().in(inDom === true ? this : inDom) : uni.createSelectorQuery(); + scrollOffset ? res.select(select).scrollOffset() : res.select(select).boundingClientRect(); + return new Promise((resolve, reject) => { + res.exec(data => { + resolve((data && data != '' && data != undefined && data.length) ? data : false); + }); + }); + }, + // 获取slot="left"和slot="right"宽度并且更新布局 + _updateLeftAndRightWidth(targetStyle, parentNodePrefix) { + this.$nextTick(() => { + let delayTime = 0; + // #ifdef MP-BAIDU + delayTime = 10; + // #endif + setTimeout(() => { + ['left','right'].map(position => { + this._getNodeClientRect(`.${parentNodePrefix}-${position}`).then(res => { + this.$set(targetStyle, position, res ? res[0].width + 'px' : '0px'); + }); + }) + }, delayTime) + }) + }, + // 通过获取css设置的底部安全区域占位view高度设置bottom距离(直接通过systemInfo在部分平台上无法获取到底部安全区域) + _getCssSafeAreaInsetBottom(success) { + this._getNodeClientRect('.zp-safe-area-inset-bottom').then(res => { + this.cssSafeAreaInsetBottom = res ? res[0].height : -1; + res && success && success(); + }); + } + } +} diff --git a/uni_modules/z-paging/components/z-paging/js/modules/data-handle.js b/uni_modules/z-paging/components/z-paging/js/modules/data-handle.js new file mode 100644 index 0000000..608313c --- /dev/null +++ b/uni_modules/z-paging/components/z-paging/js/modules/data-handle.js @@ -0,0 +1,740 @@ +// [z-paging]数据处理模块 +import u from '.././z-paging-utils' +import c from '.././z-paging-constant' +import Enum from '.././z-paging-enum' +import interceptor from '../z-paging-interceptor' + +export default { + props: { + // 自定义初始的pageNo,默认为1 + defaultPageNo: { + type: [Number, String], + default: u.gc('defaultPageNo', 1), + observer: function(newVal) { + this.pageNo = newVal; + }, + }, + // 自定义pageSize,默认为10 + defaultPageSize: { + type: [Number, String], + default: u.gc('defaultPageSize', 10), + validator: (value) => { + if (value <= 0) u.consoleErr('default-page-size必须大于0!'); + return value > 0; + } + }, + // 为保证数据一致,设置当前tab切换时的标识key,并在complete中传递相同key,若二者不一致,则complete将不会生效 + dataKey: { + type: [Number, String, Object], + default: u.gc('dataKey', null), + }, + // 使用缓存,若开启将自动缓存第一页的数据,默认为否。请注意,因考虑到切换tab时不同tab数据不同的情况,默认仅会缓存组件首次加载时第一次请求到的数据,后续的下拉刷新操作不会更新缓存。 + useCache: { + type: Boolean, + default: u.gc('useCache', false) + }, + // 使用缓存时缓存的key,用于区分不同列表的缓存数据,useCache为true时必须设置,否则缓存无效 + cacheKey: { + type: String, + default: u.gc('cacheKey', null) + }, + // 缓存模式,默认仅会缓存组件首次加载时第一次请求到的数据,可设置为always,即代表总是缓存,每次列表刷新(下拉刷新、调用reload等)都会更新缓存 + cacheMode: { + type: String, + default: u.gc('cacheMode', Enum.CacheMode.Default) + }, + // 自动注入的list名,可自动修改父view(包含ref="paging")中对应name的list值 + autowireListName: { + type: String, + default: u.gc('autowireListName', '') + }, + // 自动注入的query名,可自动调用父view(包含ref="paging")中的query方法 + autowireQueryName: { + type: String, + default: u.gc('autowireQueryName', '') + }, + // 获取分页数据Function,功能与@query类似。若设置了fetch则@query将不再触发 + fetch: { + type: Function, + default: null + }, + // fetch的附加参数,fetch配置后有效 + fetchParams: { + type: Object, + default: u.gc('fetchParams', null) + }, + // z-paging mounted后自动调用reload方法(mounted后自动调用接口),默认为是 + auto: { + type: Boolean, + default: u.gc('auto', true) + }, + // 用户下拉刷新时是否触发reload方法,默认为是 + reloadWhenRefresh: { + type: Boolean, + default: u.gc('reloadWhenRefresh', true) + }, + // reload时自动滚动到顶部,默认为是 + autoScrollToTopWhenReload: { + type: Boolean, + default: u.gc('autoScrollToTopWhenReload', true) + }, + // reload时立即自动清空原list,默认为是,若立即自动清空,则在reload之后、请求回调之前页面是空白的 + autoCleanListWhenReload: { + type: Boolean, + default: u.gc('autoCleanListWhenReload', true) + }, + // 列表刷新时自动显示下拉刷新view,默认为否 + showRefresherWhenReload: { + type: Boolean, + default: u.gc('showRefresherWhenReload', false) + }, + // 列表刷新时自动显示加载更多view,且为加载中状态,默认为否 + showLoadingMoreWhenReload: { + type: Boolean, + default: u.gc('showLoadingMoreWhenReload', false) + }, + // 组件created时立即触发reload(可解决一些情况下先看到页面再看到loading的问题),auto为true时有效。为否时将在mounted+nextTick后触发reload,默认为否 + createdReload: { + type: Boolean, + default: u.gc('createdReload', false) + }, + // 本地分页时上拉加载更多延迟时间,单位为毫秒,默认200毫秒 + localPagingLoadingTime: { + type: [Number, String], + default: u.gc('localPagingLoadingTime', 200) + }, + // 自动拼接complete中传过来的数组(使用聊天记录模式时无效) + concat: { + type: Boolean, + default: u.gc('concat', true) + }, + // 请求失败是否触发reject,默认为是 + callNetworkReject: { + type: Boolean, + default: u.gc('callNetworkReject', true) + }, + // 父组件v-model所绑定的list的值 + value: { + type: Array, + default: function() { + return []; + } + }, + // #ifdef VUE3 + modelValue: { + type: Array, + default: function() { + return []; + } + } + // #endif + }, + data (){ + return { + currentData: [], + totalData: [], + realTotalData: [], + totalLocalPagingList: [], + dataPromiseResultMap: { + reload: null, + complete: null, + localPaging: null + }, + isSettingCacheList: false, + pageNo: 1, + currentRefreshPageSize: 0, + isLocalPaging: false, + isAddedData: false, + isTotalChangeFromAddData: false, + privateConcat: true, + myParentQuery: -1, + firstPageLoaded: false, + pagingLoaded: false, + loaded: false, + isUserReload: true, + fromEmptyViewReload: false, + queryFrom: '', + listRendering: false, + isHandlingRefreshToPage: false, + isFirstPageAndNoMore: false, + totalDataChangeThrow: true + } + }, + computed: { + pageSize() { + return this.defaultPageSize; + }, + finalConcat() { + return this.concat && this.privateConcat; + }, + finalUseCache() { + if (this.useCache && !this.cacheKey) { + u.consoleErr('use-cache为true时,必须设置cache-key,否则缓存无效!'); + } + return this.useCache && !!this.cacheKey; + }, + finalCacheKey() { + return this.cacheKey ? `${c.cachePrefixKey}-${this.cacheKey}` : null; + }, + isFirstPage() { + return this.pageNo === this.defaultPageNo; + } + }, + watch: { + totalData(newVal, oldVal) { + this._totalDataChange(newVal, oldVal, this.totalDataChangeThrow); + this.totalDataChangeThrow = true; + }, + currentData(newVal, oldVal) { + this._currentDataChange(newVal, oldVal); + }, + useChatRecordMode(newVal, oldVal) { + if (newVal) { + this.nLoadingMoreFixedHeight = false; + } + }, + value: { + handler(newVal) { + // 当v-model绑定的数据源被更改时,此时数据源改变不emit input事件,避免循环调用 + if (newVal !== this.totalData) { + this.totalDataChangeThrow = false; + this.totalData = newVal; + } + }, + immediate: true + }, + // #ifdef VUE3 + modelValue: { + handler(newVal) { + // 当v-model绑定的数据源被更改时,此时数据源改变不emit input事件,避免循环调用 + if (newVal !== this.totalData) { + this.totalDataChangeThrow = false; + this.totalData = newVal; + } + }, + immediate: true + } + // #endif + }, + methods: { + // 请求结束(成功或者失败)调用此方法,将请求的结果传递给z-paging处理,第一个参数为请求结果数组,第二个参数为是否成功(默认为是) + complete(data, success = true) { + this.customNoMore = -1; + return this.addData(data, success); + }, + //【保证数据一致】请求结束(成功或者失败)调用此方法,将请求的结果传递给z-paging处理,第一个参数为请求结果数组,第二个参数为dataKey,需与:data-key绑定的一致,第三个参数为是否成功(默认为是) + completeByKey(data, dataKey = null, success = true) { + if (dataKey !== null && this.dataKey !== null && dataKey !== this.dataKey) { + this.isFirstPage && this.endRefresh(); + return new Promise(resolve => resolve()); + } + this.customNoMore = -1; + return this.addData(data, success); + }, + //【通过total判断是否有更多数据】请求结束(成功或者失败)调用此方法,将请求的结果传递给z-paging处理,第一个参数为请求结果数组,第二个参数为total(列表总数),第三个参数为是否成功(默认为是) + completeByTotal(data, total, success = true) { + if (total == 'undefined') { + this.customNoMore = -1; + } else { + const dataTypeRes = this._checkDataType(data, success, false); + data = dataTypeRes.data; + success = dataTypeRes.success; + if (total >= 0 && success) { + return new Promise((resolve, reject) => { + this.$nextTick(() => { + let nomore = false; + const realTotalDataCount = this.pageNo == this.defaultPageNo ? 0 : this.realTotalData.length; + const dataLength = this.privateConcat ? data.length : 0; + let exceedCount = realTotalDataCount + dataLength - total; + // 没有更多数据了 + if (exceedCount >= 0) { + nomore = true; + // 仅截取total内部分的数据 + exceedCount = this.defaultPageSize - exceedCount; + if (this.privateConcat && exceedCount > 0 && exceedCount < data.length) { + data = data.splice(0, exceedCount); + } + } + this.completeByNoMore(data, nomore, success).then(res => resolve(res)).catch(() => reject()); + }) + }); + } + } + return this.addData(data, success); + }, + //【自行判断是否有更多数据】请求结束(成功或者失败)调用此方法,将请求的结果传递给z-paging处理,第一个参数为请求结果数组,第二个参数为是否没有更多数据,第三个参数为是否成功(默认是是) + completeByNoMore(data, nomore, success = true) { + if (nomore != 'undefined') { + this.customNoMore = nomore == true ? 1 : 0; + } + return this.addData(data, success); + }, + // 请求结束且请求失败时调用,支持传入请求失败原因 + completeByError(errorMsg) { + this.customerEmptyViewErrorText = errorMsg; + return this.complete(false); + }, + // 与上方complete方法功能一致,新版本中设置服务端回调数组请使用complete方法 + addData(data, success = true) { + if (!this.fromCompleteEmit) { + this.disabledCompleteEmit = true; + this.fromCompleteEmit = false; + } + const currentTimeStamp = u.getTime(); + const disTime = currentTimeStamp - this.requestTimeStamp; + let minDelay = this.minDelay; + if (this.isFirstPage && this.finalShowRefresherWhenReload) { + minDelay = Math.max(400, minDelay); + } + const addDataDalay = (this.requestTimeStamp > 0 && disTime < minDelay) ? minDelay - disTime : 0; + this.$nextTick(() => { + u.delay(() => { + this._addData(data, success, false); + }, this.delay > 0 ? this.delay : addDataDalay) + }) + + return new Promise((resolve, reject) => { + this.dataPromiseResultMap.complete = { resolve, reject }; + }); + }, + // 从顶部添加数据,不会影响分页的pageNo和pageSize + addDataFromTop(data, toTop = true, toTopWithAnimate = true) { + // 数据是否拼接到顶部,如果是聊天记录模式并且列表没有倒置,则应该拼接在底部 + let addFromTop = !this.isChatRecordModeAndNotInversion; + data = Object.prototype.toString.call(data) !== '[object Array]' ? [data] : (addFromTop ? data.reverse() : data); + // #ifndef APP-NVUE + this.finalUseVirtualList && this._setCellIndex(data, 'top') + // #endif + + this.totalData = addFromTop ? [...data, ...this.totalData] : [...this.totalData, ...data]; + if (toTop) { + u.delay(() => this.useChatRecordMode ? this.scrollToBottom(toTopWithAnimate) : this.scrollToTop(toTopWithAnimate)); + } + }, + // 重新设置列表数据,调用此方法不会影响pageNo和pageSize,也不会触发请求。适用场景:当需要删除列表中某一项时,将删除对应项后的数组通过此方法传递给z-paging。(当出现类似的需要修改列表数组的场景时,请使用此方法,请勿直接修改page中:list.sync绑定的数组) + resetTotalData(data) { + this.isTotalChangeFromAddData = true; + data = Object.prototype.toString.call(data) !== '[object Array]' ? [data] : data; + this.totalData = data; + }, + // 设置本地分页数据,请求结束(成功或者失败)调用此方法,将请求的结果传递给z-paging作分页处理(若调用了此方法,则上拉加载更多时内部会自动分页,不会触发@query所绑定的事件) + setLocalPaging(data, success = true) { + this.isLocalPaging = true; + this.$nextTick(() => { + this._addData(data, success, true); + }) + return new Promise((resolve, reject) => { + this.dataPromiseResultMap.localPaging = { resolve, reject }; + }); + }, + // 重新加载分页数据,pageNo会恢复为默认值,相当于下拉刷新的效果(animate为true时会展示下拉刷新动画,默认为false) + reload(animate = this.showRefresherWhenReload) { + if (animate) { + this.privateShowRefresherWhenReload = animate; + this.isUserPullDown = true; + } + if (!this.showLoadingMoreWhenReload) { + this.listRendering = true; + } + this.$nextTick(() => { + this._preReload(animate, false); + }) + return new Promise((resolve, reject) => { + this.dataPromiseResultMap.reload = { resolve, reject }; + }); + }, + // 刷新列表数据,pageNo和pageSize不会重置,列表数据会重新从服务端获取。必须保证@query绑定的方法中的pageNo和pageSize和传给服务端的一致 + refresh() { + return this._handleRefreshWithDisPageNo(this.pageNo - this.defaultPageNo + 1); + }, + // 刷新列表数据至指定页,例如pageNo=5时则代表刷新列表至第5页,此时pageNo会变为5,列表会展示前5页的数据。必须保证@query绑定的方法中的pageNo和pageSize和传给服务端的一致 + refreshToPage(pageNo) { + this.isHandlingRefreshToPage = true; + return this._handleRefreshWithDisPageNo(pageNo + this.defaultPageNo - 1); + }, + // 手动更新列表缓存数据,将自动截取v-model绑定的list中的前pageSize条覆盖缓存,请确保在list数据更新到预期结果后再调用此方法 + updateCache() { + if (this.finalUseCache && this.totalData.length) { + this._saveLocalCache(this.totalData.slice(0, Math.min(this.totalData.length, this.pageSize))); + } + }, + // 清空分页数据 + clean() { + this._reload(true); + this._addData([], true, false); + }, + // 清空分页数据 + clear() { + this.clean(); + }, + // reload之前的一些处理 + _preReload(animate = this.showRefresherWhenReload, isFromMounted = true, retryCount = 0) { + const showRefresher = this.finalRefresherEnabled && this.useCustomRefresher; + // #ifndef APP-NVUE + // 如果获取slot="refresher"高度失败,则不触发reload,直到获取slot="refresher"高度成功 + if (this.customRefresherHeight === -1 && showRefresher) { + u.delay(() => { + retryCount ++; + // 如果重试次数是10的倍数(也就是每500毫秒),尝试重新获取一下slot="refresher"高度 + // 此举是为了解决在某些特殊情况下,z-paging组件mounted了,但是未展示在用户面前,(比如在tabbar页面中,未切换到对应tabbar但是通过代码让z-paging展示了,此时控制台会报Error: Not Found:Page,因为这时候去获取dom节点信息获取不到) + // 当用户在某个时刻让此z-paging展示在面前时,即可顺利获取到slot="refresher"高度,递归停止 + if (retryCount % 10 === 0) { + this._updateCustomRefresherHeight(); + } + this._preReload(animate, isFromMounted, retryCount); + }, c.delayTime / 2); + return; + } + // #endif + this.isUserReload = true; + this.loadingType = Enum.LoadingType.Refresher; + if (animate) { + this.privateShowRefresherWhenReload = animate; + // #ifndef APP-NVUE + if (this.useCustomRefresher) { + this._doRefresherRefreshAnimate(); + } else { + this.refresherTriggered = true; + } + // #endif + // #ifdef APP-NVUE + this.refresherStatus = Enum.Refresher.Loading; + this.refresherRevealStackCount ++; + u.delay(() => { + this._getNodeClientRect('zp-n-refresh-container', false).then((node) => { + if (node) { + let nodeHeight = node[0].height; + this.nShowRefresherReveal = true; + this.nShowRefresherRevealHeight = nodeHeight; + u.delay(() => { + this._nDoRefresherEndAnimation(0, -nodeHeight, false, false); + u.delay(() => { + this._nDoRefresherEndAnimation(nodeHeight, 0); + }, 10) + }, 10) + } + this._reload(false, isFromMounted); + this._doRefresherLoad(false); + }); + }, this.pagingLoaded ? 10 : 100) + return; + // #endif + } else { + this._refresherEnd(false, false, false, false); + } + this._reload(false, isFromMounted); + }, + // 重新加载分页数据 + _reload(isClean = false, isFromMounted = false, isUserPullDown = false) { + this.isAddedData = false; + this.insideOfPaging = -1; + this.cacheScrollNodeHeight = -1; + this.pageNo = this.defaultPageNo; + this._cleanRefresherEndTimeout(); + !this.privateShowRefresherWhenReload && !isClean && this._startLoading(true); + this.firstPageLoaded = true; + this.isTotalChangeFromAddData = false; + if (!this.isSettingCacheList) { + this.totalData = []; + } + if (!isClean) { + this._emitQuery(this.pageNo, this.defaultPageSize, isUserPullDown ? Enum.QueryFrom.UserPullDown : Enum.QueryFrom.Reload); + let delay = 0; + // #ifdef MP-TOUTIAO + delay = 5; + // #endif + u.delay(this._callMyParentQuery, delay); + if (!isFromMounted && this.autoScrollToTopWhenReload) { + let checkedNRefresherLoading = true; + // #ifdef APP-NVUE + checkedNRefresherLoading = !this.nRefresherLoading; + // #endif + checkedNRefresherLoading && this._scrollToTop(false); + } + } + // #ifdef APP-NVUE + this.$nextTick(() => { + this.nShowBottom = this.realTotalData.length > 0; + }) + // #endif + }, + // 处理服务端返回的数组 + _addData(data, success, isLocal) { + this.isAddedData = true; + this.fromEmptyViewReload = false; + this.isTotalChangeFromAddData = true; + this.refresherTriggered = false; + this._endSystemLoadingAndRefresh(); + const tempIsUserPullDown = this.isUserPullDown; + if (this.showRefresherUpdateTime && this.isFirstPage) { + u.setRefesrherTime(u.getTime(), this.refresherUpdateTimeKey); + this.$refs.refresh && this.$refs.refresh.updateTime(); + } + if (!isLocal && tempIsUserPullDown && this.isFirstPage) { + this.isUserPullDown = false; + } + if (!this.isFirstPage) { + this.listRendering = true; + this.$nextTick(() => { + u.delay(() => this.listRendering = false); + }) + } else { + this.listRendering = false; + } + let dataTypeRes = this._checkDataType(data, success, isLocal); + data = dataTypeRes.data; + success = dataTypeRes.success; + let delayTime = c.delayTime; + if (this.useChatRecordMode) delayTime = 0; + this.loadingForNow = false; + u.delay(() => { + this.pagingLoaded = true; + this.$nextTick(()=>{ + !isLocal && this._refresherEnd(delayTime > 0, true, tempIsUserPullDown); + }) + }) + if (this.isFirstPage) { + this.isLoadFailed = !success; + this.$emit('isLoadFailedChange', this.isLoadFailed); + if (this.finalUseCache && success && (this.cacheMode === Enum.CacheMode.Always ? true : this.isSettingCacheList)) { + this._saveLocalCache(data); + } + } + this.isSettingCacheList = false; + if (success) { + if (!(this.privateConcat === false && !this.isHandlingRefreshToPage && this.loadingStatus === Enum.More.NoMore)) { + this.loadingStatus = Enum.More.Default; + } + if (isLocal) { + // 如果当前是本地分页,则必然是由setLocalPaging方法触发,此时直接本地加载第一页数据即可。后续本地分页加载更多方法由滚动到底部加载更多事件处理 + this.totalLocalPagingList = data; + const localPageNo = this.defaultPageNo; + const localPageSize = this.queryFrom !== Enum.QueryFrom.Refresh ? this.defaultPageSize : this.currentRefreshPageSize; + this._localPagingQueryList(localPageNo, localPageSize, 0, res => { + u.delay(() => { + this.completeByTotal(res, this.totalLocalPagingList.length);; + }, 0) + }) + } else { + // 如果当前不是本地分页,则按照正常分页逻辑进行数据处理&emit数据 + let dataChangeDelayTime = 0; + // #ifdef APP-NVUE + if (this.privateShowRefresherWhenReload && this.finalNvueListIs === 'waterfall') { + dataChangeDelayTime = 150; + } + // #endif + u.delay(() => { + this._currentDataChange(data, this.currentData); + this._callDataPromise(true, this.totalData); + }, dataChangeDelayTime) + } + if (this.isHandlingRefreshToPage) { + this.isHandlingRefreshToPage = false; + this.pageNo = this.defaultPageNo + Math.ceil(data.length / this.pageSize) - 1; + if (data.length % this.pageSize !== 0) { + this.customNoMore = 1; + } + } + } else { + this._currentDataChange(data, this.currentData); + this._callDataPromise(false); + this.loadingStatus = Enum.More.Fail; + this.isHandlingRefreshToPage = false; + if (this.loadingType === Enum.LoadingType.LoadingMore) { + this.pageNo --; + } + } + }, + // 所有数据改变时调用 + _totalDataChange(newVal, oldVal, eventThrow=true) { + if ((!this.isUserReload || !this.autoCleanListWhenReload) && this.firstPageLoaded && !newVal.length && oldVal.length) { + return; + } + this._doCheckScrollViewShouldFullHeight(newVal); + if(!this.realTotalData.length && !newVal.length){ + eventThrow = false; + } + this.realTotalData = newVal; + // emit列表更新事件 + if (eventThrow) { + this.$emit('input', newVal); + // #ifdef VUE3 + this.$emit('update:modelValue', newVal); + // #endif + this.$emit('update:list', newVal); + this.$emit('listChange', newVal); + this._callMyParentList(newVal); + } + this.firstPageLoaded = false; + this.isTotalChangeFromAddData = false; + this.$nextTick(() => { + u.delay(()=>{ + // emit z-paging内容区域高度改变事件 + this._getNodeClientRect('.zp-paging-container-content').then(res => { + res && this.$emit('contentHeightChanged', res[0].height); + }); + }, c.delayTime * (this.isIos ? 1 : 3)) + // #ifdef APP-NVUE + // 在nvue中延时600毫秒展示底部加载更多,避免底部加载更多太早加载闪一下的问题 + u.delay(() => { + this.nShowBottom = true; + }, c.delayTime * 6, 'nShowBottomDelay'); + // #endif + }) + }, + // 当前数据改变时调用 + _currentDataChange(newVal, oldVal) { + newVal = [...newVal]; + // #ifndef APP-NVUE + this.finalUseVirtualList && this._setCellIndex(newVal, 'bottom'); + // #endif + if (this.isFirstPage && this.finalConcat) { + this.totalData = []; + } + // customNoMore:-1代表交由z-paging自行判断;1代表没有更多了;0代表还有更多数据 + if (this.customNoMore !== -1) { + // 如果customNoMore等于1 或者 customNoMore不是0并且新增数组长度为0(也就是不是明确的还有更多数据并且新增的数组长度为0),则没有更多数据了 + if (this.customNoMore === 1 || (this.customNoMore !== 0 && !newVal.length)) { + this.loadingStatus = Enum.More.NoMore; + } + } else { + // 如果新增的数据数组长度为0 或者 新增的数组长度小于默认的pageSize,则没有更多数据了 + if (!newVal.length || (newVal.length && newVal.length < this.defaultPageSize)) { + this.loadingStatus = Enum.More.NoMore; + } + } + if (!this.totalData.length) { + // #ifdef APP-NVUE + // 如果在聊天记录模式+nvue中,并且数据不满一页时需要将列表倒序,因为此时没有将列表旋转180度,数组中第0条数据应当在最底下显示 + if (this.useChatRecordMode && this.finalConcat && this.isFirstPage && this.loadingStatus === Enum.More.NoMore) { + newVal.reverse(); + } + // #endif + this.totalData = newVal; + } else { + if (this.finalConcat) { + const currentScrollTop = this.oldScrollTop; + this.totalData = [...this.totalData, ...newVal]; + // 此处是为了解决在微信小程序中,在某些情况下滚动到底部加载更多后滚动位置直接变为最底部的问题,因此需要通过代码强制滚动回加载更多前的位置 + // #ifdef MP-WEIXIN + if (!this.isIos && !this.refresherOnly && !this.usePageScroll && newVal.length) { + this.loadingMoreTimeStamp = u.getTime(); + this.$nextTick(() => { + this.scrollToY(currentScrollTop); + }) + } + // #endif + } else { + this.totalData = newVal; + } + } + this.privateConcat = true; + }, + // 根据pageNo处理refresh操作 + _handleRefreshWithDisPageNo(pageNo) { + if (!this.isHandlingRefreshToPage && !this.realTotalData.length) return this.reload(); + if (pageNo >= 1) { + this.loading = true; + this.privateConcat = false; + const totalPageSize = pageNo * this.pageSize; + this.currentRefreshPageSize = totalPageSize; + // 如果调用refresh时是本地分页,则在组件内部自己处理分页逻辑,不emit query相关事件 + if (this.isLocalPaging && this.isHandlingRefreshToPage) { + this._localPagingQueryList(this.defaultPageNo, totalPageSize, 0, res => { + this.complete(res); + }) + } else { + // emit query相关事件 + this._emitQuery(this.defaultPageNo, totalPageSize, Enum.QueryFrom.Refresh); + this._callMyParentQuery(this.defaultPageNo, totalPageSize); + } + } + return new Promise((resolve, reject) => { + this.dataPromiseResultMap.reload = { resolve, reject }; + }); + }, + // 本地分页请求 + _localPagingQueryList(pageNo, pageSize, localPagingLoadingTime, callback) { + pageNo = Math.max(1, pageNo); + pageSize = Math.max(1, pageSize); + const totalPagingList = [...this.totalLocalPagingList]; + const pageNoIndex = (pageNo - 1) * pageSize; + const finalPageNoIndex = Math.min(totalPagingList.length, pageNoIndex + pageSize); + const resultPagingList = totalPagingList.splice(pageNoIndex, finalPageNoIndex - pageNoIndex); + u.delay(() => callback(resultPagingList), localPagingLoadingTime) + }, + // 存储列表缓存数据 + _saveLocalCache(data) { + uni.setStorageSync(this.finalCacheKey, data); + }, + // 通过缓存数据填充列表数据 + _setListByLocalCache() { + this.totalData = uni.getStorageSync(this.finalCacheKey) || []; + this.isSettingCacheList = true; + }, + // 修改父view的list + _callMyParentList(newVal) { + if (this.autowireListName.length) { + const myParent = u.getParent(this.$parent); + if (myParent && myParent[this.autowireListName]) { + myParent[this.autowireListName] = newVal; + } + } + }, + // 调用父view的query + _callMyParentQuery(customPageNo = 0, customPageSize = 0) { + if (this.autowireQueryName) { + if (this.myParentQuery === -1) { + const myParent = u.getParent(this.$parent); + if (myParent && myParent[this.autowireQueryName]) { + this.myParentQuery = myParent[this.autowireQueryName]; + } + } + if (this.myParentQuery !== -1) { + customPageSize > 0 ? this.myParentQuery(customPageNo, customPageSize) : this.myParentQuery(this.pageNo, this.defaultPageSize); + } + } + }, + // emit query事件 + _emitQuery(pageNo, pageSize, from){ + this.queryFrom = from; + this.requestTimeStamp = u.getTime(); + const [lastItem] = this.realTotalData.slice(-1); + if (this.fetch) { + const fetchParams = interceptor._handleFetchParams({pageNo, pageSize, from, lastItem: lastItem || null}, this.fetchParams); + const fetchResult = this.fetch(fetchParams); + if (!interceptor._handleFetchResult(fetchResult, this, fetchParams)) { + u.isPromise(fetchResult) ? fetchResult.then(res => { + this.complete(res); + }).catch(err => { + this.complete(false); + }) : this.complete(fetchResult) + } + } else { + this.$emit('query', ...interceptor._handleQuery(pageNo, pageSize, from, lastItem || null)); + } + }, + // 触发数据改变promise + _callDataPromise(success, totalList) { + for (const key in this.dataPromiseResultMap) { + const obj = this.dataPromiseResultMap[key]; + if (!obj) continue; + success ? obj.resolve({ totalList, noMore: this.loadingStatus === Enum.More.NoMore }) : this.callNetworkReject && obj.reject(`z-paging-${key}-error`); + } + }, + // 检查complete data的类型 + _checkDataType(data, success, isLocal) { + const dataType = Object.prototype.toString.call(data); + if (dataType === '[object Boolean]') { + success = data; + data = []; + } else if (dataType !== '[object Array]') { + data = []; + if (dataType !== '[object Undefined]' && dataType !== '[object Null]') { + u.consoleErr(`${isLocal ? 'setLocalPaging' : 'complete'}参数类型不正确,第一个参数类型必须为Array!`); + } + } + return { data, success }; + }, + } +} diff --git a/uni_modules/z-paging/components/z-paging/js/modules/empty.js b/uni_modules/z-paging/components/z-paging/js/modules/empty.js new file mode 100644 index 0000000..6c99f56 --- /dev/null +++ b/uni_modules/z-paging/components/z-paging/js/modules/empty.js @@ -0,0 +1,144 @@ +// [z-paging]空数据图view模块 +import u from '.././z-paging-utils' + +export default { + props: { + // 是否强制隐藏空数据图,默认为否 + hideEmptyView: { + type: Boolean, + default: u.gc('hideEmptyView', false) + }, + // 空数据图描述文字,默认为“没有数据哦~” + emptyViewText: { + type: [String, Object], + default: u.gc('emptyViewText', null) + }, + // 是否显示空数据图重新加载按钮(无数据时),默认为否 + showEmptyViewReload: { + type: Boolean, + default: u.gc('showEmptyViewReload', false) + }, + // 加载失败时是否显示空数据图重新加载按钮,默认为是 + showEmptyViewReloadWhenError: { + type: Boolean, + default: u.gc('showEmptyViewReloadWhenError', true) + }, + // 空数据图点击重新加载文字,默认为“重新加载” + emptyViewReloadText: { + type: [String, Object], + default: u.gc('emptyViewReloadText', null) + }, + // 空数据图图片,默认使用z-paging内置的图片 + emptyViewImg: { + type: String, + default: u.gc('emptyViewImg', '') + }, + // 空数据图“加载失败”描述文字,默认为“很抱歉,加载失败” + emptyViewErrorText: { + type: [String, Object], + default: u.gc('emptyViewErrorText', null) + }, + // 空数据图“加载失败”图片,默认使用z-paging内置的图片 + emptyViewErrorImg: { + type: String, + default: u.gc('emptyViewErrorImg', '') + }, + // 空数据图样式 + emptyViewStyle: { + type: Object, + default: u.gc('emptyViewStyle', {}) + }, + // 空数据图容器样式 + emptyViewSuperStyle: { + type: Object, + default: u.gc('emptyViewSuperStyle', {}) + }, + // 空数据图img样式 + emptyViewImgStyle: { + type: Object, + default: u.gc('emptyViewImgStyle', {}) + }, + // 空数据图描述文字样式 + emptyViewTitleStyle: { + type: Object, + default: u.gc('emptyViewTitleStyle', {}) + }, + // 空数据图重新加载按钮样式 + emptyViewReloadStyle: { + type: Object, + default: u.gc('emptyViewReloadStyle', {}) + }, + // 空数据图片是否铺满z-paging,默认为否,即填充满z-paging内列表(滚动区域)部分。若设置为否,则为填铺满整个z-paging + emptyViewFixed: { + type: Boolean, + default: u.gc('emptyViewFixed', false) + }, + // 空数据图片是否垂直居中,默认为是,若设置为否即为从空数据容器顶部开始显示。emptyViewFixed为false时有效 + emptyViewCenter: { + type: Boolean, + default: u.gc('emptyViewCenter', true) + }, + // 加载中时是否自动隐藏空数据图,默认为是 + autoHideEmptyViewWhenLoading: { + type: Boolean, + default: u.gc('autoHideEmptyViewWhenLoading', true) + }, + // 用户下拉列表触发下拉刷新加载中时是否自动隐藏空数据图,默认为是 + autoHideEmptyViewWhenPull: { + type: Boolean, + default: u.gc('autoHideEmptyViewWhenPull', true) + }, + // 空数据view的z-index,默认为9 + emptyViewZIndex: { + type: Number, + default: u.gc('emptyViewZIndex', 9) + }, + }, + data() { + return { + customerEmptyViewErrorText: '' + } + }, + computed: { + finalEmptyViewImg() { + return this.isLoadFailed ? this.emptyViewErrorImg : this.emptyViewImg; + }, + finalShowEmptyViewReload() { + return this.isLoadFailed ? this.showEmptyViewReloadWhenError : this.showEmptyViewReload; + }, + // 是否展示空数据图 + showEmpty() { + if (this.refresherOnly || this.hideEmptyView || this.realTotalData.length) return false; + if (this.autoHideEmptyViewWhenLoading) { + if (this.isAddedData && !this.firstPageLoaded && !this.loading) return true; + } else { + return true; + } + return !this.autoHideEmptyViewWhenPull && !this.isUserReload; + }, + }, + methods: { + // 点击了空数据view重新加载按钮 + _emptyViewReload() { + let callbacked = false; + this.$emit('emptyViewReload', reload => { + if (reload === undefined || reload === true) { + this.fromEmptyViewReload = true; + this.reload().catch(() => {}); + } + callbacked = true; + }); + // 如果用户没有禁止默认的点击重新加载刷新列表事件,则触发列表重新刷新 + this.$nextTick(() => { + if (!callbacked) { + this.fromEmptyViewReload = true; + this.reload().catch(() => {}); + } + }) + }, + // 点击了空数据view + _emptyViewClick() { + this.$emit('emptyViewClick'); + }, + } +} \ No newline at end of file diff --git a/uni_modules/z-paging/components/z-paging/js/modules/i18n.js b/uni_modules/z-paging/components/z-paging/js/modules/i18n.js new file mode 100644 index 0000000..b521c8e --- /dev/null +++ b/uni_modules/z-paging/components/z-paging/js/modules/i18n.js @@ -0,0 +1,119 @@ +// [z-paging]i18n模块 +import { initVueI18n } from '@dcloudio/uni-i18n' +import messages from '../../i18n/index.js' +const { t } = initVueI18n(messages) + +import u from '.././z-paging-utils' +import c from '.././z-paging-constant' +import interceptor from '../z-paging-interceptor' + +const language = uni.getSystemInfoSync().language; +export default { + data() { + return { + language + } + }, + computed: { + finalLanguage() { + try { + const local = uni.getLocale(); + const language = this.language; + return local === 'auto' ? interceptor._handleLanguage2Local(language, this._language2Local(language)) : local; + } catch (e) { + // 如果获取系统本地语言异常,则默认返回中文,uni.getLocale在部分低版本HX或者cli中可能报找不到的问题 + return 'zh-Hans'; + } + }, + // 最终的下拉刷新默认状态的文字 + finalRefresherDefaultText() { + return this._getI18nText('zp.refresher.default', this.refresherDefaultText); + }, + // 最终的下拉刷新下拉中的文字 + finalRefresherPullingText() { + return this._getI18nText('zp.refresher.pulling', this.refresherPullingText); + }, + // 最终的下拉刷新中文字 + finalRefresherRefreshingText() { + return this._getI18nText('zp.refresher.refreshing', this.refresherRefreshingText); + }, + // 最终的下拉刷新完成文字 + finalRefresherCompleteText() { + return this._getI18nText('zp.refresher.complete', this.refresherCompleteText); + }, + // 最终的下拉刷新上次更新时间文字 + finalRefresherUpdateTimeTextMap() { + return { + title: t('zp.refresherUpdateTime.title'), + none: t('zp.refresherUpdateTime.none'), + today: t('zp.refresherUpdateTime.today'), + yesterday: t('zp.refresherUpdateTime.yesterday') + }; + }, + // 最终的继续下拉进入二楼文字 + finalRefresherGoF2Text() { + return this._getI18nText('zp.refresher.f2', this.refresherGoF2Text); + }, + // 最终的底部加载更多默认状态文字 + finalLoadingMoreDefaultText() { + return this._getI18nText('zp.loadingMore.default', this.loadingMoreDefaultText); + }, + // 最终的底部加载更多加载中文字 + finalLoadingMoreLoadingText() { + return this._getI18nText('zp.loadingMore.loading', this.loadingMoreLoadingText); + }, + // 最终的底部加载更多没有更多数据文字 + finalLoadingMoreNoMoreText() { + return this._getI18nText('zp.loadingMore.noMore', this.loadingMoreNoMoreText); + }, + // 最终的底部加载更多加载失败文字 + finalLoadingMoreFailText() { + return this._getI18nText('zp.loadingMore.fail', this.loadingMoreFailText); + }, + // 最终的空数据图title + finalEmptyViewText() { + return this.isLoadFailed ? this.finalEmptyViewErrorText : this._getI18nText('zp.emptyView.title', this.emptyViewText); + }, + // 最终的空数据图reload title + finalEmptyViewReloadText() { + return this._getI18nText('zp.emptyView.reload', this.emptyViewReloadText); + }, + // 最终的空数据图加载失败文字 + finalEmptyViewErrorText() { + return this.customerEmptyViewErrorText || this._getI18nText('zp.emptyView.error', this.emptyViewErrorText); + }, + // 最终的系统loading title + finalSystemLoadingText() { + return this._getI18nText('zp.systemLoading.title', this.systemLoadingText); + }, + }, + methods: { + // 获取当前z-paging的语言 + getLanguage() { + return this.finalLanguage; + }, + // 获取国际化转换后的文本 + _getI18nText(key, value) { + const dataType = Object.prototype.toString.call(value); + if (dataType === '[object Object]') { + const nextValue = value[this.finalLanguage]; + if (nextValue) return nextValue; + } else if (dataType === '[object String]') { + return value; + } + return t(key); + }, + // 系统language转i18n local + _language2Local(language) { + const formatedLanguage = language.toLowerCase().replace(new RegExp('_', ''), '-'); + if (formatedLanguage.indexOf('zh') !== -1) { + if (formatedLanguage === 'zh' || formatedLanguage === 'zh-cn' || formatedLanguage.indexOf('zh-hans') !== -1) { + return 'zh-Hans'; + } + return 'zh-Hant'; + } + if (formatedLanguage.indexOf('en') !== -1) return 'en'; + return language; + } + } +} diff --git a/uni_modules/z-paging/components/z-paging/js/modules/load-more.js b/uni_modules/z-paging/components/z-paging/js/modules/load-more.js new file mode 100644 index 0000000..5640abf --- /dev/null +++ b/uni_modules/z-paging/components/z-paging/js/modules/load-more.js @@ -0,0 +1,374 @@ +// [z-paging]滚动到底部加载更多模块 +import u from '.././z-paging-utils' +import Enum from '.././z-paging-enum' + +export default { + props: { + // 自定义底部加载更多样式 + loadingMoreCustomStyle: { + type: Object, + default: u.gc('loadingMoreCustomStyle', {}) + }, + // 自定义底部加载更多文字样式 + loadingMoreTitleCustomStyle: { + type: Object, + default: u.gc('loadingMoreTitleCustomStyle', {}) + }, + // 自定义底部加载更多加载中动画样式 + loadingMoreLoadingIconCustomStyle: { + type: Object, + default: u.gc('loadingMoreLoadingIconCustomStyle', {}) + }, + // 自定义底部加载更多加载中动画图标类型,可选flower或circle,默认为flower + loadingMoreLoadingIconType: { + type: String, + default: u.gc('loadingMoreLoadingIconType', 'flower') + }, + // 自定义底部加载更多加载中动画图标图片 + loadingMoreLoadingIconCustomImage: { + type: String, + default: u.gc('loadingMoreLoadingIconCustomImage', '') + }, + // 底部加载更多加载中view是否展示旋转动画,默认为是 + loadingMoreLoadingAnimated: { + type: Boolean, + default: u.gc('loadingMoreLoadingAnimated', true) + }, + // 是否启用加载更多数据(含滑动到底部加载更多数据和点击加载更多数据),默认为是 + loadingMoreEnabled: { + type: Boolean, + default: u.gc('loadingMoreEnabled', true) + }, + // 是否启用滑动到底部加载更多数据,默认为是 + toBottomLoadingMoreEnabled: { + type: Boolean, + default: u.gc('toBottomLoadingMoreEnabled', true) + }, + // 滑动到底部状态为默认状态时,以加载中的状态展示,默认为否。若设置为是,可避免滚动到底部看到默认状态然后立刻变为加载中状态的问题,但分页数量未超过一屏时,不会显示【点击加载更多】 + loadingMoreDefaultAsLoading: { + type: Boolean, + default: u.gc('loadingMoreDefaultAsLoading', false) + }, + // 滑动到底部"默认"文字,默认为【点击加载更多】 + loadingMoreDefaultText: { + type: [String, Object], + default: u.gc('loadingMoreDefaultText', null) + }, + // 滑动到底部"加载中"文字,默认为【正在加载...】 + loadingMoreLoadingText: { + type: [String, Object], + default: u.gc('loadingMoreLoadingText', null) + }, + // 滑动到底部"没有更多"文字,默认为【没有更多了】 + loadingMoreNoMoreText: { + type: [String, Object], + default: u.gc('loadingMoreNoMoreText', null) + }, + // 滑动到底部"加载失败"文字,默认为【加载失败,点击重新加载】 + loadingMoreFailText: { + type: [String, Object], + default: u.gc('loadingMoreFailText', null) + }, + // 当没有更多数据且分页内容未超出z-paging时是否隐藏没有更多数据的view,默认为否 + hideNoMoreInside: { + type: Boolean, + default: u.gc('hideNoMoreInside', false) + }, + // 当没有更多数据且分页数组长度少于这个值时,隐藏没有更多数据的view,默认为0,代表不限制。 + hideNoMoreByLimit: { + type: Number, + default: u.gc('hideNoMoreByLimit', 0) + }, + // 是否显示默认的加载更多text,默认为是 + showDefaultLoadingMoreText: { + type: Boolean, + default: u.gc('showDefaultLoadingMoreText', true) + }, + // 是否显示没有更多数据的view + showLoadingMoreNoMoreView: { + type: Boolean, + default: u.gc('showLoadingMoreNoMoreView', true) + }, + // 是否显示没有更多数据的分割线,默认为是 + showLoadingMoreNoMoreLine: { + type: Boolean, + default: u.gc('showLoadingMoreNoMoreLine', true) + }, + // 自定义底部没有更多数据的分割线样式 + loadingMoreNoMoreLineCustomStyle: { + type: Object, + default: u.gc('loadingMoreNoMoreLineCustomStyle', {}) + }, + // 当分页未满一屏时,是否自动加载更多,默认为否(nvue无效) + insideMore: { + type: Boolean, + default: u.gc('insideMore', false) + }, + // 距底部/右边多远时(单位px),触发 scrolltolower 事件,默认为100rpx + lowerThreshold: { + type: [Number, String], + default: u.gc('lowerThreshold', '100rpx') + }, + }, + data() { + return { + M: Enum.More, + // 底部加载更多状态 + loadingStatus: Enum.More.Default, + // 在渲染之后的底部加载更多状态 + loadingStatusAfterRender: Enum.More.Default, + // 底部加载更多时间戳 + loadingMoreTimeStamp: 0, + // 底部加载更多slot + loadingMoreDefaultSlot: null, + // 是否展示底部加载更多 + showLoadingMore: false, + // 是否是开发者自定义的加载更多,-1代表交由z-paging自行判断;1代表没有更多了;0代表还有更多数据 + customNoMore: -1, + } + }, + computed: { + // 底部加载更多配置 + zLoadMoreConfig() { + return { + status: this.loadingStatusAfterRender, + defaultAsLoading: this.loadingMoreDefaultAsLoading || (this.useChatRecordMode && this.chatLoadingMoreDefaultAsLoading), + defaultThemeStyle: this.finalLoadingMoreThemeStyle, + customStyle: this.loadingMoreCustomStyle, + titleCustomStyle: this.loadingMoreTitleCustomStyle, + iconCustomStyle: this.loadingMoreLoadingIconCustomStyle, + loadingIconType: this.loadingMoreLoadingIconType, + loadingIconCustomImage: this.loadingMoreLoadingIconCustomImage, + loadingAnimated: this.loadingMoreLoadingAnimated, + showNoMoreLine: this.showLoadingMoreNoMoreLine, + noMoreLineCustomStyle: this.loadingMoreNoMoreLineCustomStyle, + defaultText: this.finalLoadingMoreDefaultText, + loadingText: this.finalLoadingMoreLoadingText, + noMoreText: this.finalLoadingMoreNoMoreText, + failText: this.finalLoadingMoreFailText, + hideContent: !this.loadingMoreDefaultAsLoading && this.listRendering, + unit: this.unit, + isChat: this.useChatRecordMode, + chatDefaultAsLoading: this.chatLoadingMoreDefaultAsLoading + }; + }, + // 最终的底部加载更多主题 + finalLoadingMoreThemeStyle() { + return this.loadingMoreThemeStyle.length ? this.loadingMoreThemeStyle : this.defaultThemeStyle; + }, + // 最终的底部加载更多触发阈值 + finalLowerThreshold() { + return u.convertToPx(this.lowerThreshold); + }, + // 是否显示默认状态下的底部加载更多 + showLoadingMoreDefault() { + return this._showLoadingMore('Default'); + }, + // 是否显示加载中状态下的底部加载更多 + showLoadingMoreLoading() { + return this._showLoadingMore('Loading'); + }, + // 是否显示没有更多了状态下的底部加载更多 + showLoadingMoreNoMore() { + return this._showLoadingMore('NoMore'); + }, + // 是否显示加载失败状态下的底部加载更多 + showLoadingMoreFail() { + return this._showLoadingMore('Fail'); + }, + // 是否显示自定义状态下的底部加载更多 + showLoadingMoreCustom() { + return this._showLoadingMore('Custom'); + }, + // 底部加载更多固定高度 + loadingMoreFixedHeight() { + return u.addUnit('80rpx', this.unit); + }, + }, + methods: { + // 页面滚动到底部时通知z-paging进行进一步处理 + pageReachBottom() { + !this.useChatRecordMode && this.toBottomLoadingMoreEnabled && this._onLoadingMore('toBottom'); + }, + // 手动触发上拉加载更多(非必须,可依据具体需求使用) + doLoadMore(type) { + this._onLoadingMore(type); + }, + // 通过@scroll事件检测是否滚动到了底部(顺带检测下是否滚动到了顶部) + _checkScrolledToBottom(scrollDiff, checked = false) { + // 如果当前scroll-view高度未获取,则获取其高度 + if (this.cacheScrollNodeHeight === -1) { + // 获取当前scroll-view高度 + this._getNodeClientRect('.zp-scroll-view').then((res) => { + if (res) { + const scrollNodeHeight = res[0].height; + // 缓存当前scroll-view高度,如果获取过了不再获取 + this.cacheScrollNodeHeight = scrollNodeHeight; + // // scrollDiff - this.cacheScrollNodeHeight = 当前滚动区域的顶部与内容底部的距离 - scroll-view高度 = 当前滚动区域的底部与内容底部的距离(也就是最终的与底部的距离) + if (scrollDiff - scrollNodeHeight <= this.finalLowerThreshold) { + // 如果与底部的距离小于阈值,则判断为滚动到了底部,触发滚动到底部事件 + this._onLoadingMore('toBottom'); + } + } + }); + } else { + // scrollDiff - this.cacheScrollNodeHeight = 当前滚动区域的顶部与内容底部的距离 - scroll-view高度 = 当前滚动区域的底部与内容底部的距离(也就是最终的与底部的距离) + if (scrollDiff - this.cacheScrollNodeHeight <= this.finalLowerThreshold) { + // 如果与底部的距离小于阈值,则判断为滚动到了底部,触发滚动到底部事件 + this._onLoadingMore('toBottom'); + } else if (scrollDiff - this.cacheScrollNodeHeight <= 500 && !checked) { + // 如果与底部的距离小于500px,则获取当前滚动的位置,延迟150毫秒重复上述步骤再次检测(避免@scroll触发时获取的scrollTop不正确导致的其他问题,此时获取的scrollTop不一定可信)。防止因为部分性能较差安卓设备@scroll采样率过低导致的滚动到底部但是依然没有触发的问题 + u.delay(() => { + this._getNodeClientRect('.zp-scroll-view', true, true).then((res) => { + if (res) { + this.oldScrollTop = res[0].scrollTop; + const newScrollDiff = res[0].scrollHeight - this.oldScrollTop; + this._checkScrolledToBottom(newScrollDiff, true); + } + }) + }, 150, 'checkScrolledToBottomDelay') + } + // 检测一下是否已经滚动到了顶部了,因为在安卓中滚动到顶部时scrollTop不一定为0(和滚动到底部一样的原因),所以需要在scrollTop小于150px时,通过获取.zp-scroll-view的scrollTop再判断一下 + if (this.oldScrollTop <= 150 && this.oldScrollTop !== 0) { + u.delay(() => { + // 这里再判断一下是否确实已经滚动到顶部了,如果已经滚动到顶部了,则不用再判断了,再次判断的原因是可能150毫秒之后oldScrollTop才是0 + if (this.oldScrollTop !== 0) { + this._getNodeClientRect('.zp-scroll-view', true, true).then((res) => { + // 如果150毫秒后.zp-scroll-view的scrollTop为0,则认为已经滚动到了顶部了 + if (res && res[0].scrollTop === 0 && this.oldScrollTop !== 0) { + this._onScrollToUpper(); + } + }) + } + }, 150, 'checkScrolledToTopDelay') + } + } + }, + // 触发加载更多时调用,from:toBottom-滑动到底部触发;1、click-点击加载更多触发 + _onLoadingMore(from = 'click') { + // 如果是ios并且是滚动到底部的,则在滚动到底部时候尝试将列表设置为禁止滚动然后设置为允许滚动,以禁止底部bounce的效果 + if (this.isIos && from === 'toBottom' && !this.scrollToBottomBounceEnabled && this.scrollEnable) { + this.scrollEnable = false; + this.$nextTick(() => { + this.scrollEnable = true; + }) + } + // emit scrolltolower + this.$emit('scrolltolower', from); + // 如果是只使用下拉刷新 或者 禁用底部加载更多 或者 底部加载更多不是默认状态或加载失败状态 或者 是加载中状态 或者 空数据图已经展示了,则return,不触发内部加载更多逻辑 + if (this.refresherOnly || !this.loadingMoreEnabled || !(this.loadingStatus === Enum.More.Default || this.loadingStatus === Enum.More.Fail) || this.loading || this.showEmpty) return; + // #ifdef MP-WEIXIN + if (!this.isIos && !this.refresherOnly && !this.usePageScroll) { + const currentTimestamp = u.getTime(); + // 在非ios平台+scroll-view中节流处理 + if (this.loadingMoreTimeStamp > 0 && currentTimestamp - this.loadingMoreTimeStamp < 100) { + this.loadingMoreTimeStamp = 0; + return; + } + } + // #endif + // 处理加载更多数据 + this._doLoadingMore(); + }, + // 处理开始加载更多 + _doLoadingMore() { + if (this.pageNo >= this.defaultPageNo && this.loadingStatus !== Enum.More.NoMore) { + this.pageNo ++; + this._startLoading(false); + if (this.isLocalPaging) { + // 如果是本地分页,则在组件内部对数据进行分页处理,不触发@query事件 + this._localPagingQueryList(this.pageNo, this.defaultPageSize, this.localPagingLoadingTime, res => { + this.completeByTotal(res, this.totalLocalPagingList.length); + this.queryFrom = Enum.QueryFrom.LoadingMore; + }) + } else { + // emit @query相关加载更多事件 + this._emitQuery(this.pageNo, this.defaultPageSize, Enum.QueryFrom.LoadingMore); + this._callMyParentQuery(); + } + // 设置当前加载状态为底部加载更多状态 + this.loadingType = Enum.LoadingType.LoadingMore; + } + }, + // (预处理)判断当没有更多数据且分页内容未超出z-paging时是否显示没有更多数据的view + _preCheckShowNoMoreInside(newVal, scrollViewNode, pagingContainerNode) { + if (this.loadingStatus === Enum.More.NoMore && this.hideNoMoreByLimit > 0 && newVal.length) { + this.showLoadingMore = newVal.length > this.hideNoMoreByLimit; + } else if ((this.loadingStatus === Enum.More.NoMore && this.hideNoMoreInside && newVal.length) || (this.insideMore && this.insideOfPaging !== false && newVal.length)) { + this.$nextTick(() => { + this._checkShowNoMoreInside(newVal, scrollViewNode, pagingContainerNode); + }) + if (this.insideMore && this.insideOfPaging !== false && newVal.length) { + this.showLoadingMore = newVal.length; + } + } else { + this.showLoadingMore = newVal.length; + } + }, + // 判断当没有更多数据且分页内容未超出z-paging时是否显示没有更多数据的view + async _checkShowNoMoreInside(totalData, oldScrollViewNode, oldPagingContainerNode) { + try { + const scrollViewNode = oldScrollViewNode || await this._getNodeClientRect('.zp-scroll-view'); + // 在页面滚动模式下 + if (this.usePageScroll) { + if (scrollViewNode) { + // 获取滚动内容总高度 + const scrollViewTotalH = scrollViewNode[0].top + scrollViewNode[0].height; + // 如果滚动内容总高度小于窗口高度,则认为内容未超出z-paging + this.insideOfPaging = scrollViewTotalH < this.windowHeight; + // 如果需要没有更多数据时,隐藏底部加载更多view,并且内容未超过z-paging,则隐藏底部加载更多 + if (this.hideNoMoreInside) { + this.showLoadingMore = !this.insideOfPaging; + } + // 如果需要内容未超过z-paging时自动加载更多,则触发加载更多 + this._updateInsideOfPaging(); + } + } else { + // 在scroll-view滚动模式下 + const pagingContainerNode = oldPagingContainerNode || await this._getNodeClientRect('.zp-paging-container-content'); + // 获取滚动内容总高度 + const pagingContainerH = pagingContainerNode ? pagingContainerNode[0].height : 0; + // 获取z-paging内置scroll-view高度 + const scrollViewH = scrollViewNode ? scrollViewNode[0].height : 0; + // 如果滚动内容总高度小于z-paging内置scroll-view高度,则认为内容未超出z-paging + this.insideOfPaging = pagingContainerH < scrollViewH; + if (this.hideNoMoreInside) { + this.showLoadingMore = !this.insideOfPaging; + } + // 如果需要内容未超过z-paging时自动加载更多,则触发加载更多 + this._updateInsideOfPaging(); + } + } catch (e) { + // 如果发生了异常,判断totalData数组长度为0,则认为内容未超出z-paging + this.insideOfPaging = !totalData.length; + if (this.hideNoMoreInside) { + this.showLoadingMore = !this.insideOfPaging; + } + // 如果需要内容未超过z-paging时自动加载更多,则触发加载更多 + this._updateInsideOfPaging(); + } + }, + // 是否要展示上拉加载更多view + _showLoadingMore(type) { + if (!this.showLoadingMoreWhenReload && (!(this.loadingStatus === Enum.More.Default ? this.nShowBottom : true) || !this.realTotalData.length)) return false; + if (((!this.showLoadingMoreWhenReload || this.isUserPullDown || this.loadingStatus !== Enum.More.Loading) && !this.showLoadingMore) || + (!this.loadingMoreEnabled && (!this.showLoadingMoreWhenReload || this.isUserPullDown || this.loadingStatus !== Enum.More.Loading)) || this.refresherOnly) { + return false; + } + if (this.useChatRecordMode && type !== 'Loading') return false; + if (!this.zSlots) return false; + if (type === 'Custom') { + return this.showDefaultLoadingMoreText && !(this.loadingStatus === Enum.More.NoMore && !this.showLoadingMoreNoMoreView); + } + const res = this.loadingStatus === Enum.More[type] && this.zSlots[`loadingMore${type}`] && (type === 'NoMore' ? this.showLoadingMoreNoMoreView : true); + if (res) { + // #ifdef APP-NVUE + if (!this.isIos) { + this.nLoadingMoreFixedHeight = false; + } + // #endif + } + return res; + }, + } +} diff --git a/uni_modules/z-paging/components/z-paging/js/modules/loading.js b/uni_modules/z-paging/components/z-paging/js/modules/loading.js new file mode 100644 index 0000000..19596ce --- /dev/null +++ b/uni_modules/z-paging/components/z-paging/js/modules/loading.js @@ -0,0 +1,95 @@ +// [z-paging]loading相关模块 +import u from '.././z-paging-utils' +import Enum from '.././z-paging-enum' + +export default { + props: { + // 第一次加载后自动隐藏loading slot,默认为是 + autoHideLoadingAfterFirstLoaded: { + type: Boolean, + default: u.gc('autoHideLoadingAfterFirstLoaded', true) + }, + // loading slot是否铺满屏幕并固定,默认为否 + loadingFullFixed: { + type: Boolean, + default: u.gc('loadingFullFixed', false) + }, + // 是否自动显示系统Loading:即uni.showLoading,若开启则将在刷新列表时(调用reload、refresh时)显示,下拉刷新和滚动到底部加载更多不会显示,默认为false。 + autoShowSystemLoading: { + type: Boolean, + default: u.gc('autoShowSystemLoading', false) + }, + // 显示系统Loading时是否显示透明蒙层,防止触摸穿透,默认为是(H5、App、微信小程序、百度小程序有效) + systemLoadingMask: { + type: Boolean, + default: u.gc('systemLoadingMask', true) + }, + // 显示系统Loading时显示的文字,默认为"加载中" + systemLoadingText: { + type: [String, Object], + default: u.gc('systemLoadingText', null) + }, + }, + data() { + return { + loading: false, + loadingForNow: false, + } + }, + watch: { + // loading状态 + loadingStatus(newVal) { + this.$emit('loadingStatusChange', newVal); + this.$nextTick(() => { + this.loadingStatusAfterRender = newVal; + }) + if (this.useChatRecordMode) { + if (this.isFirstPage && (newVal === Enum.More.NoMore || newVal === Enum.More.Fail)) { + this.isFirstPageAndNoMore = true; + return; + } + } + this.isFirstPageAndNoMore = false; + }, + loading(newVal){ + if (newVal) { + this.loadingForNow = newVal; + } + }, + }, + computed: { + // 是否显示loading + showLoading() { + if (this.firstPageLoaded || !this.loading || !this.loadingForNow) return false; + if (this.finalShowSystemLoading) { + // 显示系统loading + uni.showLoading({ + title: this.finalSystemLoadingText, + mask: this.systemLoadingMask + }) + } + return this.autoHideLoadingAfterFirstLoaded ? (this.fromEmptyViewReload ? true : !this.pagingLoaded) : this.loadingType === Enum.LoadingType.Refresher; + }, + // 最终的是否显示系统loading + finalShowSystemLoading() { + return this.autoShowSystemLoading && this.loadingType === Enum.LoadingType.Refresher; + } + }, + methods: { + // 处理开始加载更多状态 + _startLoading(isReload = false) { + if ((this.showLoadingMoreWhenReload && !this.isUserPullDown) || !isReload) { + this.loadingStatus = Enum.More.Loading; + } + this.loading = true; + }, + // 停止系统loading和refresh + _endSystemLoadingAndRefresh(){ + this.finalShowSystemLoading && uni.hideLoading(); + !this.useCustomRefresher && uni.stopPullDownRefresh(); + // #ifdef APP-NVUE + this.usePageScroll && uni.stopPullDownRefresh(); + // #endif + } + } +} diff --git a/uni_modules/z-paging/components/z-paging/js/modules/nvue.js b/uni_modules/z-paging/components/z-paging/js/modules/nvue.js new file mode 100644 index 0000000..300b084 --- /dev/null +++ b/uni_modules/z-paging/components/z-paging/js/modules/nvue.js @@ -0,0 +1,255 @@ +// [z-paging]nvue独有部分模块 +import u from '.././z-paging-utils' +import c from '.././z-paging-constant' +import Enum from '.././z-paging-enum' + +// #ifdef APP-NVUE +const weexAnimation = weex.requireModule('animation'); +// #endif +export default { + props: { + // #ifdef APP-NVUE + // nvue中修改列表类型,可选值有list、waterfall和scroller,默认为list + nvueListIs: { + type: String, + default: u.gc('nvueListIs', 'list') + }, + // nvue waterfall配置,仅在nvue中且nvueListIs=waterfall时有效,配置参数详情参见:https://uniapp.dcloud.io/component/waterfall + nvueWaterfallConfig: { + type: Object, + default: u.gc('nvueWaterfallConfig', {}) + }, + // nvue 控制是否回弹效果,iOS不支持动态修改 + nvueBounce: { + type: Boolean, + default: u.gc('nvueBounce', true) + }, + // nvue中通过代码滚动到顶部/底部时,是否加快动画效果(无滚动动画时无效),默认为否 + nvueFastScroll: { + type: Boolean, + default: u.gc('nvueFastScroll', false) + }, + // nvue中list的id + nvueListId: { + type: String, + default: u.gc('nvueListId', '') + }, + // nvue中refresh组件的样式 + nvueRefresherStyle: { + type: Object, + default: u.gc('nvueRefresherStyle', {}) + }, + // nvue中是否按分页模式(类似竖向swiper)显示List,默认为false + nvuePagingEnabled: { + type: Boolean, + default: u.gc('nvuePagingEnabled', false) + }, + // 是否隐藏nvue列表底部的tagView,此view用于标识滚动到底部位置,若隐藏则滚动到底部功能将失效,在nvue中实现吸顶+swiper功能时需将最外层z-paging的此属性设置为true。默认为否 + hideNvueBottomTag: { + type: Boolean, + default: u.gc('hideNvueBottomTag', false) + }, + // nvue中控制onscroll事件触发的频率:表示两次onscroll事件之间列表至少滚动了10px。注意,将该值设置为较小的数值会提高滚动事件采样的精度,但同时也会降低页面的性能 + offsetAccuracy: { + type: Number, + default: u.gc('offsetAccuracy', 10) + }, + // #endif + }, + data() { + return { + nRefresherLoading: false, + nListIsDragging: false, + nShowBottom: true, + nFixFreezing: false, + nShowRefresherReveal: false, + nLoadingMoreFixedHeight: false, + nShowRefresherRevealHeight: 0, + nOldShowRefresherRevealHeight: -1, + nRefresherWidth: uni.upx2px(750), + nF2Opacity: 0 + } + }, + computed: { + // #ifdef APP-NVUE + nScopedSlots() { + // #ifdef VUE2 + return this.$scopedSlots; + // #endif + // #ifdef VUE3 + return null; + // #endif + }, + nWaterfallColumnCount() { + if (this.finalNvueListIs !== 'waterfall') return 0; + return this._nGetWaterfallConfig('column-count', 2); + }, + nWaterfallColumnWidth() { + return this._nGetWaterfallConfig('column-width', 'auto'); + }, + nWaterfallColumnGap() { + return this._nGetWaterfallConfig('column-gap', 'normal'); + }, + nWaterfallLeftGap() { + return this._nGetWaterfallConfig('left-gap', 0); + }, + nWaterfallRightGap() { + return this._nGetWaterfallConfig('right-gap', 0); + }, + nViewIs() { + const is = this.finalNvueListIs; + return is === 'scroller' || is === 'view' ? 'view' : is === 'waterfall' ? 'header' : 'cell'; + }, + nSafeAreaBottomHeight() { + return this.safeAreaInsetBottom ? this.safeAreaBottom : 0; + }, + finalNvueListIs() { + if (this.usePageScroll) return 'view'; + const nvueListIsLowerCase = this.nvueListIs.toLowerCase(); + if (['list','waterfall','scroller'].indexOf(nvueListIsLowerCase) !== -1) return nvueListIsLowerCase; + return 'list'; + }, + finalNvueSuperListIs() { + return this.usePageScroll ? 'view' : 'scroller'; + }, + finalNvueRefresherEnabled() { + return this.finalNvueListIs !== 'view' && this.finalRefresherEnabled && !this.nShowRefresherReveal && !this.useChatRecordMode; + }, + // #endif + }, + mounted(){ + // #ifdef APP-NVUE + //旋转屏幕时更新宽度 + uni.onWindowResize((res) => { + // this._nUpdateRefresherWidth(); + }) + // #endif + }, + methods: { + // #ifdef APP-NVUE + // 列表滚动时触发 + _nOnScroll(e) { + this.$emit('scroll', e); + const contentOffsetY = -e.contentOffset.y; + this.oldScrollTop = contentOffsetY; + this.nListIsDragging = e.isDragging; + this._checkShouldShowBackToTop(contentOffsetY, contentOffsetY - 1); + }, + // 列表滚动结束 + _nOnScrollend(e) { + this.$emit('scrollend', e); + }, + // 下拉刷新刷新中 + _nOnRrefresh() { + if (this.nShowRefresherReveal) return; + // 进入刷新状态 + this.nRefresherLoading = true; + if (this.refresherStatus === Enum.Refresher.GoF2) { + this._handleGoF2(); + this.$nextTick(() => { + this._nRefresherEnd(); + }) + } else { + this.refresherStatus = Enum.Refresher.Loading; + this._doRefresherLoad(); + } + + }, + // 下拉刷新下拉中 + _nOnPullingdown(e) { + if (this.refresherStatus === Enum.Refresher.Loading || (this.isIos && !this.nListIsDragging)) return; + this._emitTouchmove(e); + let { viewHeight, pullingDistance } = e; + // 更新下拉刷新状态 + // 下拉刷新距离超过阈值 + if (pullingDistance >= viewHeight) { + // 如果开启了下拉进入二楼并且下拉刷新距离超过进入二楼阈值,则当前下拉刷新状态为松手进入二楼,否则为松手立即刷新 + // (pullingDistance - viewHeight) + this.finalRefresherThreshold 不等同于pullingDistance,此处是为了兼容不同平台下拉相同距离pullingDistance不一致的问题,pullingDistance仅与viewHeight互相关联 + this.refresherStatus = this.refresherF2Enabled && (pullingDistance - viewHeight) + this.finalRefresherThreshold >= this.finalRefresherF2Threshold ? Enum.Refresher.GoF2 : Enum.Refresher.ReleaseToRefresh; + } else { + // 下拉刷新距离未超过阈值,显示默认状态 + this.refresherStatus = Enum.Refresher.Default; + } + }, + // 下拉刷新结束 + _nRefresherEnd(doEnd = true) { + if (doEnd) { + this._nDoRefresherEndAnimation(0, -this.nShowRefresherRevealHeight); + !this.usePageScroll && this.$refs['zp-n-list'].resetLoadmore(); + this.nRefresherLoading = false; + } + }, + // 执行主动触发下拉刷新动画 + _nDoRefresherEndAnimation(height, translateY, animate = true, checkStack = true) { + // 清除下拉刷新相关timeout + this._cleanRefresherCompleteTimeout(); + this._cleanRefresherEndTimeout(); + + if (!this.finalShowRefresherWhenReload) { + // 如果reload不需要自动展示下拉刷新view,则在complete duration结束后再把下拉刷新状态设置回默认 + this.refresherEndTimeout = u.delay(() => { + this.refresherStatus = Enum.Refresher.Default; + }, this.refresherCompleteDuration); + return; + } + // 用户处理用户在短时间内多次调用reload的情况,此时下拉刷新view不需要重复显示,只需要保证最后一次reload对应的请求结束后收回下拉刷新view即可 + const stackCount = this.refresherRevealStackCount; + if (height === 0 && checkStack) { + this.refresherRevealStackCount --; + if (stackCount > 1) return; + this.refresherEndTimeout = u.delay(() => { + this.refresherStatus = Enum.Refresher.Default; + }, this.refresherCompleteDuration); + } + if (stackCount > 1) { + this.refresherStatus = Enum.Refresher.Loading; + } + + const duration = animate ? 200 : 0; + if (this.nOldShowRefresherRevealHeight !== height) { + if (height > 0) { + this.nShowRefresherReveal = true; + } + // 展示下拉刷新view + weexAnimation.transition(this.$refs['zp-n-list-refresher-reveal'], { + styles: { + height: `${height}px`, + transform: `translateY(${translateY}px)`, + }, + duration, + timingFunction: 'linear', + needLayout: true, + delay: 0 + }) + } + u.delay(() => { + if (animate) { + this.nShowRefresherReveal = height > 0; + } + }, duration > 0 ? duration - 60 : 0); + this.nOldShowRefresherRevealHeight = height; + }, + // 滚动到底部加载更多 + _nOnLoadmore() { + if (this.nShowRefresherReveal || !this.totalData.length) return; + this.useChatRecordMode ? this.doChatRecordLoadMore() : this._onLoadingMore('toBottom'); + }, + // 获取nvue waterfall单项配置 + _nGetWaterfallConfig(key, defaultValue) { + return this.nvueWaterfallConfig[key] || defaultValue; + }, + // 更新nvue 下拉刷新view容器的宽度 + _nUpdateRefresherWidth() { + u.delay(() => { + this.$nextTick(()=>{ + this._getNodeClientRect('.zp-n-list').then(node => { + if (node) { + this.nRefresherWidth = node[0].width || this.nRefresherWidth; + } + }) + }) + }) + } + // #endif + } +} diff --git a/uni_modules/z-paging/components/z-paging/js/modules/refresher.js b/uni_modules/z-paging/components/z-paging/js/modules/refresher.js new file mode 100644 index 0000000..057bebb --- /dev/null +++ b/uni_modules/z-paging/components/z-paging/js/modules/refresher.js @@ -0,0 +1,824 @@ +// [z-paging]下拉刷新view模块 +import u from '.././z-paging-utils' +import c from '.././z-paging-constant' +import Enum from '.././z-paging-enum' + +// #ifdef APP-NVUE +const weexAnimation = weex.requireModule('animation'); +// #endif +export default { + props: { + // 下拉刷新的主题样式,支持black,white,默认black + refresherThemeStyle: { + type: String, + default: u.gc('refresherThemeStyle', '') + }, + // 自定义下拉刷新中左侧图标的样式 + refresherImgStyle: { + type: Object, + default: u.gc('refresherImgStyle', {}) + }, + // 自定义下拉刷新中右侧状态描述文字的样式 + refresherTitleStyle: { + type: Object, + default: u.gc('refresherTitleStyle', {}) + }, + // 自定义下拉刷新中右侧最后更新时间文字的样式(show-refresher-update-time为true时有效) + refresherUpdateTimeStyle: { + type: Object, + default: u.gc('refresherUpdateTimeStyle', {}) + }, + // 在微信小程序和QQ小程序中,是否实时监听下拉刷新中进度,默认为否 + watchRefresherTouchmove: { + type: Boolean, + default: u.gc('watchRefresherTouchmove', false) + }, + // 底部加载更多的主题样式,支持black,white,默认black + loadingMoreThemeStyle: { + type: String, + default: u.gc('loadingMoreThemeStyle', '') + }, + // 是否只使用下拉刷新,设置为true后将关闭mounted自动请求数据、关闭滚动到底部加载更多,强制隐藏空数据图。默认为否 + refresherOnly: { + type: Boolean, + default: u.gc('refresherOnly', false) + }, + // 自定义下拉刷新默认状态下回弹动画时间,单位为毫秒,默认为100毫秒,nvue无效 + refresherDefaultDuration: { + type: [Number, String], + default: u.gc('refresherDefaultDuration', 100) + }, + // 自定义下拉刷新结束以后延迟回弹的时间,单位为毫秒,默认为0 + refresherCompleteDelay: { + type: [Number, String], + default: u.gc('refresherCompleteDelay', 0) + }, + // 自定义下拉刷新结束回弹动画时间,单位为毫秒,默认为300毫秒(refresherEndBounceEnabled为false时,refresherCompleteDuration为设定值的1/3),nvue无效 + refresherCompleteDuration: { + type: [Number, String], + default: u.gc('refresherCompleteDuration', 300) + }, + // 自定义下拉刷新中是否允许列表滚动,默认为是 + refresherRefreshingScrollable: { + type: Boolean, + default: u.gc('refresherRefreshingScrollable', true) + }, + // 自定义下拉刷新结束状态下是否允许列表滚动,默认为否 + refresherCompleteScrollable: { + type: Boolean, + default: u.gc('refresherCompleteScrollable', false) + }, + // 是否使用自定义的下拉刷新,默认为是,即使用z-paging的下拉刷新。设置为false即代表使用uni scroll-view自带的下拉刷新,h5、App、微信小程序以外的平台不支持uni scroll-view自带的下拉刷新 + useCustomRefresher: { + type: Boolean, + default: u.gc('useCustomRefresher', true) + }, + // 自定义下拉刷新下拉帧率,默认为40,过高可能会出现抖动问题 + refresherFps: { + type: [Number, String], + default: u.gc('refresherFps', 40) + }, + // 自定义下拉刷新允许触发的最大下拉角度,默认为40度,当下拉角度小于设定值时,自定义下拉刷新动画不会被触发 + refresherMaxAngle: { + type: [Number, String], + default: u.gc('refresherMaxAngle', 40) + }, + // 自定义下拉刷新的角度由未达到最大角度变到达到最大角度时,是否继续下拉刷新手势,默认为否 + refresherAngleEnableChangeContinued: { + type: Boolean, + default: u.gc('refresherAngleEnableChangeContinued', false) + }, + // 自定义下拉刷新默认状态下的文字 + refresherDefaultText: { + type: [String, Object], + default: u.gc('refresherDefaultText', null) + }, + // 自定义下拉刷新松手立即刷新状态下的文字 + refresherPullingText: { + type: [String, Object], + default: u.gc('refresherPullingText', null) + }, + // 自定义下拉刷新刷新中状态下的文字 + refresherRefreshingText: { + type: [String, Object], + default: u.gc('refresherRefreshingText', null) + }, + // 自定义下拉刷新刷新结束状态下的文字 + refresherCompleteText: { + type: [String, Object], + default: u.gc('refresherCompleteText', null) + }, + // 自定义继续下拉进入二楼文字 + refresherGoF2Text: { + type: [String, Object], + default: u.gc('refresherGoF2Text', null) + }, + // 自定义下拉刷新默认状态下的图片 + refresherDefaultImg: { + type: String, + default: u.gc('refresherDefaultImg', null) + }, + // 自定义下拉刷新松手立即刷新状态下的图片,默认与refresherDefaultImg一致 + refresherPullingImg: { + type: String, + default: u.gc('refresherPullingImg', null) + }, + // 自定义下拉刷新刷新中状态下的图片 + refresherRefreshingImg: { + type: String, + default: u.gc('refresherRefreshingImg', null) + }, + // 自定义下拉刷新刷新结束状态下的图片 + refresherCompleteImg: { + type: String, + default: u.gc('refresherCompleteImg', null) + }, + // 自定义下拉刷新刷新中状态下是否展示旋转动画 + refresherRefreshingAnimated: { + type: Boolean, + default: u.gc('refresherRefreshingAnimated', true) + }, + // 是否开启自定义下拉刷新刷新结束回弹效果,默认为是 + refresherEndBounceEnabled: { + type: Boolean, + default: u.gc('refresherEndBounceEnabled', true) + }, + // 是否开启自定义下拉刷新,默认为是 + refresherEnabled: { + type: Boolean, + default: u.gc('refresherEnabled', true) + }, + // 设置自定义下拉刷新阈值,默认为80rpx + refresherThreshold: { + type: [Number, String], + default: u.gc('refresherThreshold', '80rpx') + }, + // 设置系统下拉刷新默认样式,支持设置 black,white,none,none 表示不使用默认样式,默认为black + refresherDefaultStyle: { + type: String, + default: u.gc('refresherDefaultStyle', 'black') + }, + // 设置自定义下拉刷新区域背景 + refresherBackground: { + type: String, + default: u.gc('refresherBackground', 'transparent') + }, + // 设置固定的自定义下拉刷新区域背景 + refresherFixedBackground: { + type: String, + default: u.gc('refresherFixedBackground', 'transparent') + }, + // 设置固定的自定义下拉刷新区域高度,默认为0 + refresherFixedBacHeight: { + type: [Number, String], + default: u.gc('refresherFixedBacHeight', 0) + }, + // 设置自定义下拉刷新下拉超出阈值后继续下拉位移衰减的比例,范围0-1,值越大代表衰减越多。默认为0.65(nvue无效) + refresherOutRate: { + type: Number, + default: u.gc('refresherOutRate', 0.65) + }, + // 是否开启下拉进入二楼功能,默认为否 + refresherF2Enabled: { + type: Boolean, + default: u.gc('refresherF2Enabled', false) + }, + // 下拉进入二楼阈值,默认为200rpx + refresherF2Threshold: { + type: [Number, String], + default: u.gc('refresherF2Threshold', '200rpx') + }, + // 下拉进入二楼动画时间,单位为毫秒,默认为200毫秒 + refresherF2Duration: { + type: [Number, String], + default: u.gc('refresherF2Duration', 200) + }, + // 下拉进入二楼状态松手后是否弹出二楼,默认为是 + showRefresherF2: { + type: Boolean, + default: u.gc('showRefresherF2', true) + }, + // 设置自定义下拉刷新下拉时实际下拉位移与用户下拉距离的比值,默认为0.75,即代表若用户下拉10px,则实际位移为7.5px(nvue无效) + refresherPullRate: { + type: Number, + default: u.gc('refresherPullRate', 0.75) + }, + // 是否显示最后更新时间,默认为否 + showRefresherUpdateTime: { + type: Boolean, + default: u.gc('showRefresherUpdateTime', false) + }, + // 如果需要区别不同页面的最后更新时间,请为不同页面的z-paging的`refresher-update-time-key`设置不同的字符串 + refresherUpdateTimeKey: { + type: String, + default: u.gc('refresherUpdateTimeKey', 'default') + }, + // 下拉刷新时下拉到“松手立即刷新”或“松手进入二楼”状态时是否使手机短振动,默认为否(h5无效) + refresherVibrate: { + type: Boolean, + default: u.gc('refresherVibrate', false) + }, + // 下拉刷新时是否禁止下拉刷新view跟随用户触摸竖直移动,默认为否。注意此属性只是禁止下拉刷新view移动,其他下拉刷新逻辑依然会正常触发 + refresherNoTransform: { + type: Boolean, + default: u.gc('refresherNoTransform', false) + }, + // 是否开启下拉刷新状态栏占位,适用于隐藏导航栏时,下拉刷新需要避开状态栏高度的情况,默认为否 + useRefresherStatusBarPlaceholder: { + type: Boolean, + default: u.gc('useRefresherStatusBarPlaceholder', false) + }, + }, + data() { + return { + R: Enum.Refresher, + //下拉刷新状态 + refresherStatus: Enum.Refresher.Default, + refresherTouchstartY: 0, + lastRefresherTouchmove: null, + refresherReachMaxAngle: true, + refresherTransform: 'translateY(0px)', + refresherTransition: '', + finalRefresherDefaultStyle: 'black', + refresherRevealStackCount: 0, + refresherCompleteTimeout: null, + refresherCompleteSubTimeout: null, + refresherEndTimeout: null, + isTouchmovingTimeout: null, + refresherTriggered: false, + isTouchmoving: false, + isTouchEnded: false, + isUserPullDown: false, + privateRefresherEnabled: -1, + privateShowRefresherWhenReload: false, + customRefresherHeight: -1, + showCustomRefresher: false, + doRefreshAnimateAfter: false, + isRefresherInComplete: false, + showF2: false, + f2Transform: '', + pullDownTimeStamp: 0, + moveDis: 0, + oldMoveDis: 0, + currentDis: 0, + oldCurrentMoveDis: 0, + oldRefresherTouchmoveY: 0, + oldTouchDirection: '', + oldEmitedTouchDirection: '', + oldPullingDistance: -1, + refresherThresholdUpdateTag: 0 + } + }, + watch: { + refresherDefaultStyle: { + handler(newVal) { + if (newVal.length) { + this.finalRefresherDefaultStyle = newVal; + } + }, + immediate: true + }, + refresherStatus(newVal) { + newVal === Enum.Refresher.Loading && this._cleanRefresherEndTimeout(); + this.refresherVibrate && (newVal === Enum.Refresher.ReleaseToRefresh || newVal === Enum.Refresher.GoF2) && this._doVibrateShort(); + this.$emit('refresherStatusChange', newVal); + this.$emit('update:refresherStatus', newVal); + }, + // 监听当前下拉刷新启用/禁用状态 + refresherEnabled(newVal) { + // 当禁用下拉刷新时,强制收回正在展示的下拉刷新view + !newVal && this.endRefresh(); + } + }, + computed: { + pullDownDisTimeStamp() { + return 1000 / this.refresherFps; + }, + refresherThresholdUnitConverted() { + return u.addUnit(this.refresherThreshold, this.unit); + }, + finalRefresherEnabled() { + if (this.useChatRecordMode) return false; + if (this.privateRefresherEnabled === -1) return this.refresherEnabled; + return this.privateRefresherEnabled === 1; + }, + finalRefresherThreshold() { + let refresherThreshold = this.refresherThresholdUnitConverted; + let idDefault = false; + if (refresherThreshold === u.addUnit(80, this.unit)) { + idDefault = true; + if (this.showRefresherUpdateTime) { + refresherThreshold = u.addUnit(120, this.unit); + } + } + if (idDefault && this.customRefresherHeight > 0) return this.customRefresherHeight + this.finalRefresherThresholdPlaceholder; + return u.convertToPx(refresherThreshold) + this.finalRefresherThresholdPlaceholder; + }, + finalRefresherF2Threshold() { + return u.convertToPx(u.addUnit(this.refresherF2Threshold, this.unit)); + }, + finalRefresherThresholdPlaceholder() { + return this.useRefresherStatusBarPlaceholder ? this.statusBarHeight : 0; + }, + finalRefresherFixedBacHeight() { + return u.convertToPx(this.refresherFixedBacHeight); + }, + finalRefresherThemeStyle() { + return this.refresherThemeStyle.length ? this.refresherThemeStyle : this.defaultThemeStyle; + }, + finalRefresherOutRate() { + let rate = this.refresherOutRate; + rate = Math.max(0,rate); + rate = Math.min(1,rate); + return rate; + }, + finalRefresherPullRate() { + let rate = this.refresherPullRate; + rate = Math.max(0,rate); + return rate; + }, + finalRefresherTransform() { + if (this.refresherNoTransform || this.refresherTransform === 'translateY(0px)') return 'none'; + return this.refresherTransform; + }, + finalShowRefresherWhenReload() { + return this.showRefresherWhenReload || this.privateShowRefresherWhenReload; + }, + finalRefresherTriggered() { + if (!(this.finalRefresherEnabled && !this.useCustomRefresher)) return false; + return this.refresherTriggered; + }, + showRefresher() { + const showRefresher = this.finalRefresherEnabled || this.useCustomRefresher && !this.useChatRecordMode; + // #ifndef APP-NVUE + this.active && this.customRefresherHeight === -1 && showRefresher && this.updateCustomRefresherHeight(); + // #endif + return showRefresher; + }, + hasTouchmove() { + // #ifdef VUE2 + // #ifdef APP-VUE || H5 + if (this.$listeners && !this.$listeners.refresherTouchmove) return false; + // #endif + // #ifdef MP-WEIXIN || MP-QQ + return this.watchRefresherTouchmove; + // #endif + return true; + // #endif + return this.watchRefresherTouchmove; + }, + }, + methods: { + // 终止下拉刷新状态 + endRefresh() { + this.totalData = this.realTotalData; + this._refresherEnd(); + this._endSystemLoadingAndRefresh(); + this._handleScrollViewBounce({ bounce: true }); + this.$nextTick(() => { + this.refresherTriggered = false; + }) + }, + // 手动更新自定义下拉刷新view高度 + updateCustomRefresherHeight() { + u.delay(() => this.$nextTick(this._updateCustomRefresherHeight)); + }, + // 关闭二楼 + closeF2() { + this._handleCloseF2(); + }, + // 自定义下拉刷新被触发 + _onRefresh(fromScrollView = false, isUserPullDown = true) { + if (fromScrollView && !(this.finalRefresherEnabled && !this.useCustomRefresher)) return; + this.$emit('onRefresh'); + this.$emit('Refresh'); + // #ifdef APP-NVUE + if (this.loading) { + u.delay(this._nRefresherEnd, 500) + return; + } + // #endif + if (this.loading || this.isRefresherInComplete) return; + this.loadingType = Enum.LoadingType.Refresher; + if (this.nShowRefresherReveal) return; + this.isUserPullDown = isUserPullDown; + this.isUserReload = !isUserPullDown; + this._startLoading(true); + this.refresherTriggered = true; + if(this.reloadWhenRefresh && isUserPullDown){ + this.useChatRecordMode ? this._onLoadingMore('click') : this._reload(false, false, isUserPullDown); + } + }, + // 自定义下拉刷新被复位 + _onRestore() { + this.refresherTriggered = 'restore'; + this.$emit('onRestore'); + this.$emit('Restore'); + }, + // #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5 + // touch开始 + _refresherTouchstart(e) { + this._handleListTouchstart(); + if (this._touchDisabled()) return; + this._handleRefresherTouchstart(u.getTouch(e)); + }, + // #endif + // 进一步处理touch开始结果 + _handleRefresherTouchstart(touch) { + if (!this.loading && this.isTouchEnded) { + this.isTouchmoving = false; + } + this.loadingType = Enum.LoadingType.Refresher; + this.isTouchmovingTimeout && clearTimeout(this.isTouchmovingTimeout); + this.isTouchEnded = false; + this.refresherTransition = ''; + this.refresherTouchstartY = touch.touchY; + this.$emit('refresherTouchstart', this.refresherTouchstartY); + this.lastRefresherTouchmove = touch; + this._cleanRefresherCompleteTimeout(); + this._cleanRefresherEndTimeout(); + }, + + // 非appvue或微信小程序或QQ小程序或h5平台,使用js控制下拉刷新 + // #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5 + // touch中 + _refresherTouchmove(e) { + const currentTimeStamp = u.getTime(); + let touch = null; + let refresherTouchmoveY = 0; + if (this.watchTouchDirectionChange) { + // 检测下拉刷新方向改变 + touch = u.getTouch(e); + refresherTouchmoveY = touch.touchY; + const direction = refresherTouchmoveY > this.oldRefresherTouchmoveY ? 'top' : 'bottom'; + // 只有在方向改变的时候才emit相关事件 + if (direction === this.oldTouchDirection && direction !== this.oldEmitedTouchDirection) { + this._handleTouchDirectionChange({ direction }); + this.oldEmitedTouchDirection = direction; + } + this.oldTouchDirection = direction; + this.oldRefresherTouchmoveY = refresherTouchmoveY; + } + // 节流处理,在pullDownDisTimeStamp时间内的下拉刷新中事件不进行处理 + if (this.pullDownTimeStamp && currentTimeStamp - this.pullDownTimeStamp <= this.pullDownDisTimeStamp) return; + // 如果不允许下拉,则return + if (this._touchDisabled()) return; + this.pullDownTimeStamp = Number(currentTimeStamp); + touch = u.getTouch(e); + refresherTouchmoveY = touch.touchY; + // 获取当前touch的y - 初始touch的y,计算它们的差 + let moveDis = refresherTouchmoveY - this.refresherTouchstartY; + if (moveDis < 0) return; + // 对下拉刷新的角度进行限制 + if (this.refresherMaxAngle >= 0 && this.refresherMaxAngle <= 90 && this.lastRefresherTouchmove && this.lastRefresherTouchmove.touchY <= refresherTouchmoveY) { + if (!moveDis && !this.refresherAngleEnableChangeContinued && this.moveDis < 1 && !this.refresherReachMaxAngle) return; + const x = Math.abs(touch.touchX - this.lastRefresherTouchmove.touchX); + const y = Math.abs(refresherTouchmoveY - this.lastRefresherTouchmove.touchY); + const z = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)); + if ((x || y) && x > 1) { + // 获取下拉刷新前后两次位移的角度 + const angle = Math.asin(y / z) / Math.PI * 180; + // 如果角度小于配置要求,则return + if (angle < this.refresherMaxAngle) { + this.lastRefresherTouchmove = touch; + this.refresherReachMaxAngle = false; + return; + } + } + } + // 获取最终的moveDis + moveDis = this._getFinalRefresherMoveDis(moveDis); + // 处理下拉刷新位移 + this._handleRefresherTouchmove(moveDis, touch); + // 下拉刷新时,禁止页面滚动以防止页面向下滚动和下拉刷新同时作用导致下拉刷新位置偏移超过预期 + if (!this.disabledBounce) { + // #ifndef MP-LARK + this._handleScrollViewBounce({ bounce: false }); + // #endif + this.disabledBounce = true; + } + this._emitTouchmove({ pullingDistance: moveDis, dy: this.moveDis - this.oldMoveDis }); + }, + // #endif + // 进一步处理touch中结果 + _handleRefresherTouchmove(moveDis, touch) { + this.refresherReachMaxAngle = true; + this.isTouchmovingTimeout && clearTimeout(this.isTouchmovingTimeout); + this.isTouchmoving = true; + this.isTouchEnded = false; + // 更新下拉刷新状态 + // 下拉刷新距离超过阈值 + if (moveDis >= this.finalRefresherThreshold) { + // 如果开启了下拉进入二楼并且下拉刷新距离超过进入二楼阈值,则当前下拉刷新状态为松手进入二楼,否则为松手立即刷新 + this.refresherStatus = this.refresherF2Enabled && moveDis >= this.finalRefresherF2Threshold ? Enum.Refresher.GoF2 : Enum.Refresher.ReleaseToRefresh; + } else { + // 下拉刷新距离未超过阈值,显示默认状态 + this.refresherStatus = Enum.Refresher.Default; + } + // #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5 + // this.scrollEnable = false; + // 通过transform控制下拉刷新view垂直偏移 + this.refresherTransform = `translateY(${moveDis}px)`; + this.lastRefresherTouchmove = touch; + // #endif + this.moveDis = moveDis; + }, + // #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5 + // touch结束 + _refresherTouchend(e) { + // 下拉刷新用户手离开屏幕,允许列表滚动 + this._handleScrollViewBounce({bounce: true}); + if (this._touchDisabled() || !this.isTouchmoving) return; + const touch = u.getTouch(e); + let refresherTouchendY = touch.touchY; + let moveDis = refresherTouchendY - this.refresherTouchstartY; + moveDis = this._getFinalRefresherMoveDis(moveDis); + this._handleRefresherTouchend(moveDis); + this.disabledBounce = false; + }, + // #endif + // 进一步处理touch结束结果 + _handleRefresherTouchend(moveDis) { + // #ifndef APP-PLUS || H5 || MP-WEIXIN + if (!this.isTouchmoving) return; + // #endif + this.isTouchmovingTimeout && clearTimeout(this.isTouchmovingTimeout); + this.refresherReachMaxAngle = true; + this.isTouchEnded = true; + const refresherThreshold = this.finalRefresherThreshold; + if (moveDis >= refresherThreshold && (this.refresherStatus === Enum.Refresher.ReleaseToRefresh || this.refresherStatus === Enum.Refresher.GoF2)) { + // 如果是松手进入二楼状态,则触发进入二楼 + if (this.refresherStatus === Enum.Refresher.GoF2) { + this._handleGoF2(); + this._refresherEnd(); + } else { + // 如果是松手立即刷新状态,则触发下拉刷新 + // #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5 + this.refresherTransform = `translateY(${refresherThreshold}px)`; + this.refresherTransition = 'transform .1s linear'; + // #endif + u.delay(() => { + this._emitTouchmove({ pullingDistance: refresherThreshold, dy: this.moveDis - refresherThreshold }); + }, 0.1); + this.moveDis = refresherThreshold; + this.refresherStatus = Enum.Refresher.Loading; + this._doRefresherLoad(); + } + } else { + this._refresherEnd(); + this.isTouchmovingTimeout = u.delay(() => { + this.isTouchmoving = false; + }, this.refresherDefaultDuration); + } + this.scrollEnable = true; + this.$emit('refresherTouchend', moveDis); + }, + // 处理列表触摸开始事件 + _handleListTouchstart() { + if (this.useChatRecordMode && this.autoHideKeyboardWhenChat) { + uni.hideKeyboard(); + this.$emit('hidedKeyboard'); + } + }, + // 处理scroll-view bounce是否生效 + _handleScrollViewBounce({ bounce }) { + if (!this.usePageScroll && !this.scrollToTopBounceEnabled) { + if (this.wxsScrollTop <= 5) { + // #ifdef APP-VUE || MP-WEIXIN || MP-QQ || H5 + this.refresherTransition = ''; + // #endif + this.scrollEnable = bounce; + } else if (bounce) { + this.scrollEnable = bounce; + } + } + }, + // wxs正在下拉状态改变处理 + _handleWxsPullingDownStatusChange(onPullingDown) { + this.wxsOnPullingDown = onPullingDown; + if (onPullingDown && !this.useChatRecordMode) { + this.renderPropScrollTop = 0; + } + }, + // wxs正在下拉处理 + _handleWxsPullingDown({ moveDis, diffDis }){ + this._emitTouchmove({ pullingDistance: moveDis,dy: diffDis }); + }, + // wxs触摸方向改变 + _handleTouchDirectionChange({ direction }) { + this.$emit('touchDirectionChange',direction); + }, + // wxs通知更新其props + _handlePropUpdate(){ + this.wxsPropType = u.getTime().toString(); + }, + // 下拉刷新结束 + _refresherEnd(shouldEndLoadingDelay = true, fromAddData = false, isUserPullDown = false, setLoading = true) { + if (this.loadingType === Enum.LoadingType.Refresher) { + const refresherCompleteDelay = (fromAddData && (isUserPullDown || this.showRefresherWhenReload)) ? this.refresherCompleteDelay : 0; + const refresherStatus = refresherCompleteDelay > 0 ? Enum.Refresher.Complete : Enum.Refresher.Default; + if (this.finalShowRefresherWhenReload) { + const stackCount = this.refresherRevealStackCount; + this.refresherRevealStackCount --; + if (stackCount > 1) return; + } + this._cleanRefresherEndTimeout(); + this.refresherEndTimeout = u.delay(() => { + this.refresherStatus = refresherStatus; + }, this.refresherStatus !== Enum.Refresher.Default && refresherStatus === Enum.Refresher.Default ? this.refresherCompleteDuration : 0); + + // #ifndef APP-NVUE + if (refresherCompleteDelay > 0) { + this.isRefresherInComplete = true; + } + // #endif + this._cleanRefresherCompleteTimeout(); + this.refresherCompleteTimeout = u.delay(() => { + let animateDuration = 1; + const animateType = this.refresherEndBounceEnabled && fromAddData ? 'cubic-bezier(0.19,1.64,0.42,0.72)' : 'linear'; + if (fromAddData) { + animateDuration = this.refresherEndBounceEnabled ? this.refresherCompleteDuration / 1000 : this.refresherCompleteDuration / 3000; + } + this.refresherTransition = `transform ${fromAddData ? animateDuration : this.refresherDefaultDuration / 1000}s ${animateType}`; + // #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5 + this.refresherTransform = 'translateY(0px)'; + this.currentDis = 0; + // #endif + // #ifdef APP-VUE || MP-WEIXIN || MP-QQ || H5 + this.wxsPropType = this.refresherTransition + 'end' + u.getTime(); + // #endif + // #ifdef APP-NVUE + this._nRefresherEnd(); + // #endif + this.moveDis = 0; + // #ifndef APP-NVUE + if (refresherStatus === Enum.Refresher.Complete) { + if (this.refresherCompleteSubTimeout) { + clearTimeout(this.refresherCompleteSubTimeout); + this.refresherCompleteSubTimeout = null; + } + this.refresherCompleteSubTimeout = u.delay(() => { + this.$nextTick(() => { + this.refresherStatus = Enum.Refresher.Default; + this.isRefresherInComplete = false; + }) + }, animateDuration * 800); + } + // #endif + this._emitTouchmove({ pullingDistance: 0, dy: this.moveDis }); + }, refresherCompleteDelay); + } + if (setLoading) { + u.delay(() => this.loading = false, shouldEndLoadingDelay ? 10 : 0); + isUserPullDown && this._onRestore(); + } + }, + // 处理进入二楼 + _handleGoF2() { + if (this.showF2 || !this.refresherF2Enabled) return; + this.$emit('refresherF2Change', 'go'); + + if (!this.showRefresherF2) return; + // #ifndef APP-NVUE + this.f2Transform = `translateY(${-this.superContentHeight}px)`; + this.showF2 = true; + u.delay(() => { + this.f2Transform = 'translateY(0px)'; + }, 100, 'f2ShowDelay') + // #endif + + // #ifdef APP-NVUE + this.showF2 = true; + this.$nextTick(() => { + weexAnimation.transition(this.$refs['zp-n-f2'], { + styles: { transform: `translateY(${-this.superContentHeight}px)` }, + duration: 0, + timingFunction: 'linear', + needLayout: true, + delay: 0 + }) + this.nF2Opacity = 1; + }) + u.delay(() => { + weexAnimation.transition(this.$refs['zp-n-f2'], { + styles: { transform: 'translateY(0px)' }, + duration: this.refresherF2Duration, + timingFunction: 'linear', + needLayout: true, + delay: 0 + }) + }, 10, 'f2GoDelay') + // #endif + }, + // 处理退出二楼 + _handleCloseF2() { + if (!this.showF2 || !this.refresherF2Enabled) return; + this.$emit('refresherF2Change', 'close'); + + if (!this.showRefresherF2) return; + // #ifndef APP-NVUE + this.f2Transform = `translateY(${-this.superContentHeight}px)`; + // #endif + + // #ifdef APP-NVUE + weexAnimation.transition(this.$refs['zp-n-f2'], { + styles: { transform: `translateY(${-this.superContentHeight}px)` }, + duration: this.refresherF2Duration, + timingFunction: 'linear', + needLayout: true, + delay: 0 + }) + // #endif + + u.delay(() => { + this.showF2 = false; + this.nF2Opacity = 0; + }, this.refresherF2Duration, 'f2CloseDelay') + }, + // 模拟用户手动触发下拉刷新 + _doRefresherRefreshAnimate() { + this._cleanRefresherCompleteTimeout(); + // 用户处理用户在短时间内多次调用reload的情况,此时下拉刷新view不需要重复显示,只需要保证最后一次reload对应的请求结束后收回下拉刷新view即可 + // #ifndef APP-NVUE + const doRefreshAnimateAfter = !this.doRefreshAnimateAfter && (this.finalShowRefresherWhenReload) && this + .customRefresherHeight === -1 && this.refresherThreshold === u.addUnit(80, this.unit); + if (doRefreshAnimateAfter) { + this.doRefreshAnimateAfter = true; + return; + } + // #endif + this.refresherRevealStackCount ++; + // #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5 + this.refresherTransform = `translateY(${this.finalRefresherThreshold}px)`; + // #endif + // #ifdef APP-VUE || MP-WEIXIN || MP-QQ || H5 + this.wxsPropType = 'begin' + u.getTime(); + // #endif + this.moveDis = this.finalRefresherThreshold; + this.refresherStatus = Enum.Refresher.Loading; + this.isTouchmoving = true; + this.isTouchmovingTimeout && clearTimeout(this.isTouchmovingTimeout); + this._doRefresherLoad(false); + }, + // 触发下拉刷新 + _doRefresherLoad(isUserPullDown = true) { + this._onRefresh(false,isUserPullDown); + this.loading = true; + }, + // #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5 + // 获取处理后的moveDis + _getFinalRefresherMoveDis(moveDis) { + let diffDis = moveDis - this.oldCurrentMoveDis; + this.oldCurrentMoveDis = moveDis; + if (diffDis > 0) { + // 根据配置的下拉刷新用户手势位移与实际需要的位移比率计算最终的diffDis + diffDis = diffDis * this.finalRefresherPullRate; + if (this.currentDis > this.finalRefresherThreshold) { + diffDis = diffDis * (1 - this.finalRefresherOutRate); + } + } + // 控制diffDis过大的情况,比如进入页面突然猛然下拉,此时diffDis不应进行太大的偏移 + diffDis = diffDis > 100 ? diffDis / 100 : diffDis; + this.currentDis += diffDis; + this.currentDis = Math.max(0, this.currentDis); + return this.currentDis; + }, + // 判断touch手势是否要触发 + _touchDisabled() { + const checkOldScrollTop = this.oldScrollTop > 5; + return this.loading || this.isRefresherInComplete || this.useChatRecordMode || !this.refresherEnabled || !this.useCustomRefresher ||(this.usePageScroll && this.useCustomRefresher && this.pageScrollTop > 10) || (!(this.usePageScroll && this.useCustomRefresher) && checkOldScrollTop); + }, + // #endif + // 更新自定义下拉刷新view高度 + _updateCustomRefresherHeight() { + this._getNodeClientRect('.zp-custom-refresher-slot-view').then((res) => { + this.customRefresherHeight = res ? res[0].height : 0; + this.showCustomRefresher = this.customRefresherHeight > 0; + if (this.doRefreshAnimateAfter) { + this.doRefreshAnimateAfter = false; + this._doRefresherRefreshAnimate(); + } + }); + }, + // emit pullingDown事件 + _emitTouchmove(e) { + // #ifndef APP-NVUE + e.viewHeight = this.finalRefresherThreshold; + // #endif + e.rate = e.viewHeight > 0 ? e.pullingDistance / e.viewHeight : 0; + this.hasTouchmove && this.oldPullingDistance !== e.pullingDistance && this.$emit('refresherTouchmove', e); + this.oldPullingDistance = e.pullingDistance; + }, + // 清除refresherCompleteTimeout + _cleanRefresherCompleteTimeout() { + this.refresherCompleteTimeout = this._cleanTimeout(this.refresherCompleteTimeout); + // #ifdef APP-NVUE + this._nRefresherEnd(false); + // #endif + }, + // 清除refresherEndTimeout + _cleanRefresherEndTimeout() { + this.refresherEndTimeout = this._cleanTimeout(this.refresherEndTimeout); + }, + } +} diff --git a/uni_modules/z-paging/components/z-paging/js/modules/scroller.js b/uni_modules/z-paging/components/z-paging/js/modules/scroller.js new file mode 100644 index 0000000..3e57ee5 --- /dev/null +++ b/uni_modules/z-paging/components/z-paging/js/modules/scroller.js @@ -0,0 +1,520 @@ +// [z-paging]scroll相关模块 +import u from '.././z-paging-utils' +import Enum from '.././z-paging-enum' + +// #ifdef APP-NVUE +const weexDom = weex.requireModule('dom'); +// #endif + +export default { + props: { + // 使用页面滚动,默认为否,当设置为是时则使用页面的滚动而非此组件内部的scroll-view的滚动,使用页面滚动时z-paging无需设置确定的高度且对于长列表展示性能更高,但配置会略微繁琐 + usePageScroll: { + type: Boolean, + default: u.gc('usePageScroll', false) + }, + // 是否可以滚动,使用内置scroll-view和nvue时有效,默认为是 + scrollable: { + type: Boolean, + default: u.gc('scrollable', true) + }, + // 控制是否出现滚动条,默认为是 + showScrollbar: { + type: Boolean, + default: u.gc('showScrollbar', true) + }, + // 是否允许横向滚动,默认为否 + scrollX: { + type: Boolean, + default: u.gc('scrollX', false) + }, + // iOS设备上滚动到顶部时是否允许回弹效果,默认为否。关闭回弹效果后可使滚动到顶部与下拉刷新更连贯,但是有吸顶view时滚动到顶部时可能出现抖动。 + scrollToTopBounceEnabled: { + type: Boolean, + default: u.gc('scrollToTopBounceEnabled', false) + }, + // iOS设备上滚动到底部时是否允许回弹效果,默认为是。 + scrollToBottomBounceEnabled: { + type: Boolean, + default: u.gc('scrollToBottomBounceEnabled', true) + }, + // 在设置滚动条位置时使用动画过渡,默认为否 + scrollWithAnimation: { + type: Boolean, + default: u.gc('scrollWithAnimation', false) + }, + // 值应为某子元素id(id不能以数字开头)。设置哪个方向可滚动,则在哪个方向滚动到该元素 + scrollIntoView: { + type: String, + default: u.gc('scrollIntoView', '') + }, + }, + data() { + return { + scrollTop: 0, + oldScrollTop: 0, + scrollViewStyle: {}, + scrollViewContainerStyle: {}, + scrollViewInStyle: {}, + pageScrollTop: -1, + scrollEnable: true, + privateScrollWithAnimation: -1, + cacheScrollNodeHeight: -1, + superContentHeight: 0, + } + }, + watch: { + oldScrollTop(newVal) { + !this.usePageScroll && this._scrollTopChange(newVal,false); + }, + pageScrollTop(newVal) { + this.usePageScroll && this._scrollTopChange(newVal,true); + }, + usePageScroll: { + handler(newVal) { + this.loaded && this.autoHeight && this._setAutoHeight(!newVal); + // #ifdef H5 + if (newVal) { + this.$nextTick(() => { + const mainScrollRef = this.$refs['zp-scroll-view'].$refs.main; + if (mainScrollRef) { + mainScrollRef.style = {}; + } + }) + } + // #endif + }, + immediate: true + }, + finalScrollTop(newVal) { + this.renderPropScrollTop = newVal < 6 ? 0 : 10; + } + }, + computed: { + finalScrollWithAnimation() { + if (this.privateScrollWithAnimation !== -1) { + return this.privateScrollWithAnimation === 1; + } + return this.scrollWithAnimation; + }, + finalScrollViewStyle() { + if (this.superContentZIndex != 1) { + this.scrollViewStyle['z-index'] = this.superContentZIndex; + this.scrollViewStyle['position'] = 'relative'; + } + return this.scrollViewStyle; + }, + finalScrollTop() { + return this.usePageScroll ? this.pageScrollTop : this.oldScrollTop; + }, + // 当前是否是旧版webview + finalIsOldWebView() { + return this.isOldWebView && !this.usePageScroll; + }, + // 当前scroll-view/list-view是否允许滚动 + finalScrollable() { + return this.scrollable && !this.usePageScroll && this.scrollEnable + && (this.refresherCompleteScrollable ? true : this.refresherStatus !== Enum.Refresher.Complete) + && (this.refresherRefreshingScrollable ? true : this.refresherStatus !== Enum.Refresher.Loading); + } + }, + methods: { + // 滚动到顶部,animate为是否展示滚动动画,默认为是 + scrollToTop(animate, checkReverse = true) { + // 如果是聊天记录模式并且列表倒置了,则滚动到顶部实际上是滚动到底部 + if (this.useChatRecordMode && checkReverse && !this.isChatRecordModeAndNotInversion) { + this.scrollToBottom(animate, false); + return; + } + this.$nextTick(() => { + this._scrollToTop(animate, false); + // #ifdef APP-NVUE + if (this.nvueFastScroll && animate) { + u.delay(() => { + this._scrollToTop(false, false); + }); + } + // #endif + }) + }, + // 滚动到底部,animate为是否展示滚动动画,默认为是 + scrollToBottom(animate, checkReverse = true) { + // 如果是聊天记录模式并且列表倒置了,则滚动到底部实际上是滚动到顶部 + if (this.useChatRecordMode && checkReverse && !this.isChatRecordModeAndNotInversion) { + this.scrollToTop(animate, false); + return; + } + this.$nextTick(() => { + this._scrollToBottom(animate); + // #ifdef APP-NVUE + if (this.nvueFastScroll && animate) { + u.delay(() => { + this._scrollToBottom(false); + }); + } + // #endif + }) + }, + // 滚动到指定view(vue中有效)。sel为需要滚动的view的id值,不包含"#";offset为偏移量,单位为px;animate为是否展示滚动动画,默认为否 + scrollIntoViewById(sel, offset, animate) { + this._scrollIntoView(sel, offset, animate); + }, + // 滚动到指定view(vue中有效)。nodeTop为需要滚动的view的top值(通过uni.createSelectorQuery()获取);offset为偏移量,单位为px;animate为是否展示滚动动画,默认为否 + scrollIntoViewByNodeTop(nodeTop, offset, animate) { + this.scrollTop = this.oldScrollTop; + this.$nextTick(() => { + this._scrollIntoViewByNodeTop(nodeTop, offset, animate); + }) + }, + // 滚动到指定位置(vue中有效)。y为与顶部的距离,单位为px;offset为偏移量,单位为px;animate为是否展示滚动动画,默认为否 + scrollToY(y, offset, animate) { + this.scrollTop = this.oldScrollTop; + this.$nextTick(() => { + this._scrollToY(y, offset, animate); + }) + }, + // 滚动到指定view(nvue中和虚拟列表中有效)。index为需要滚动的view的index(第几个,从0开始);offset为偏移量,单位为px;animate为是否展示滚动动画,默认为否 + scrollIntoViewByIndex(index, offset, animate) { + if (index >= this.realTotalData.length) { + u.consoleErr('当前滚动的index超出已渲染列表长度,请先通过refreshToPage加载到对应index页并等待渲染成功后再调用此方法!') + return; + } + this.$nextTick(() => { + // #ifdef APP-NVUE + // 在nvue中,根据index获取对应节点信息并滚动到此节点位置 + this._scrollIntoView(index, offset, animate); + // #endif + // #ifndef APP-NVUE + if (this.finalUseVirtualList) { + const isCellFixed = this.cellHeightMode === Enum.CellHeightMode.Fixed; + u.delay(() => { + if (this.finalUseVirtualList) { + // 虚拟列表 + 每个cell高度完全相同模式下,此时滚动到对应index的cell就是滚动到scrollTop = cellHeight * index的位置 + // 虚拟列表 + 高度是动态非固定的模式下,此时滚动到对应index的cell就是滚动到scrollTop = 缓存的cell高度数组中第index个的lastTotalHeight的位置 + const scrollTop = isCellFixed ? this.virtualCellHeight * index : this.virtualHeightCacheList[index].lastTotalHeight; + this.scrollToY(scrollTop, offset, animate); + } + }, isCellFixed ? 0 : 100) + } + // #endif + }) + }, + // 滚动到指定view(nvue中有效)。view为需要滚动的view(通过`this.$refs.xxx`获取),不包含"#";offset为偏移量,单位为px;animate为是否展示滚动动画,默认为否 + scrollIntoViewByView(view, offset, animate) { + this._scrollIntoView(view, offset, animate); + }, + // 当使用页面滚动并且自定义下拉刷新时,请在页面的onPageScroll中调用此方法,告知z-paging当前的pageScrollTop,否则会导致在任意位置都可以下拉刷新 + updatePageScrollTop(value) { + this.pageScrollTop = value; + }, + // 当使用页面滚动并且设置了slot="top"时,默认初次加载会自动获取其高度,并使内部容器下移,当slot="top"的view高度动态改变时,在其高度需要更新时调用此方法 + updatePageScrollTopHeight() { + this._updatePageScrollTopOrBottomHeight('top'); + }, + // 当使用页面滚动并且设置了slot="bottom"时,默认初次加载会自动获取其高度,并使内部容器下移,当slot="bottom"的view高度动态改变时,在其高度需要更新时调用此方法 + updatePageScrollBottomHeight() { + this._updatePageScrollTopOrBottomHeight('bottom'); + }, + // 更新slot="left"和slot="right"宽度,当slot="left"或slot="right"宽度动态改变时调用 + updateLeftAndRightWidth() { + if (!this.finalIsOldWebView) return; + this.$nextTick(() => this._updateLeftAndRightWidth(this.scrollViewContainerStyle, 'zp-page')); + }, + // 更新z-paging内置scroll-view的scrollTop + updateScrollViewScrollTop(scrollTop, animate = true) { + this._updatePrivateScrollWithAnimation(animate); + this.scrollTop = this.oldScrollTop; + this.$nextTick(() => { + this.scrollTop = scrollTop; + this.oldScrollTop = this.scrollTop; + }); + }, + + // 当滚动到顶部时 + _onScrollToUpper() { + this.$emit('scrolltoupper'); + this.$emit('scrollTopChange', 0); + this.$nextTick(() => { + this.oldScrollTop = 0; + }) + }, + // 当滚动到底部时 + _onScrollToLower(e) { + (!e.detail || !e.detail.direction || e.detail.direction === 'bottom') + && this.toBottomLoadingMoreEnabled + && this._onLoadingMore(this.useChatRecordMode ? 'click' : 'toBottom') + }, + // 滚动到顶部 + _scrollToTop(animate = true, isPrivate = true) { + // #ifdef APP-NVUE + // 在nvue中需要通过weex.scrollToElement滚动到顶部,此时在顶部插入了一个view,使得滚动到这个view位置 + const el = this.$refs['zp-n-list-top-tag']; + if (this.usePageScroll) { + this._getNodeClientRect('zp-page-scroll-top', false).then(node => { + const nodeHeight = node ? node[0].height : 0; + weexDom.scrollToElement(el, { + offset: -nodeHeight, + animated: animate + }); + }); + } else { + if (!this.isIos && this.nvueListIs === 'scroller') { + this._getNodeClientRect('zp-n-refresh-container', false).then(node => { + const nodeHeight = node ? node[0].height : 0; + weexDom.scrollToElement(el, { + offset: -nodeHeight, + animated: animate + }); + }); + } else { + weexDom.scrollToElement(el, { + offset: 0, + animated: animate + }); + } + } + return; + // #endif + if (this.usePageScroll) { + this.$nextTick(() => { + uni.pageScrollTo({ + scrollTop: 0, + duration: animate ? 100 : 0, + }); + }); + return; + } + this._updatePrivateScrollWithAnimation(animate); + this.scrollTop = this.oldScrollTop; + this.$nextTick(() => { + this.scrollTop = 0; + this.oldScrollTop = this.scrollTop; + }); + }, + // 滚动到底部 + async _scrollToBottom(animate = true) { + // #ifdef APP-NVUE + // 在nvue中需要通过weex.scrollToElement滚动到顶部,此时在底部插入了一个view,使得滚动到这个view位置 + const el = this.$refs['zp-n-list-bottom-tag']; + if (el) { + weexDom.scrollToElement(el, { + offset: 0, + animated: animate + }); + } else { + u.consoleErr('滚动到底部失败,因为您设置了hideNvueBottomTag为true'); + } + return; + // #endif + if (this.usePageScroll) { + this.$nextTick(() => { + uni.pageScrollTo({ + scrollTop: Number.MAX_VALUE, + duration: animate ? 100 : 0, + }); + }); + return; + } + try { + this._updatePrivateScrollWithAnimation(animate); + const pagingContainerNode = await this._getNodeClientRect('.zp-paging-container'); + const scrollViewNode = await this._getNodeClientRect('.zp-scroll-view'); + const pagingContainerH = pagingContainerNode ? pagingContainerNode[0].height : 0; + const scrollViewH = scrollViewNode ? scrollViewNode[0].height : 0; + if (pagingContainerH > scrollViewH) { + this.scrollTop = this.oldScrollTop; + this.$nextTick(() => { + this.scrollTop = pagingContainerH - scrollViewH + this.virtualPlaceholderTopHeight; + this.oldScrollTop = this.scrollTop; + }); + } + } catch (e) {} + }, + // 滚动到指定view + _scrollIntoView(sel, offset = 0, animate = false, finishCallback) { + try { + this.scrollTop = this.oldScrollTop; + this.$nextTick(() => { + // #ifdef APP-NVUE + const refs = this.$parent.$refs; + if (!refs) return; + const dataType = Object.prototype.toString.call(sel); + let el = null; + if (dataType === '[object Number]') { + const els = refs[`z-paging-${sel}`]; + el = els ? els[0] : null; + } else if (dataType === '[object Array]') { + el = sel[0]; + } else { + el = sel; + } + if (el) { + weexDom.scrollToElement(el, { + offset: -offset, + animated: animate + }); + } else { + u.consoleErr('在nvue中滚动到指定位置,cell必须设置 :ref="`z-paging-${index}`"'); + } + return; + // #endif + this._getNodeClientRect('#' + sel.replace('#', ''), this.$parent).then((node) => { + if (node) { + let nodeTop = node[0].top; + this._scrollIntoViewByNodeTop(nodeTop, offset, animate); + finishCallback && finishCallback(); + } + }); + }); + } catch (e) {} + }, + // 通过nodeTop滚动到指定view + _scrollIntoViewByNodeTop(nodeTop, offset = 0, animate = false) { + // 如果是聊天记录模式并且列表倒置了,此时nodeTop需要等于scroll-view高度 - nodeTop + if (this.isChatRecordModeAndInversion) { + this._getNodeClientRect('.zp-scroll-view').then(sNode => { + if (sNode) { + this._scrollToY(sNode[0].height - nodeTop, offset, animate, true); + } + }) + } else { + this._scrollToY(nodeTop, offset, animate, true); + } + }, + // 滚动到指定位置 + _scrollToY(y, offset = 0, animate = false, addScrollTop = false) { + this._updatePrivateScrollWithAnimation(animate); + u.delay(() => { + if (this.usePageScroll) { + if (addScrollTop && this.pageScrollTop !== -1) { + y += this.pageScrollTop; + } + const scrollTop = y - offset; + uni.pageScrollTo({ + scrollTop, + duration: animate ? 100 : 0 + }); + } else { + if (addScrollTop) { + y += this.oldScrollTop; + } + this.scrollTop = y - offset; + } + }, 10) + }, + // scroll-view滚动中 + _scroll(e) { + this.$emit('scroll', e); + const scrollTop = e.detail.scrollTop; + // #ifndef APP-NVUE + this.finalUseVirtualList && this._updateVirtualScroll(scrollTop, this.oldScrollTop - scrollTop); + // #endif + this.oldScrollTop = scrollTop; + // 滚动区域内容的总高度 - 当前滚动的scrollTop = 当前滚动区域的顶部与内容底部的距离 + const scrollDiff = e.detail.scrollHeight - this.oldScrollTop; + // 在非ios平台滚动中,再次验证一下是否滚动到了底部。因为在一些安卓设备中,有概率滚动到底部不触发@scrolltolower事件,因此添加双重检测逻辑 + !this.isIos && this._checkScrolledToBottom(scrollDiff); + }, + // 更新内置的scroll-view是否启用滚动动画 + _updatePrivateScrollWithAnimation(animate) { + this.privateScrollWithAnimation = animate ? 1 : 0; + u.delay(() => this.$nextTick(() => { + // 在滚动结束后将滚动动画状态设置回初始状态 + this.privateScrollWithAnimation = -1; + }), 100, 'updateScrollWithAnimationDelay') + }, + // 检测scrollView是否要铺满屏幕 + _doCheckScrollViewShouldFullHeight(totalData) { + if (this.autoFullHeight && this.usePageScroll && this.isTotalChangeFromAddData) { + // #ifndef APP-NVUE + this.$nextTick(() => { + this._checkScrollViewShouldFullHeight((scrollViewNode, pagingContainerNode) => { + this._preCheckShowNoMoreInside(totalData, scrollViewNode, pagingContainerNode) + }); + }) + // #endif + // #ifdef APP-NVUE + this._preCheckShowNoMoreInside(totalData) + // #endif + } else { + this._preCheckShowNoMoreInside(totalData) + } + }, + // 检测z-paging是否要全屏覆盖(当使用页面滚动并且不满全屏时,默认z-paging需要铺满全屏,避免数据过少时内部的empty-view无法正确展示) + async _checkScrollViewShouldFullHeight(callback) { + try { + const scrollViewNode = await this._getNodeClientRect('.zp-scroll-view'); + const pagingContainerNode = await this._getNodeClientRect('.zp-paging-container-content'); + if (!scrollViewNode || !pagingContainerNode) return; + const scrollViewHeight = pagingContainerNode[0].height; + const scrollViewTop = scrollViewNode[0].top; + if (this.isAddedData && scrollViewHeight + scrollViewTop <= this.windowHeight) { + this._setAutoHeight(true, scrollViewNode); + callback(scrollViewNode, pagingContainerNode); + } else { + this._setAutoHeight(false); + callback(null, null); + } + } catch (e) { + callback(null, null); + } + }, + // 更新缓存中z-paging整个内容容器高度 + async _updateCachedSuperContentHeight() { + const superContentNode = await this._getNodeClientRect('.z-paging-content'); + if (superContentNode) { + this.superContentHeight = superContentNode[0].height; + } + }, + // scrollTop改变时触发 + _scrollTopChange(newVal, isPageScrollTop){ + this.$emit('scrollTopChange', newVal); + this.$emit('update:scrollTop', newVal); + this._checkShouldShowBackToTop(newVal); + // 之前在安卓中scroll-view有概率滚动到顶部时scrollTop不为0导致下拉刷新判断异常,因此判断scrollTop在105之内都允许下拉刷新,但此方案会导致某些情况(例如滚动到距离顶部10px处)下拉抖动,因此改为通过获取zp-scroll-view的节点信息中的scrollTop进行验证的方案 + // const scrollTop = this.isIos ? (newVal > 5 ? 6 : 0) : (newVal > 105 ? 106 : (newVal > 5 ? 6 : 0)); + const scrollTop = newVal > 5 ? 6 : 0; + if (isPageScrollTop && this.wxsPageScrollTop !== scrollTop) { + this.wxsPageScrollTop = scrollTop; + } else if (!isPageScrollTop && this.wxsScrollTop !== scrollTop) { + this.wxsScrollTop = scrollTop; + if (scrollTop > 6) { + this.scrollEnable = true; + } + } + }, + // 更新使用页面滚动时slot="top"或"bottom"插入view的高度 + _updatePageScrollTopOrBottomHeight(type) { + // #ifndef APP-NVUE + if (!this.usePageScroll) return; + // #endif + this._doCheckScrollViewShouldFullHeight(this.realTotalData); + const node = `.zp-page-${type}`; + const marginText = `margin${type.slice(0,1).toUpperCase() + type.slice(1)}`; + let safeAreaInsetBottomAdd = this.safeAreaInsetBottom; + this.$nextTick(() => { + let delayTime = 0; + // #ifdef MP-BAIDU || APP-NVUE + delayTime = 50; + // #endif + u.delay(() => { + this._getNodeClientRect(node).then((res) => { + if (res) { + let pageScrollNodeHeight = res[0].height; + if (type === 'bottom') { + if (safeAreaInsetBottomAdd) { + pageScrollNodeHeight += this.safeAreaBottom; + } + } else { + this.cacheTopHeight = pageScrollNodeHeight; + } + this.$set(this.scrollViewStyle, marginText, `${pageScrollNodeHeight}px`); + } else if (safeAreaInsetBottomAdd) { + this.$set(this.scrollViewStyle, marginText, `${this.safeAreaBottom}px`); + } + }); + }, delayTime) + }) + }, + } +} diff --git a/uni_modules/z-paging/components/z-paging/js/modules/virtual-list.js b/uni_modules/z-paging/components/z-paging/js/modules/virtual-list.js new file mode 100644 index 0000000..baa8c91 --- /dev/null +++ b/uni_modules/z-paging/components/z-paging/js/modules/virtual-list.js @@ -0,0 +1,516 @@ +// [z-paging]虚拟列表模块 +import u from '.././z-paging-utils' +import c from '.././z-paging-constant' +import Enum from '.././z-paging-enum' + +export default { + props: { + // 是否使用虚拟列表,默认为否 + useVirtualList: { + type: Boolean, + default: u.gc('useVirtualList', false) + }, + // 在使用虚拟列表时,是否使用兼容模式,默认为否 + useCompatibilityMode: { + type: Boolean, + default: u.gc('useCompatibilityMode', false) + }, + // 使用兼容模式时传递的附加数据 + extraData: { + type: Object, + default: u.gc('extraData', {}) + }, + // 是否在z-paging内部循环渲染列表(内置列表),默认为否。若use-virtual-list为true,则此项恒为true + useInnerList: { + type: Boolean, + default: u.gc('useInnerList', false) + }, + // 强制关闭inner-list,默认为false,如果为true将强制关闭innerList,适用于开启了虚拟列表后需要强制关闭inner-list的情况 + forceCloseInnerList: { + type: Boolean, + default: u.gc('forceCloseInnerList', false) + }, + // 内置列表cell的key名称,仅nvue有效,在nvue中开启use-inner-list时必须填此项 + cellKeyName: { + type: String, + default: u.gc('cellKeyName', '') + }, + // innerList样式 + innerListStyle: { + type: Object, + default: u.gc('innerListStyle', {}) + }, + // innerCell样式 + innerCellStyle: { + type: Object, + default: u.gc('innerCellStyle', {}) + }, + // 预加载的列表可视范围(列表高度)页数,默认为12,即预加载当前页及上下各12页的cell。此数值越大,则虚拟列表中加载的dom越多,内存消耗越大(会维持在一个稳定值),但增加预加载页面数量可缓解快速滚动短暂白屏问题 + preloadPage: { + type: [Number, String], + default: u.gc('preloadPage', 12), + validator: (value) => { + if (value <= 0) u.consoleErr('preload-page必须大于0!'); + return value > 0; + } + }, + // 虚拟列表cell高度模式,默认为fixed,也就是每个cell高度完全相同,将以第一个cell高度为准进行计算。可选值【dynamic】,即代表高度是动态非固定的,【dynamic】性能低于【fixed】。 + cellHeightMode: { + type: String, + default: u.gc('cellHeightMode', Enum.CellHeightMode.Fixed) + }, + // 固定的cell高度,cellHeightMode=fixed才有效,若设置了值,则不计算第一个cell高度而使用设置的cell高度 + fixedCellHeight: { + type: [Number, String], + default: u.gc('fixedCellHeight', 0) + }, + // 虚拟列表列数,默认为1。常用于每行有多列的情况,例如每行有2列数据,需要将此值设置为2 + virtualListCol: { + type: [Number, String], + default: u.gc('virtualListCol', 1) + }, + // 虚拟列表scroll取样帧率,默认为80,过低容易出现白屏问题,过高容易出现卡顿问题 + virtualScrollFps: { + type: [Number, String], + default: u.gc('virtualScrollFps', 80) + }, + }, + data() { + return { + virtualListKey: u.getInstanceId(), + virtualPageHeight: 0, + virtualCellHeight: 0, + virtualScrollTimeStamp: 0, + + virtualList: [], + virtualPlaceholderTopHeight: 0, + virtualPlaceholderBottomHeight: 0, + virtualTopRangeIndex: 0, + virtualBottomRangeIndex: 0, + lastVirtualTopRangeIndex: 0, + lastVirtualBottomRangeIndex: 0, + virtualItemInsertedCount: 0, + + virtualHeightCacheList: [], + + getCellHeightRetryCount: { + fixed: 0, + dynamic: 0 + }, + pagingOrgTop: -1, + updateVirtualListFromDataChange: false + } + }, + watch: { + // 监听总数据的改变,刷新虚拟列表布局 + realTotalData() { + this.updateVirtualListRender(); + }, + // 监听虚拟列表渲染数组的改变并emit + virtualList(newVal){ + this.$emit('update:virtualList', newVal); + this.$emit('virtualListChange', newVal); + }, + // 监听虚拟列表顶部占位高度改变并emit + virtualPlaceholderTopHeight(newVal) { + this.$emit('virtualTopHeightChange', newVal); + } + }, + computed: { + virtualCellIndexKey() { + return c.listCellIndexKey; + }, + finalUseVirtualList() { + if (this.useVirtualList && this.usePageScroll){ + u.consoleErr('使用页面滚动时,开启虚拟列表无效!'); + } + return this.useVirtualList && !this.usePageScroll; + }, + finalUseInnerList() { + return this.useInnerList || (this.finalUseVirtualList && !this.forceCloseInnerList); + }, + finalCellKeyName() { + // #ifdef APP-NVUE + if (this.finalUseVirtualList && !this.cellKeyName.length){ + u.consoleErr('在nvue中开启use-virtual-list必须设置cell-key-name,否则将可能导致列表渲染错误!'); + } + // #endif + return this.cellKeyName; + }, + finalVirtualPageHeight(){ + return this.virtualPageHeight > 0 ? this.virtualPageHeight : this.windowHeight; + }, + finalFixedCellHeight() { + return u.convertToPx(this.fixedCellHeight); + }, + virtualRangePageHeight(){ + return this.finalVirtualPageHeight * this.preloadPage; + }, + virtualScrollDisTimeStamp() { + return 1000 / this.virtualScrollFps; + }, + }, + methods: { + // 在使用动态高度虚拟列表时,若在列表数组中需要插入某个item,需要调用此方法;item:需要插入的item,index:插入的cell位置,若index为2,则插入的item在原list的index=1之后,index从0开始 + doInsertVirtualListItem(item, index) { + if (this.cellHeightMode !== Enum.CellHeightMode.Dynamic) return; + this.virtualItemInsertedCount ++; + if (!item || Object.prototype.toString.call(item) !== '[object Object]') { + item = { item }; + } + const cellIndexKey = this.virtualCellIndexKey; + item[cellIndexKey] = `custom-${this.virtualItemInsertedCount}`; + item[c.listCellIndexUniqueKey] = `${this.virtualListKey}-${item[cellIndexKey]}`; + this.$nextTick(async () => { + let retryCount = 0; + while (retryCount <= 10) { + await u.wait(c.delayTime); + + const cellNode = await this._getNodeClientRect(`#zp-id-${item[cellIndexKey]}`, this.finalUseInnerList); + // 如果获取当前cell的节点信息失败,则重试(不超过10次) + if (!cellNode) { + retryCount ++; + continue; + } + + const currentHeight = cellNode ? cellNode[0].height : 0; + const lastHeightCache = this.virtualHeightCacheList[index - 1]; + const lastTotalHeight = lastHeightCache ? lastHeightCache.totalHeight : 0; + // 在缓存的cell高度数组中,插入此cell高度信息 + this.virtualHeightCacheList.splice(index, 0, { + height: currentHeight, + lastTotalHeight, + totalHeight: lastTotalHeight + currentHeight + }); + + // 从当前index起后续的cell缓存高度的lastTotalHeight和totalHeight需要加上当前cell的高度 + for (let i = index + 1; i < this.virtualHeightCacheList.length; i++) { + const thisNode = this.virtualHeightCacheList[i]; + thisNode.lastTotalHeight += currentHeight; + thisNode.totalHeight += currentHeight; + } + + this._updateVirtualScroll(this.oldScrollTop); + break; + } + }) + }, + // 在使用动态高度虚拟列表时,手动更新指定cell的缓存高度(当cell高度在初始化之后再次改变时调用);index:需要更新的cell在列表中的位置,从0开始 + didUpdateVirtualListCell(index) { + if (this.cellHeightMode !== Enum.CellHeightMode.Dynamic) return; + const currentNode = this.virtualHeightCacheList[index]; + this.$nextTick(() => { + this._getNodeClientRect(`#zp-id-${index}`, this.finalUseInnerList).then(cellNode => { + // 更新当前cell的高度 + const cellNodeHeight = cellNode ? cellNode[0].height : 0; + const heightDis = cellNodeHeight - currentNode.height; + currentNode.height = cellNodeHeight; + currentNode.totalHeight = currentNode.lastTotalHeight + cellNodeHeight; + + // 从当前index起后续的cell缓存高度的lastTotalHeight和totalHeight需要加上当前cell变化的高度 + for (let i = index + 1; i < this.virtualHeightCacheList.length; i++) { + const thisNode = this.virtualHeightCacheList[i]; + thisNode.totalHeight += heightDis; + thisNode.lastTotalHeight += heightDis; + } + }); + }) + }, + // 在使用动态高度虚拟列表时,若删除了列表数组中的某个item,需要调用此方法以更新高度缓存数组;index:删除的cell在列表中的位置,从0开始 + didDeleteVirtualListCell(index) { + if (this.cellHeightMode !== Enum.CellHeightMode.Dynamic) return; + const currentNode = this.virtualHeightCacheList[index]; + // 从当前index起后续的cell缓存高度的lastTotalHeight和totalHeight需要减去当前cell的高度 + for (let i = index + 1; i < this.virtualHeightCacheList.length; i++) { + const thisNode = this.virtualHeightCacheList[i]; + thisNode.totalHeight -= currentNode.height; + thisNode.lastTotalHeight -= currentNode.height; + } + // 将当前cell的高度信息从高度缓存数组中删除 + this.virtualHeightCacheList.splice(index, 1); + }, + // 手动触发虚拟列表渲染更新,可用于解决例如修改了虚拟列表数组中元素,但展示未更新的情况 + updateVirtualListRender() { + // #ifndef APP-NVUE + if (this.finalUseVirtualList) { + this.updateVirtualListFromDataChange = true; + this.$nextTick(() => { + this.getCellHeightRetryCount.fixed = 0; + if (this.realTotalData.length) { + this.cellHeightMode === Enum.CellHeightMode.Fixed && this.isFirstPage && this._updateFixedCellHeight() + } else { + this._resetDynamicListState(!this.isUserPullDown); + } + this._updateVirtualScroll(this.oldScrollTop); + }) + } + // #endif + }, + // 初始化虚拟列表 + _virtualListInit() { + this.$nextTick(() => { + u.delay(() => { + // 获取虚拟列表滚动区域的高度 + this._getNodeClientRect('.zp-scroll-view').then(node => { + if (node) { + this.pagingOrgTop = node[0].top; + this.virtualPageHeight = node[0].height; + } + }); + }); + }) + }, + // cellHeightMode为fixed时获取第一个cell高度 + _updateFixedCellHeight() { + if (!this.finalFixedCellHeight) { + this.$nextTick(() => { + u.delay(() => { + this._getNodeClientRect(`#zp-id-${0}`,this.finalUseInnerList).then(cellNode => { + if (!cellNode) { + if (this.getCellHeightRetryCount.fixed > 10) return; + this.getCellHeightRetryCount.fixed ++; + // 如果获取第一个cell的节点信息失败,则重试(不超过10次) + this._updateFixedCellHeight(); + } else { + this.virtualCellHeight = cellNode[0].height; + this._updateVirtualScroll(this.oldScrollTop); + } + }); + }, c.delayTime, 'updateFixedCellHeightDelay'); + }) + } else { + this.virtualCellHeight = this.finalFixedCellHeight; + } + }, + // cellHeightMode为dynamic时获取每个cell高度 + _updateDynamicCellHeight(list, dataFrom = 'bottom') { + const dataFromTop = dataFrom === 'top'; + const heightCacheList = this.virtualHeightCacheList; + const currentCacheList = dataFromTop ? [] : heightCacheList; + let listTotalHeight = 0; + this.$nextTick(() => { + u.delay(async () => { + for (let i = 0; i < list.length; i++) { + const cellNode = await this._getNodeClientRect(`#zp-id-${list[i][this.virtualCellIndexKey]}`, this.finalUseInnerList); + const currentHeight = cellNode ? cellNode[0].height : 0; + if (!cellNode) { + if (this.getCellHeightRetryCount.dynamic <= 10) { + heightCacheList.splice(heightCacheList.length - i, i); + this.getCellHeightRetryCount.dynamic ++; + // 如果获取当前cell的节点信息失败,则重试(不超过10次) + this._updateDynamicCellHeight(list, dataFrom); + } + return; + } + const lastHeightCache = currentCacheList.length ? currentCacheList.slice(-1)[0] : null; + const lastTotalHeight = lastHeightCache ? lastHeightCache.totalHeight : 0; + // 缓存当前cell的高度信息:height-当前cell高度;lastTotalHeight-前面所有cell的高度总和;totalHeight-包含当前cell的所有高度总和 + currentCacheList.push({ + height: currentHeight, + lastTotalHeight, + totalHeight: lastTotalHeight + currentHeight + }); + if (dataFromTop) { + listTotalHeight += currentHeight; + } + } + // 如果数据是从顶部拼接的 + if (dataFromTop && list.length) { + for (let i = 0; i < heightCacheList.length; i++) { + // 更新之前所有项的缓存高度,需要加上此次插入的所有cell高度之和(因为是从顶部插入的cell) + const heightCacheItem = heightCacheList[i]; + heightCacheItem.lastTotalHeight += listTotalHeight; + heightCacheItem.totalHeight += listTotalHeight; + } + this.virtualHeightCacheList = currentCacheList.concat(heightCacheList); + } + this._updateVirtualScroll(this.oldScrollTop); + }, c.delayTime, 'updateDynamicCellHeightDelay') + }) + }, + // 设置cellItem的index + _setCellIndex(list, dataFrom = 'bottom') { + let currentItemIndex = 0; + const cellIndexKey = this.virtualCellIndexKey; + ([Enum.QueryFrom.Refresh, Enum.QueryFrom.Reload].indexOf(this.queryFrom) >= 0) && this._resetDynamicListState(); + if (this.totalData.length) { + if (dataFrom === 'bottom') { + currentItemIndex = this.realTotalData.length; + const lastItem = this.realTotalData.length ? this.realTotalData.slice(-1)[0] : null; + if (lastItem && lastItem[cellIndexKey] !== undefined) { + currentItemIndex = lastItem[cellIndexKey] + 1; + } + } else if (dataFrom === 'top') { + const firstItem = this.realTotalData.length ? this.realTotalData[0] : null; + if (firstItem && firstItem[cellIndexKey] !== undefined) { + currentItemIndex = firstItem[cellIndexKey] - list.length; + } + } + } else { + this._resetDynamicListState(); + } + for (let i = 0; i < list.length; i++) { + let item = list[i]; + if (!item || Object.prototype.toString.call(item) !== '[object Object]') { + item = { item }; + } + if (item[c.listCellIndexUniqueKey]) { + item = u.deepCopy(item); + } + item[cellIndexKey] = currentItemIndex + i; + item[c.listCellIndexUniqueKey] = `${this.virtualListKey}-${item[cellIndexKey]}`; + list[i] = item; + } + this.getCellHeightRetryCount.dynamic = 0; + this.cellHeightMode === Enum.CellHeightMode.Dynamic && this._updateDynamicCellHeight(list, dataFrom); + }, + // 更新scroll滚动(虚拟列表滚动时触发) + _updateVirtualScroll(scrollTop, scrollDiff = 0) { + const currentTimeStamp = u.getTime(); + scrollTop === 0 && this._resetTopRange(); + if (scrollTop !== 0 && this.virtualScrollTimeStamp && currentTimeStamp - this.virtualScrollTimeStamp <= this.virtualScrollDisTimeStamp) { + return; + } + this.virtualScrollTimeStamp = currentTimeStamp; + + let scrollIndex = 0; + const cellHeightMode = this.cellHeightMode; + if (cellHeightMode === Enum.CellHeightMode.Fixed) { + // 如果是固定高度的虚拟列表 + // 计算当前滚动到的cell的index = scrollTop / 虚拟列表cell的固定高度 + scrollIndex = parseInt(scrollTop / this.virtualCellHeight) || 0; + // 更新顶部和底部占位view的高度(为兼容考虑,顶部采用transformY的方式占位) + this._updateFixedTopRangeIndex(scrollIndex); + this._updateFixedBottomRangeIndex(scrollIndex); + } else if(cellHeightMode === Enum.CellHeightMode.Dynamic) { + // 如果是不固定高度的虚拟列表 + // 当前滚动的方向 + const scrollDirection = scrollDiff > 0 ? 'top' : 'bottom'; + // 视图区域的高度 + const rangePageHeight = this.virtualRangePageHeight; + // 顶部视图区域外的高度(顶部不需要渲染而是需要占位部分的高度) + const topRangePageOffset = scrollTop - rangePageHeight; + // 底部视图区域外的高度(底部不需要渲染而是需要占位部分的高度) + const bottomRangePageOffset = scrollTop + this.finalVirtualPageHeight + rangePageHeight; + + let virtualBottomRangeIndex = 0; + let virtualPlaceholderBottomHeight = 0; + let reachedLimitBottom = false; + const heightCacheList = this.virtualHeightCacheList; + const lastHeightCache = !!heightCacheList ? heightCacheList.slice(-1)[0] : null; + + let startTopRangeIndex = this.virtualTopRangeIndex; + // 如果是向底部滚动(顶部占位的高度不断增大,顶部的实际渲染cell数量不断减少) + if (scrollDirection === 'bottom') { + // 从顶部视图边缘的cell的位置开始向后查找 + for (let i = startTopRangeIndex; i < heightCacheList.length; i++){ + const heightCacheItem = heightCacheList[i]; + // 如果查找到某个cell对应的totalHeight大于顶部视图区域外的高度,则此cell为顶部视图边缘的cell + if (heightCacheItem && heightCacheItem.totalHeight > topRangePageOffset) { + // 记录顶部视图边缘cell的index并更新顶部占位区域的高度并停止继续查找 + this.virtualTopRangeIndex = i; + this.virtualPlaceholderTopHeight = heightCacheItem.lastTotalHeight; + break; + } + } + } else { + // 如果是向顶部滚动(顶部占位的高度不断减少,顶部的实际渲染cell数量不断增加) + let topRangeMatched = false; + // 从顶部视图边缘的cell的位置开始向前查找 + for (let i = startTopRangeIndex; i >= 0; i--){ + const heightCacheItem = heightCacheList[i]; + // 如果查找到某个cell对应的totalHeight小于顶部视图区域外的高度,则此cell为顶部视图边缘的cell + if (heightCacheItem && heightCacheItem.totalHeight < topRangePageOffset) { + // 记录顶部视图边缘cell的index并更新顶部占位区域的高度并停止继续查找 + this.virtualTopRangeIndex = i; + this.virtualPlaceholderTopHeight = heightCacheItem.lastTotalHeight; + topRangeMatched = true; + break; + } + } + // 如果查找不到,则认为顶部占位高度为0了,顶部cell不需要继续复用,重置topRangeIndex和placeholderTopHeight + !topRangeMatched && this._resetTopRange(); + } + // 从顶部视图边缘的cell的位置开始向后查找 + for (let i = this.virtualTopRangeIndex; i < heightCacheList.length; i++){ + const heightCacheItem = heightCacheList[i]; + // 如果查找到某个cell对应的totalHeight大于底部视图区域外的高度,则此cell为底部视图边缘的cell + if (heightCacheItem && heightCacheItem.totalHeight > bottomRangePageOffset) { + // 记录底部视图边缘cell的index并更新底部占位区域的高度并停止继续查找 + virtualBottomRangeIndex = i; + virtualPlaceholderBottomHeight = lastHeightCache.totalHeight - heightCacheItem.totalHeight; + reachedLimitBottom = true; + break; + } + } + if (!reachedLimitBottom || this.virtualBottomRangeIndex === 0) { + this.virtualBottomRangeIndex = this.realTotalData.length ? this.realTotalData.length - 1 : this.pageSize; + this.virtualPlaceholderBottomHeight = 0; + } else { + this.virtualBottomRangeIndex = virtualBottomRangeIndex; + this.virtualPlaceholderBottomHeight = virtualPlaceholderBottomHeight; + } + this._updateVirtualList(); + } + }, + // 更新fixedCell模式下topRangeIndex&placeholderTopHeight + _updateFixedTopRangeIndex(scrollIndex) { + let virtualTopRangeIndex = this.virtualCellHeight === 0 ? 0 : scrollIndex - (parseInt(this.finalVirtualPageHeight / this.virtualCellHeight) || 1) * this.preloadPage; + virtualTopRangeIndex *= this.virtualListCol; + virtualTopRangeIndex = Math.max(0, virtualTopRangeIndex); + this.virtualTopRangeIndex = virtualTopRangeIndex; + this.virtualPlaceholderTopHeight = (virtualTopRangeIndex / this.virtualListCol) * this.virtualCellHeight; + }, + // 更新fixedCell模式下bottomRangeIndex&placeholderBottomHeight + _updateFixedBottomRangeIndex(scrollIndex) { + let virtualBottomRangeIndex = this.virtualCellHeight === 0 ? this.pageSize : scrollIndex + (parseInt(this.finalVirtualPageHeight / this.virtualCellHeight) || 1) * (this.preloadPage + 1); + virtualBottomRangeIndex *= this.virtualListCol; + virtualBottomRangeIndex = Math.min(this.realTotalData.length, virtualBottomRangeIndex); + this.virtualBottomRangeIndex = virtualBottomRangeIndex; + this.virtualPlaceholderBottomHeight = (this.realTotalData.length - virtualBottomRangeIndex) * this.virtualCellHeight / this.virtualListCol; + this._updateVirtualList(); + }, + // 更新virtualList + _updateVirtualList() { + const shouldUpdateList = this.updateVirtualListFromDataChange || (this.lastVirtualTopRangeIndex !== this.virtualTopRangeIndex || this.lastVirtualBottomRangeIndex !== this.virtualBottomRangeIndex); + if (shouldUpdateList) { + this.updateVirtualListFromDataChange = false; + this.lastVirtualTopRangeIndex = this.virtualTopRangeIndex; + this.lastVirtualBottomRangeIndex = this.virtualBottomRangeIndex; + this.virtualList = this.realTotalData.slice(this.virtualTopRangeIndex, this.virtualBottomRangeIndex + 1); + } + }, + // 重置动态cell模式下的高度缓存数据、虚拟列表和滚动状态 + _resetDynamicListState(resetVirtualList = false) { + this.virtualHeightCacheList = []; + if (resetVirtualList) { + this.virtualList = []; + } + this.virtualTopRangeIndex = 0; + this.virtualPlaceholderTopHeight = 0; + }, + // 重置topRangeIndex和placeholderTopHeight + _resetTopRange() { + this.virtualTopRangeIndex = 0; + this.virtualPlaceholderTopHeight = 0; + this._updateVirtualList(); + }, + // 检测虚拟列表当前滚动位置,如发现滚动位置不正确则重新计算虚拟列表相关参数(为解决在App中可能出现的长时间进入后台后打开App白屏的问题) + _checkVirtualListScroll() { + if (this.finalUseVirtualList) { + this.$nextTick(() => { + this._getNodeClientRect('.zp-paging-touch-view').then(node => { + const currentTop = node ? node[0].top : 0; + if (!node || (currentTop === this.pagingOrgTop && this.virtualPlaceholderTopHeight !== 0)) { + this._updateVirtualScroll(0); + } + }); + }) + } + }, + // 处理使用内置列表时点击了cell事件 + _innerCellClick(item, index) { + this.$emit('innerCellClick', item, index); + } + } +} diff --git a/uni_modules/z-paging/components/z-paging/js/z-paging-config.js b/uni_modules/z-paging/components/z-paging/js/z-paging-config.js new file mode 100644 index 0000000..c7b714e --- /dev/null +++ b/uni_modules/z-paging/components/z-paging/js/z-paging-config.js @@ -0,0 +1,21 @@ +// [z-paging]处理main.js中的配置信息工具 + +let config = null; +let getedStorage = false; +const storageKey = 'Z-PAGING-CONFIG-STORAGE-KEY' + +function setConfig(value) { + uni.setStorageSync(storageKey, value); +} + +function getConfig() { + if (getedStorage) return config; + config = uni.getStorageSync(storageKey); + getedStorage = true; + return config; +} + +export default { + setConfig, + getConfig +}; diff --git a/uni_modules/z-paging/components/z-paging/js/z-paging-constant.js b/uni_modules/z-paging/components/z-paging/js/z-paging-constant.js new file mode 100644 index 0000000..9c340b7 --- /dev/null +++ b/uni_modules/z-paging/components/z-paging/js/z-paging-constant.js @@ -0,0 +1,19 @@ +// [z-paging]常量 + +export default { + // 当前版本号 + version: '2.7.12', + // 延迟操作的通用时间 + delayTime: 100, + // 请求失败时候全局emit使用的key + errorUpdateKey: 'z-paging-error-emit', + // 全局emit complete的key + completeUpdateKey: 'z-paging-complete-emit', + // z-paging缓存的前缀key + cachePrefixKey: 'z-paging-cache', + + // 虚拟列表中列表index的key + listCellIndexKey: 'zp_index', + // 虚拟列表中列表的唯一key + listCellIndexUniqueKey: 'zp_unique_index' +} diff --git a/uni_modules/z-paging/components/z-paging/js/z-paging-enum.js b/uni_modules/z-paging/components/z-paging/js/z-paging-enum.js new file mode 100644 index 0000000..043d5b9 --- /dev/null +++ b/uni_modules/z-paging/components/z-paging/js/z-paging-enum.js @@ -0,0 +1,45 @@ +// [z-paging]枚举 + +export default { + // 当前加载类型 0.下拉刷新 1.上拉加载更多 + LoadingType: { + Refresher: 0, + LoadingMore: 1 + }, + // 下拉刷新状态 0.默认状态 1.松手立即刷新 2.刷新中 3.刷新结束 4.松手进入二楼 + Refresher: { + Default: 0, + ReleaseToRefresh: 1, + Loading: 2, + Complete: 3, + GoF2: 4 + }, + // 底部加载更多状态 0.默认状态 1.加载中 2.没有更多数据 3.加载失败 + More: { + Default: 0, + Loading: 1, + NoMore: 2, + Fail: 3 + }, + // @query触发来源 0.用户主动下拉刷新 1.通过reload触发 2.通过refresh触发 3.通过滚动到底部加载更多或点击底部加载更多触发 + QueryFrom: { + UserPullDown: 0, + Reload: 1, + Refresh: 2, + LoadingMore: 3 + }, + // 虚拟列表cell高度模式 + CellHeightMode: { + // 固定高度 + Fixed: 'fixed', + // 动态高度 + Dynamic: 'dynamic' + }, + // 列表缓存模式 + CacheMode: { + // 默认模式,只会缓存一次 + Default: 'default', + // 总是缓存,每次列表刷新(下拉刷新、调用reload等)都会更新缓存 + Always: 'always' + } +} \ No newline at end of file diff --git a/uni_modules/z-paging/components/z-paging/js/z-paging-interceptor.js b/uni_modules/z-paging/components/z-paging/js/z-paging-interceptor.js new file mode 100644 index 0000000..147291a --- /dev/null +++ b/uni_modules/z-paging/components/z-paging/js/z-paging-interceptor.js @@ -0,0 +1,97 @@ +// [z-paging]拦截器 + +const queryKey = 'Query'; +const fetchParamsKey = 'FetchParams'; +const fetchResultKey = 'FetchResult'; +const language2LocalKey = 'Language2Local'; + +// 拦截&处理@query事件 +function handleQuery(callback) { + _addHandleByKey(queryKey, callback); + return this; +} + +// 拦截&处理@query事件(私有,请勿调用) +function _handleQuery(pageNo, pageSize, from, lastItem) { + const callback = _getHandleByKey(queryKey); + return callback ? callback(pageNo, pageSize, from, lastItem) : [pageNo, pageSize, from]; +} + +// 拦截&处理:fetch参数 +function handleFetchParams(callback) { + _addHandleByKey(fetchParamsKey, callback); + return this; +} + +// 拦截&处理:fetch参数(私有,请勿调用) +function _handleFetchParams(parmas, extraParams) { + const callback = _getHandleByKey(fetchParamsKey); + return callback ? callback(parmas, extraParams || {}) : { pageNo: parmas.pageNo, pageSize: parmas.pageSize, ...(extraParams || {}) }; +} + +// 拦截&处理:fetch结果 +function handleFetchResult(callback) { + _addHandleByKey(fetchResultKey, callback); + return this; +} + +// 拦截&处理:fetch结果(私有,请勿调用) +function _handleFetchResult(result, paging, params) { + const callback = _getHandleByKey(fetchResultKey); + callback && callback(result, paging, params); + return callback ? true : false; +} + +// 拦截&处理系统language转i18n local +function handleLanguage2Local(callback) { + _addHandleByKey(language2LocalKey, callback); + return this; +} + +// 拦截&处理系统language转i18n local(私有,请勿调用) +function _handleLanguage2Local(language, local) { + const callback = _getHandleByKey(language2LocalKey); + return callback ? callback(language, local) : local; +} + +// 获取当前app对象 +function _getApp(){ + // #ifndef APP-NVUE + return getApp(); + // #endif + // #ifdef APP-NVUE + return getApp({ allowDefault: true }); + // #endif +} + +// 是否可以访问globalData +function _hasGlobalData() { + return _getApp() && _getApp().globalData; +} + +// 添加处理函数 +function _addHandleByKey(key, callback) { + try { + setTimeout(function() { + if (_hasGlobalData()) { + _getApp().globalData[`zp_handle${key}Callback`] = callback; + } + }, 1); + } catch (_) {} +} + +// 获取处理回调函数 +function _getHandleByKey(key) { + return _hasGlobalData() ? _getApp().globalData[`zp_handle${key}Callback`] : null; +} + +export default { + handleQuery, + _handleQuery, + handleFetchParams, + _handleFetchParams, + handleFetchResult, + _handleFetchResult, + handleLanguage2Local, + _handleLanguage2Local +}; diff --git a/uni_modules/z-paging/components/z-paging/js/z-paging-main.js b/uni_modules/z-paging/components/z-paging/js/z-paging-main.js new file mode 100644 index 0000000..11e18de --- /dev/null +++ b/uni_modules/z-paging/components/z-paging/js/z-paging-main.js @@ -0,0 +1,503 @@ +// [z-paging]核心js + +import zStatic from './z-paging-static' +import c from './z-paging-constant' +import u from './z-paging-utils' + +import zPagingRefresh from '../components/z-paging-refresh' +import zPagingLoadMore from '../components/z-paging-load-more' +import zPagingEmptyView from '../../z-paging-empty-view/z-paging-empty-view' + +// modules +import commonLayoutModule from './modules/common-layout' +import dataHandleModule from './modules/data-handle' +import i18nModule from './modules/i18n' +import nvueModule from './modules/nvue' +import emptyModule from './modules/empty' +import refresherModule from './modules/refresher' +import loadMoreModule from './modules/load-more' +import loadingModule from './modules/loading' +import chatRecordModerModule from './modules/chat-record-mode' +import scrollerModule from './modules/scroller' +import backToTopModule from './modules/back-to-top' +import virtualListModule from './modules/virtual-list' + +import Enum from './z-paging-enum' + +const systemInfo = uni.getSystemInfoSync(); + +export default { + name: "z-paging", + components: { + zPagingRefresh, + zPagingLoadMore, + zPagingEmptyView + }, + mixins: [ + commonLayoutModule, + dataHandleModule, + i18nModule, + nvueModule, + emptyModule, + refresherModule, + loadMoreModule, + loadingModule, + chatRecordModerModule, + scrollerModule, + backToTopModule, + virtualListModule + ], + data() { + return { + // --------------静态资源--------------- + base64Arrow: zStatic.base64Arrow, + base64Flower: zStatic.base64Flower, + base64BackToTop: zStatic.base64BackToTop, + + // -------------全局数据相关-------------- + // 当前加载类型 + loadingType: Enum.LoadingType.Refresher, + requestTimeStamp: 0, + wxsPropType: '', + renderPropScrollTop: -1, + checkScrolledToBottomTimeOut: null, + cacheTopHeight: -1, + statusBarHeight: systemInfo.statusBarHeight, + + // --------------状态&判断--------------- + insideOfPaging: -1, + isLoadFailed: false, + isIos: systemInfo.platform === 'ios', + disabledBounce: false, + fromCompleteEmit: false, + disabledCompleteEmit: false, + pageLaunched: false, + active: false, + + // ---------------wxs相关--------------- + wxsIsScrollTopInTopRange: true, + wxsScrollTop: 0, + wxsPageScrollTop: 0, + wxsOnPullingDown: false, + }; + }, + props: { + // 调用complete后延迟处理的时间,单位为毫秒,默认0毫秒,优先级高于minDelay + delay: { + type: [Number, String], + default: u.gc('delay', 0), + }, + // 触发@query后最小延迟处理的时间,单位为毫秒,默认0毫秒,优先级低于delay(假设设置为300毫秒,若分页请求时间小于300毫秒,则在调用complete后延迟[300毫秒-请求时长];若请求时长大于300毫秒,则不延迟),当show-refresher-when-reload为true或reload(true)时,其最小值为400 + minDelay: { + type: [Number, String], + default: u.gc('minDelay', 0), + }, + // 设置z-paging的style,部分平台(如微信小程序)无法直接修改组件的style,可使用此属性代替 + pagingStyle: { + type: Object, + default: u.gc('pagingStyle', {}), + }, + // z-paging的高度,优先级低于pagingStyle中设置的height;传字符串,如100px、100rpx、100% + height: { + type: String, + default: u.gc('height', '') + }, + // z-paging的宽度,优先级低于pagingStyle中设置的width;传字符串,如100px、100rpx、100% + width: { + type: String, + default: u.gc('width', '') + }, + // z-paging的最大宽度,优先级低于pagingStyle中设置的max-width;传字符串,如100px、100rpx、100%。默认为空,也就是铺满窗口宽度,若设置了特定值则会自动添加margin: 0 auto + maxWidth: { + type: String, + default: u.gc('maxWidth', '') + }, + // z-paging的背景色,优先级低于pagingStyle中设置的background。传字符串,如"#ffffff" + bgColor: { + type: String, + default: u.gc('bgColor', '') + }, + // 设置z-paging的容器(插槽的父view)的style + pagingContentStyle: { + type: Object, + default: u.gc('pagingContentStyle', {}), + }, + // z-paging是否自动高度,若自动高度则会自动铺满屏幕 + autoHeight: { + type: Boolean, + default: u.gc('autoHeight', false) + }, + // z-paging是否自动高度时,附加的高度,注意添加单位px或rpx,若需要减少高度,则传负数 + autoHeightAddition: { + type: [Number, String], + default: u.gc('autoHeightAddition', '0px') + }, + // loading(下拉刷新、上拉加载更多)的主题样式,支持black,white,默认black + defaultThemeStyle: { + type: String, + default: u.gc('defaultThemeStyle', 'black') + }, + // z-paging是否使用fixed布局,若使用fixed布局,则z-paging的父view无需固定高度,z-paging高度默认为100%,默认为是(当使用内置scroll-view滚动时有效) + fixed: { + type: Boolean, + default: u.gc('fixed', true) + }, + // 是否开启底部安全区域适配 + safeAreaInsetBottom: { + type: Boolean, + default: u.gc('safeAreaInsetBottom', false) + }, + // 开启底部安全区域适配后,是否使用placeholder形式实现,默认为否。为否时滚动区域会自动避开底部安全区域,也就是所有滚动内容都不会挡住底部安全区域,若设置为是,则滚动时滚动内容会挡住底部安全区域,但是当滚动到底部时才会避开底部安全区域 + useSafeAreaPlaceholder: { + type: Boolean, + default: u.gc('useSafeAreaPlaceholder', false) + }, + // z-paging bottom的背景色,默认透明,传字符串,如"#ffffff" + bottomBgColor: { + type: String, + default: u.gc('bottomBgColor', '') + }, + // slot="top"的view的z-index,默认为99,仅使用页面滚动时有效 + topZIndex: { + type: Number, + default: u.gc('topZIndex', 99) + }, + // z-paging内容容器父view的z-index,默认为1 + superContentZIndex: { + type: Number, + default: u.gc('superContentZIndex', 1) + }, + // z-paging内容容器部分的z-index,默认为1 + contentZIndex: { + type: Number, + default: u.gc('contentZIndex', 1) + }, + // z-paging二楼的z-index,默认为100 + f2ZIndex: { + type: Number, + default: u.gc('f2ZIndex', 100) + }, + // 使用页面滚动时,是否在不满屏时自动填充满屏幕,默认为是 + autoFullHeight: { + type: Boolean, + default: u.gc('autoFullHeight', true) + }, + // 是否监听列表触摸方向改变,默认为否 + watchTouchDirectionChange: { + type: Boolean, + default: u.gc('watchTouchDirectionChange', false) + }, + // z-paging中布局的单位,默认为rpx + unit: { + type: String, + default: u.gc('unit', 'rpx') + } + }, + created() { + // 组件创建时,检测是否开始加载状态 + if (this.createdReload && !this.refresherOnly && this.auto) { + this._startLoading(); + this.$nextTick(this._preReload); + } + }, + mounted() { + this.active = true; + this.wxsPropType = u.getTime().toString(); + this.renderJsIgnore; + if (!this.createdReload && !this.refresherOnly && this.auto) { + // 开始预加载 + u.delay(() => this.$nextTick(this._preReload), 0); + } + // 如果开启了列表缓存,在初始化的时候通过缓存数据填充列表数据 + this.finalUseCache && this._setListByLocalCache(); + let delay = 0; + // #ifdef H5 || MP + delay = c.delayTime; + // #endif + this.$nextTick(() => { + // 初始化systemInfo + this.systemInfo = uni.getSystemInfoSync(); + // 初始化z-paging高度 + !this.usePageScroll && this.autoHeight && this._setAutoHeight(); + this.loaded = true; + u.delay(() => { + // 更新fixed模式下z-paging的布局,主要是更新windowTop、windowBottom + this.updateFixedLayout(); + // 更新缓存中z-paging整个内容容器高度 + this._updateCachedSuperContentHeight(); + }); + }) + // 初始化页面滚动模式下slot="top"、slot="bottom"高度 + this.updatePageScrollTopHeight(); + this.updatePageScrollBottomHeight(); + // 初始化slot="left"、slot="right"宽度 + this.updateLeftAndRightWidth(); + if (this.finalRefresherEnabled && this.useCustomRefresher) { + this.$nextTick(() => { + this.isTouchmoving = true; + }) + } + // 监听uni.$emit中全局emit的complete error等事件 + this._onEmit(); + // #ifdef APP-NVUE + if (!this.isIos && !this.useChatRecordMode) { + this.nLoadingMoreFixedHeight = true; + } + // 在nvue中更新nvue下拉刷新view容器的宽度,而不是写死默认的750rpx,需要考虑列表宽度不是铺满屏幕的情况 + this._nUpdateRefresherWidth(); + // #endif + // #ifndef APP-NVUE + // 虚拟列表模式时,初始化数据 + this.finalUseVirtualList && this._virtualListInit(); + // #endif + // #ifndef APP-PLUS + this.$nextTick(() => { + // 非app平台中,在通过获取css设置的底部安全区域占位view高度设置bottom距离后,更新页面滚动底部高度 + setTimeout(() => { + this._getCssSafeAreaInsetBottom(() => this.safeAreaInsetBottom && this.updatePageScrollBottomHeight()); + }, delay) + }) + // #endif + }, + destroyed() { + this._handleUnmounted(); + }, + // #ifdef VUE3 + unmounted() { + this._handleUnmounted(); + }, + // #endif + watch: { + defaultThemeStyle: { + handler(newVal) { + if (newVal.length) { + this.finalRefresherDefaultStyle = newVal; + } + }, + immediate: true + }, + autoHeight(newVal) { + this.loaded && !this.usePageScroll && this._setAutoHeight(newVal); + }, + autoHeightAddition(newVal) { + this.loaded && !this.usePageScroll && this.autoHeight && this._setAutoHeight(newVal); + }, + }, + computed: { + // 当前z-paging的内置样式 + finalPagingStyle() { + const pagingStyle = { ...this.pagingStyle }; + if (!this.systemInfo) return pagingStyle; + const { windowTop, windowBottom } = this; + if (!this.usePageScroll && this.fixed) { + if (windowTop && !pagingStyle.top) { + pagingStyle.top = windowTop + 'px'; + } + if (windowBottom && !pagingStyle.bottom) { + pagingStyle.bottom = windowBottom + 'px'; + } + } + if (this.bgColor.length && !pagingStyle['background']) { + pagingStyle['background'] = this.bgColor; + } + if (this.height.length && !pagingStyle['height']) { + pagingStyle['height'] = this.height; + } + if (this.width.length && !pagingStyle['width']) { + pagingStyle['width'] = this.width; + } + if (this.maxWidth.length && !pagingStyle['max-width']) { + pagingStyle['max-width'] = this.maxWidth; + pagingStyle['margin'] = '0 auto'; + } + return pagingStyle; + }, + // 当前z-paging内容的样式 + finalPagingContentStyle() { + if (this.contentZIndex != 1) { + this.pagingContentStyle['z-index'] = this.contentZIndex; + this.pagingContentStyle['position'] = 'relative'; + } + return this.pagingContentStyle; + }, + + renderJsIgnore() { + if ((this.usePageScroll && this.useChatRecordMode) || (!this.refresherEnabled && this.scrollable) || !this.useCustomRefresher) { + this.$nextTick(() => { + this.renderPropScrollTop = 10; + }) + } + return 0; + }, + windowHeight() { + if (!this.systemInfo) return 0; + return this.systemInfo.windowHeight || 0; + }, + windowBottom() { + if (!this.systemInfo) return 0; + let windowBottom = this.systemInfo.windowBottom || 0; + // 如果开启底部安全区域适配并且不使用placeholder的形式体现并且不是聊天记录模式(因为聊天记录模式在keyboardHeight计算初已添加了底部安全区域),在windowBottom添加底部安全区域高度 + if (this.safeAreaInsetBottom && !this.useSafeAreaPlaceholder && !this.useChatRecordMode) { + windowBottom += this.safeAreaBottom; + } + return windowBottom; + }, + isIosAndH5() { + // #ifndef H5 + return false; + // #endif + return this.isIos; + } + }, + methods: { + // 当前版本号 + getVersion() { + return `z-paging v${c.version}`; + }, + // 设置nvue List的specialEffects + setSpecialEffects(args) { + this.setListSpecialEffects(args); + }, + // 与setSpecialEffects等效,兼容旧版本 + setListSpecialEffects(args) { + this.nFixFreezing = args && Object.keys(args).length; + if (this.isIos) { + this.privateRefresherEnabled = 0; + } + !this.usePageScroll && this.$refs['zp-n-list'].setSpecialEffects(args); + }, + // #ifdef APP-VUE + // 当app长时间进入后台后进入前台,因系统内存管理导致app重新加载时,进行一些适配处理 + _handlePageLaunch() { + // 首次触发不进行处理,只有进入后台后打开app重新加载时才处理 + if (this.pageLaunched) { + // 解决在vue3+ios中,app ReLaunch时顶部下拉刷新展示位置向下偏移的问题 + // #ifdef VUE3 + this.refresherThresholdUpdateTag = 1; + this.$nextTick(() => { + this.refresherThresholdUpdateTag = 0; + }) + // #endif + // 解决使用虚拟列表时,app ReLaunch时白屏问题 + this._checkVirtualListScroll(); + } + this.pageLaunched = true; + }, + // #endif + // 使手机发生较短时间的振动(15ms) + _doVibrateShort() { + // #ifndef H5 + + // #ifdef APP-PLUS + if (this.isIos) { + const UISelectionFeedbackGenerator = plus.ios.importClass('UISelectionFeedbackGenerator'); + const feedbackGenerator = new UISelectionFeedbackGenerator(); + feedbackGenerator.init(); + setTimeout(() => { + feedbackGenerator.selectionChanged(); + }, 0) + } else { + plus.device.vibrate(15); + } + // #endif + // #ifndef APP-PLUS + uni.vibrateShort(); + // #endif + + // #endif + }, + // 设置z-paging高度 + async _setAutoHeight(shouldFullHeight = true, scrollViewNode = null) { + let heightKey = 'min-height'; + // #ifndef APP-NVUE + heightKey = 'min-height'; + // #endif + try { + if (shouldFullHeight) { + // 如果需要铺满全屏,则计算当前全屏可是区域的高度 + let finalScrollViewNode = scrollViewNode || await this._getNodeClientRect('.zp-scroll-view'); + let finalScrollBottomNode = await this._getNodeClientRect('.zp-page-bottom'); + if (finalScrollViewNode) { + const scrollViewTop = finalScrollViewNode[0].top; + let scrollViewHeight = this.windowHeight - scrollViewTop; + scrollViewHeight -= finalScrollBottomNode ? finalScrollBottomNode[0].height : 0; + const additionHeight = u.convertToPx(this.autoHeightAddition); + const finalHeight = scrollViewHeight + additionHeight - (this.insideMore ? 1 : 0) + 'px !important'; + this.$set(this.scrollViewStyle, heightKey, finalHeight); + this.$set(this.scrollViewInStyle, heightKey, finalHeight); + } + } else { + this.$delete(this.scrollViewStyle, heightKey); + this.$delete(this.scrollViewInStyle, heightKey); + } + } catch (e) {} + }, + // 组件销毁后续处理 + _handleUnmounted() { + this.active = false; + this._offEmit(); + // 取消监听键盘高度变化事件(H5、百度小程序、抖音小程序、飞书小程序、QQ小程序、快手小程序不支持) + // #ifndef H5 || MP-BAIDU || MP-TOUTIAO || MP-QQ || MP-KUAISHOU + this.useChatRecordMode && uni.offKeyboardHeightChange(this._handleKeyboardHeightChange); + // #endif + }, + // 触发更新是否超出页面状态 + _updateInsideOfPaging() { + this.insideMore && this.insideOfPaging === true && setTimeout(this.doLoadMore, 200) + }, + // 清除timeout + _cleanTimeout(timeout) { + if (timeout) { + clearTimeout(timeout); + timeout = null; + } + return timeout; + }, + // 添加全局emit监听 + _onEmit() { + uni.$on(c.errorUpdateKey, (errorMsg) => { + if (this.loading) { + if (!!errorMsg) { + this.customerEmptyViewErrorText = errorMsg; + } + this.complete(false).catch(() => {}); + } + }) + uni.$on(c.completeUpdateKey, (data) => { + setTimeout(() => { + if (this.loading) { + if (!this.disabledCompleteEmit) { + const type = data.type || 'normal'; + const list = data.list || data; + const rule = data.rule; + this.fromCompleteEmit = true; + switch (type){ + case 'normal': + this.complete(list); + break; + case 'total': + this.completeByTotal(list, rule); + break; + case 'nomore': + this.completeByNoMore(list, rule); + break; + case 'key': + this.completeByKey(list, rule); + break; + default: + break; + } + } else { + this.disabledCompleteEmit = false; + } + } + }, 1); + }) + }, + // 销毁全局emit和listener监听 + _offEmit(){ + uni.$off(c.errorUpdateKey); + uni.$off(c.completeUpdateKey); + }, + }, +}; diff --git a/uni_modules/z-paging/components/z-paging/js/z-paging-mixin.js b/uni_modules/z-paging/components/z-paging/js/z-paging-mixin.js new file mode 100644 index 0000000..9d09caf --- /dev/null +++ b/uni_modules/z-paging/components/z-paging/js/z-paging-mixin.js @@ -0,0 +1,22 @@ +// [z-paging]使用页面滚动时引入此mixin,用于监听和处理onPullDownRefresh等页面生命周期方法 + +export default { + onPullDownRefresh() { + if (this.isPagingRefNotFound()) return; + this.$refs.paging.reload().catch(() => {}); + }, + onPageScroll(e) { + if (this.isPagingRefNotFound()) return; + this.$refs.paging.updatePageScrollTop(e.scrollTop); + e.scrollTop < 10 && this.$refs.paging.doChatRecordLoadMore(); + }, + onReachBottom() { + if (this.isPagingRefNotFound()) return; + this.$refs.paging.pageReachBottom(); + }, + methods: { + isPagingRefNotFound() { + return !this.$refs.paging; + } + } +} diff --git a/uni_modules/z-paging/components/z-paging/js/z-paging-static.js b/uni_modules/z-paging/components/z-paging/js/z-paging-static.js new file mode 100644 index 0000000..0b0bd13 --- /dev/null +++ b/uni_modules/z-paging/components/z-paging/js/z-paging-static.js @@ -0,0 +1,13 @@ +// [z-paging]公用的静态图片资源 + +export default { + base64Arrow: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkBAMAAACCzIhnAAAAD1BMVEVHcExRUVFMTExRUVFRUVE9CdWsAAAABHRSTlMAjjrY9ZnUjwAAAQFJREFUWMPt2MsNgzAMgGEEE1B1gKJmAIRYoCH7z9RCXrabh33iYktcIv35EEg5ZBh07pvxJU6MFSPOSRnjnBUjUsaciRUjMsb4xIoRCWNiYsUInzE5sWKEyxiYWDbyefqHx1zIeiYTk7mQYziTYecxHvEJjwmIT3hMQELCYSISEg4TkZj0mYTEpM8kJCU9JiMp6TEZyUmbAUhO2gxAQNJiIAKSFgMRmNQZhMCkziAEJTUGIyipMRjBSZkhCE7KDEFIUmTeGCHJxWz0zXaE0GTCG8ZFtEaS347r/1fe11YyHYVfubxayfjoHmc0YYwmmmiiiSaaaKLJ7ckyz5ve+dw3Xw2emdwm9xSbAAAAAElFTkSuQmCC', + base64ArrowWhite: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkBAMAAACCzIhnAAAAElBMVEVHcEz///////////////////+IGTx/AAAABnRSTlMA/dAkXZOhASU/AAABYElEQVRYw+2YwXLCIBCGsdAHWGbyAKZ4zxi9O017rxLf/1UaWFAgA1m8dcpedNSPf/l/Vh0Ya/Wn6hN0JcGvoCqRM4C8VBFiDwBqqNuJKV0rAnCgy3AUqZE57x0iqTL8Br4U3WBf/YWaIlTKfAcELU/h9w72CSVPa3C3OCDvhpHbRp/s2vq4fHhCeiCl2A3m4Qd71DQR257mFBlMcTlbFnFWzNtHxewYEfSiaLS4el8d8nyhmKJd1CF4eOS0keLMAuSxubLBIeIGQW8YHCFFo7EH9+YDcQt9FMZEswTheaNxTHwHT8SZorJjMrEVwo4Zo0U8HSEyZvJMOg4RjnmmRr8nDYeIz3OMkbfE/QhBo+U9RnZJxjGCRh/WKmHEMWLNkfPKsGh/CWJk1JjG0kcuJggTt34VDP8aWAFhp4nybVb5+9qQhjSkIQ1pSEMa8k+Q5U9rV3dF8MpFBK+/7miVq1/HZ2qmo9D+pAAAAABJRU5ErkJggg==', + base64Flower: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkBAMAAACCzIhnAAAAKlBMVEVHcEzDw8Ovr6+pqamUlJTCwsKenp61tbWxsbGysrLNzc2bm5u5ubmjo6MpovhuAAAACnRSTlMA/P79/sHDhiZS0DxZowAABBBJREFUWMPtl89rE0EUx7ctTXatB3MI1SWnDbUKPUgXqh4ED8Uf7KUVSm3ooVSpSii0Fn/gD4j4o+APiEoVmos9FO2celiqZVgwgaKHPQiCCkv+F99kM7Ozm5kxq1dfD91k9pPve9/3ZjbRNHHok/mKli4eIPNgSuRObuN9SqSEzM20iGnm0yIbqCuV7NSSSIV7uyPM6JMBYdeTOanh/QihJYZsUCSby+VkMj2AvOt0rAeQAwqE3lfKMZVlQCZk1QOCKkkVPadITCfIRNKxfoJI5+0OIFtJx14CMSg1mRSDko7VAfksRQzEbGYqxOJcVTWMCH2I1/IACNW0PWU2M8cmAVHtnH5mM1VRWtwKZjOd5JbF6s1IbaYqaotjNlPHgDAnlAizubTR6ovMYn052g/U5qcmOpi0WL8xTS/3IfSet5m8MEr5ajjF5le6dq/OJpobrdY0t3i9QgefWrxW9/1BLhk0E9m8FeUMhhXal499iD0eQRfDF+ts/tttORRerfp+oV7f4xJj82iUYm1Yzod+ZQEAlS/8mMBwKebVmCVp1f0JLS6zKd17+iwRKTARVg2SHtz3iEbBH+Q+U28zW2Jiza8Tjb1YFoYZMsJyjDqp3M9XBQdSdPLFdxEpvOB37JrHcmR/y9+LgoTlCFGZEa2sc6d4PGlweEa2JSVPoVm+IfGG3ZL037iV9oH+P+Jxc4HGVflNq1M0pivao/EopO4b/ojVCP9GjmiXOeS0DOn1o/iiccT4ORnyvBGF3yUywkQajW4Ti0SGuiy/wVSg/L8w+X/8Q+hvUx8Xd90z4oV5a1i88MbFWHz0WZZ1UrTwBGPX3Rat9AFiXRMRjoMdIdJLEOt2h7jrYOzgOamKZSWSNspOS0X8SAqRYmxRL7sg4eLzYmNehcxh3uoyud/BH2Udux4ywxFTc1xC7Mgf4vMhc5S+kSH3Y7yj+qpwIWSoPTVCOOPVthGx9FbGqrwFw6wSFxJr+17zeKcztt3u+2roAEVgUjDd+AHGuxHy2rZHaa8JMkTHEeyi85ANPO9j9BVuBRD2FY5LDMo/Sz/2hReqGIs/KiFin+CsPsYO/yvM3jL2vE8EbX7/Bf8ejtr2GLN65bioAdgLd8Bis/mD5GmP2qeqyo2ZwQEOtAjRIDH7mBKpUcMoApbZJ5UIxkEwxyMZyMxW/uKFvHCFR3SSmerHyDNQ2dF4JG6zIMpBgLfjSF9x1D6smFcYnGApjmSLICO3ecCDWrQ48geba9DI3STy2i7ax6WIB62fSyIZIiO3GFQqSURp8wCo7GhJBGwuSovJBNjb7kT6FPVnIa9qJ2Ko+l9mefGIdinaMp0yC1URYiwsdfNE45EuA5Cx9EhalfvN5s+UyItm81vaB3p4joniN+SCP7Qc1hblAAAAAElFTkSuQmCC', + base64FlowerWhite: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkBAMAAACCzIhnAAAAElBMVEX///9HcEz///////////////84chYNAAAABnRSTlP/AGzCOYZj5g1nAAACfklEQVRYw+2YTVPDIBCGtza9Jw25a0bvcax30o73OOr//yvma2F3YWlpPTijXNpAHrK8LLALVPFium2vNIFSbwGKTGQA2GUiHcD29yDNy3sMIdUBQl7r2H8mOEVqAHgPkYZUS6Qc2zYhQqtjyDZEximCZwWZLIBeIgYShs2NzxKpSUehYpMJhURGb+O+w5BpMCAREKPnCDHbIY20SzhM5yxziAXpOiBXydrekT9i5XDEq4NIIHHgyU5mRGqviII4mREJJA4QJzMiILwlRJzpKxJKvCBm8OsBBbLux0tsPl4RKYm5aPu6jw1U4mGxEUR9g8M1PcqBEp/WJliNgYOXueBzS4jZSIcgY5lCtevgDSgyzE+rAfuOTQMq0yzvoGH18qju27Mayzs4fPyMziCx81NJa5RNfW7vPYK9KOfDiVkBxFHG8hAj9txuoBuSWORsFfkpBf7xKFLSeaOefEojh5jz22DJEqMP8fUyaKdQx+RnG+yXMpe8Aars8ueR1pVH/bW3FyyvPRw90upLDHwpgBDtg4aUBNkxRLXMAi03IhcZtr1m+FeI/O/JNyDmmL1djLOauSlNflBpW18RQ2bPqXI22MXXEk75KRHTnkPkYbESbdKP2ZFk0r5sIwffAjy1lx+vx7NLjB6/E7Jfv5ERKhzpN0w8IDE8IGFDv5dhz10s7GFiXRZcUeLCEG5P5nDq9k4PFDcoMpE3GY4OuxuCXhmuyNB6k0RsLIAvqp9NE5r8ZCSS8gxnUp7ODdYhZTqxuiJ9uyJJtPmpqJ7wVj+XVieS903iViHziqAhchLEJAyb7jWU647EpUofQ0ziUuXXXhDddtlllSwjgSQu7r4BRWhQqfDPMVwAAAAASUVORK5CYII=', + base64Success: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkBAMAAACCzIhnAAAAElBMVEVRUVFHcExTU1NRUVFRUVFRUVFOSlSUAAAABnRSTlP/AI6+VySB3ZENAAACcElEQVRYw+2YyYKCMAyGI8hdpdxdZu7gcpdZ7jL6/s8yYheSNi0aPdqbwOffpGmaFOYPD3gj4bisN7vddv17N/JVgxn5x12IWgIaWTuO/IE3PseQbwjGPo2cgRmHFLJwdm/X643zwiqOKPPJ1nj3sjEP2iiifZWj5bhopSyGaEO2HX5fbQJzwJ+W7x/jw5ZFjsEU0PMph9xE8i5EqprKALW95eJQURkgzw98uJ/JvwGecR7bIjWWsUgVrrIfFZ2HlLy3sKETD1mmRLRMRhGVssRa0xJkdn3SpJBymBkM8+pSSDXMDNyDaToVHd2fgpNt0sjwiUZO19+jGQ+gQEg9Oq+bufmAVGihomNmjQG7UG3020vrlm7lkFnKFGU3kZ0KGAdmKe821pipQ+qEKcrZeTL2g5FsUks4cStjEZWwXg0b0n4GxmEpkWwIs5VBynjgK7xZaz1/0D7OxkVuLpsY5BQNFyLS84VBjjbg0iL2r2EQHBOxBhikuUOkdxODVF1cxHoWtPPsiyXO455Iv34hssCO8EV4ZIYTjS8SR4qYSHRiTiYQ4ZFbHi0iIhhBTi6dTCgSWRcnw4h4yGTuyTAiOGBIWGoZTgSHJQl+LcOJ4OCnW6yX2bMnJ9pidCOXtkTkTrIGpYuOynAiOF14SamMiOCk5Ke+mq8BcOrrvym8d0zKIQnWT+M1WwOQNO4fFiWb18hhERxJPx2fblbPHHyC41VyiAtKBUFBIih7JMWVoIQTFIr3lKPN80WvoLSWFPC653ioTZA0I0FrQ7qU6asaK0H7JmkSJa2ooOGVtNUsc3j9FYHkIkJy3SG6VHnfXKXGP9t4N9Q4Ye98AAAAAElFTkSuQmCC', + base64SuccessWhite: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkBAMAAACCzIhnAAAAGFBMVEVHcEz///////////////////////////8dS1W+AAAAB3RSTlMAiVYk6KvDHLfaegAAAo1JREFUWMPtWEtzmzAQNhCTq910ytXpiyvxTNOr60zrayepx9d02gnX4sTm7xcEiJX2gdnkGJ1A4tOnfWqXyeR1vMRYzrcPD9v5h5MBl3/Ldvx4cxIg/FWC8X0xjLjalM54uhhCfCrRuJURX0pi3EmIqZV7O59vrRZmguStHL9b7S7ftfLwOtiZDw7AHMtmquAQ12b5Wwbnordm8g9zLLO49qc/m2n6aKnhwPOGZ08hAiNHhheiHae1lOUPGZpQkPKa3q0mOUjaRzSRaGUjpy/mmWSwySSpllcEteBKAT52KEnSbblA51pJEPxBQoiH1FP4E3s5+FJv07h6/ylD6ui7B+9fq/ehrFB98ghec9EoVtyjK8pqCHLmCBOwMWSCeWFNN4MbPAk55NhsvoFHSSVR0k5TCTTEzlUGcqV/nVp7n9oIVkmtaqbAEqEgfdgHJPwsEAyZ9r4VAZXFjpEwyaw3+H2v42KYxKhs1XvY/gSSGv+IHyUSuHXCeZhLAgVI3EjgSGo1Fb3xO0tGGU9S2/KAIbtjxpJASG73qox6w5LUq0cEOa+iIONIWIilQSQ0pPa2jgaRQAgQP7c0mITRWGxpMAmEQFN2NAQJNCV0mI6GIIEO47hlQ0ORQLd0nL+hoUjg1m6I1TRr8uYEAriBHLcVFQ5UEMiBe3XkTBEG04WXlGKGxPnMS305XQPA1Ocn2JiuAZwE66fxnKwBnDTuXxZTMq85lwW6kt5ndLqZPefiU1yvmktcUSooChJF2aMprhQlnKJQ5FxRKkcVRa+itNYU8Io2oVkY14w0NMWYlqft91Bj9VHq+ca3b43BxjWJmla0sfKohlfTVpPN+93L/yLQ/IjQ/O5Q/VR5HdL4D7mlxmjwVdELAAAAAElFTkSuQmCC', + base64Empty: 'data:image/png;base64,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', + base64Error: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJYAAACWCAMAAAAL34HQAAALeGlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNi4wLWMwMDIgNzkuMTY0NDg4LCAyMDIwLzA3LzEwLTIyOjA2OjUzICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0RXZ0PSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VFdmVudCMiIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczpwaG90b3Nob3A9Imh0dHA6Ly9ucy5hZG9iZS5jb20vcGhvdG9zaG9wLzEuMC8iIHhtbG5zOnRpZmY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vdGlmZi8xLjAvIiB4bWxuczpleGlmPSJodHRwOi8vbnMuYWRvYmUuY29tL2V4aWYvMS4wLyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOSAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDIyLTAyLTIyVDIxOjIxOjQ1KzA4OjAwIiB4bXA6TWV0YWRhdGFEYXRlPSIyMDI0LTAxLTEzVDE5OjEwOjEwKzA4OjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAyNC0wMS0xM1QxOToxMDoxMCswODowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MTQ3NTExNjAtZDY5MC00ZTkzLWFhNGUtNGMwYTViNGU1ZGFjIiB4bXBNTTpEb2N1bWVudElEPSJhZG9iZTpkb2NpZDpwaG90b3Nob3A6YzRiNzlkYWMtZTJmYS1iNzQ0LWIxM2ItOWU1N2VjMDhhM2YwIiB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6ZDA4MDI4MDItMzUyYS04NTRhLTkxYjctNmRlNmQ1MmViM2QwIiBwaG90b3Nob3A6Q29sb3JNb2RlPSIzIiBwaG90b3Nob3A6SUNDUHJvZmlsZT0ic1JHQiBJRUM2MTk2Ni0yLjEiIHRpZmY6T3JpZW50YXRpb249IjEiIHRpZmY6WFJlc29sdXRpb249IjMwMDAwMDAvMTAwMDAiIHRpZmY6WVJlc29sdXRpb249IjMwMDAwMDAvMTAwMDAiIHRpZmY6UmVzb2x1dGlvblVuaXQ9IjIiIGV4aWY6Q29sb3JTcGFjZT0iMSIgZXhpZjpQaXhlbFhEaW1lbnNpb249IjMwMCIgZXhpZjpQaXhlbFlEaW1lbnNpb249IjMwMCI+IDx4bXBNTTpIaXN0b3J5PiA8cmRmOlNlcT4gPHJkZjpsaSBzdEV2dDphY3Rpb249ImNyZWF0ZWQiIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6ZDA4MDI4MDItMzUyYS04NTRhLTkxYjctNmRlNmQ1MmViM2QwIiBzdEV2dDp3aGVuPSIyMDIyLTAyLTIyVDIxOjIxOjQ1KzA4OjAwIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOSAoV2luZG93cykiLz4gPHJkZjpsaSBzdEV2dDphY3Rpb249InNhdmVkIiBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOjQwNjg2NzJkLWY5NDMtOTU0Mi1iMDBiLTVlMDExNmE1NmIzZSIgc3RFdnQ6d2hlbj0iMjAyNC0wMS0xM1QxMDoyNjoxNiswODowMCIgc3RFdnQ6c29mdHdhcmVBZ2VudD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTkgKFdpbmRvd3MpIiBzdEV2dDpjaGFuZ2VkPSIvIi8+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJzYXZlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDpjZjk1NTE1OC04MjFiLTA4NDUtYWJmNS05YTE1NGM1ZTY4NjEiIHN0RXZ0OndoZW49IjIwMjQtMDEtMTNUMTE6MDQ6MDQrMDg6MDAiIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFkb2JlIFBob3Rvc2hvcCBDQyAyMDE5IChXaW5kb3dzKSIgc3RFdnQ6Y2hhbmdlZD0iLyIvPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0iY29udmVydGVkIiBzdEV2dDpwYXJhbWV0ZXJzPSJmcm9tIGFwcGxpY2F0aW9uL3ZuZC5hZG9iZS5waG90b3Nob3AgdG8gaW1hZ2UvcG5nIi8+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJkZXJpdmVkIiBzdEV2dDpwYXJhbWV0ZXJzPSJjb252ZXJ0ZWQgZnJvbSBhcHBsaWNhdGlvbi92bmQuYWRvYmUucGhvdG9zaG9wIHRvIGltYWdlL3BuZyIvPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0ic2F2ZWQiIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6ZGM1Y2IyNWItZDZlNC0yZjQ2LTgyODQtZmUwOTNlY2M2ZTkxIiBzdEV2dDp3aGVuPSIyMDI0LTAxLTEzVDExOjA0OjA0KzA4OjAwIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOSAoV2luZG93cykiIHN0RXZ0OmNoYW5nZWQ9Ii8iLz4gPHJkZjpsaSBzdEV2dDphY3Rpb249InNhdmVkIiBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOjE0NzUxMTYwLWQ2OTAtNGU5My1hYTRlLTRjMGE1YjRlNWRhYyIgc3RFdnQ6d2hlbj0iMjAyNC0wMS0xM1QxOToxMDoxMCswODowMCIgc3RFdnQ6c29mdHdhcmVBZ2VudD0iQWRvYmUgUGhvdG9zaG9wIDIyLjAgKE1hY2ludG9zaCkiIHN0RXZ0OmNoYW5nZWQ9Ii8iLz4gPC9yZGY6U2VxPiA8L3htcE1NOkhpc3Rvcnk+IDx4bXBNTTpEZXJpdmVkRnJvbSBz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', + base64BackToTop: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADIBAMAAABfdrOtAAAAElBMVEVRUVH+/v5HcEyZmZlRUVFRUVGm1ByOAAAABnRSTlPMzADMTZAJBBGsAAAEnElEQVR42t2cS27jMAyGf7/2U+QCQeDsbeQCgZDujaC5/1UmkzaJn+JDFGcw3LdfflKibJkkDnxrL7dbg7sNt6+L4O8OYBM+B0ys+QrGkHZG+OEEQ8g6go8Bx1GIGMdpNOQyIG6XdMgnSPtKhLQDGEZFBgYMkhKFtGBb0EIEjDgFRowoBVaMGAWpMedEfxMiZtwpUsgZCqtlkCNUdpVAWigtCCCDFtLwIWeoreZCWiRYYEKGFEjDg+yRZCUH0iLRAgNyToXUNCRZyMqWhGnUN2IPm3wSlwJ7IUspyCBkIQUZhCykIIeQuRTkEDKXAuM9srrtYbrZN7Y98giZSoFd+t1OxmMITG0dcrSFXFchZ1tIvQZpYWxhBbK3hpQrkMEa0iwh5t4a+QvZvDXyF7J5a+Qv5PPW21/I5623v5DPW29/IaO3Xv5Clrw1y1/Ikrdm+Qs5svw83yNnSJ5BQb4F/F7EIEJSnThGBAXxkFQfLOviQUE8JAUPsosHBfGQfDAtHhREQ1JxIV00KIgmrnRI84S0yAd5BAXxxJUck0f6Qnwr9qmr6xF5xLMjcwn/iudIEAdWnyjkEXlQKZiRVzoqRyLbgeUKKR8Q4alY7cSnoxzSf2ggsqehKr6YVpcXpOd7H93f60cKhOd7Re2LteUF4eLqiVS1mr0ge4io6C2+soaFkJ7MuuuQs1yITEp9hwwKISIpzR2iESKSIoT0rLNwuVHQqoSIpAQJpGce60vIUSdEIuUqgPTsJ5QFZK8UIpBS8iG94GFrDjlrhfCl8CG96Llxmle4kEr6vKWBPIVo9kqDQSRk9/3cWoikcCFPAd33v4dIChPyEvLzBA6RlEYWke4JEUnhKXkLeUEKxRHJFfKCQHGucIW8IdZSRkLeEGMpYyEjiK2UsZARxFTKRMgYYillImQMMZQyFTKB2EmZCplAuFLIHT8TMoWwpQwiIVMIUwqpZP5bp5CCvCTiQKr5f5lCQN+tPCBn2ZvVDFJwIDUP0m1BYAfZYRNSsCB7BqTbhoARePIxtZ9tgwWkoJcwCalmv3MBAemtO4R6dah2HaKQqj8Zvp9sQDjvJ21+SPCBHPJDDk6QITekEV7gqCC19CpKAym9IMfckKv4olMBCeIrWwVEfvkshzQekO9r9P1/ALk+IG1eSPCDiCJfyG+FyU+A6ZCa/piZDinpz7LpkCv5gdkAEshP5emQhv7onw6pGeULyZCSUYiRDAmMkpJkCKs4JhFSq8p8hJBSVbAkhARV6ZUQoisik0FqXTmcDHLVFfbJIEFXoiiCNMpiSxGkVJaNiiBBWQArgTTaUl4JpNQWJUsgQVteXQg+AKkLxQWFGKW+5J2+eVp4S168X3CF1CltCKdTJ8lb84YK2bUBO+wZW0Pqv9nk4tKu49N45NJC5dMM5tLW5tOg59Jq6NM06dL+abFXwr/RkuvTXJwae1abtE/Dt0/ruksTvs84AZ/BCC4jHnyGVfiM3VBQFANEXEah+Ax18RlP4zNox2dkkM/wI58xTn8yDCXGYCDV3W5RGSajtXyGhG1jbpbjzpwGt/0MJft8jqC7iUbQ/QZaxdnKqcIftwAAAABJRU5ErkJggg==', +} \ No newline at end of file diff --git a/uni_modules/z-paging/components/z-paging/js/z-paging-utils.js b/uni_modules/z-paging/components/z-paging/js/z-paging-utils.js new file mode 100644 index 0000000..b06c6fe --- /dev/null +++ b/uni_modules/z-paging/components/z-paging/js/z-paging-utils.js @@ -0,0 +1,270 @@ +// [z-paging]工具类 + +import zLocalConfig from '../config/index' +import c from './z-paging-constant' + +const storageKey = 'Z-PAGING-REFRESHER-TIME-STORAGE-KEY'; +let config = null; +let configLoaded = false; +const timeoutMap = {}; + +// 获取默认配置信息 +function gc(key, defaultValue) { + // 这里return一个函数以解决在vue3+appvue中,props默认配置读取在main.js之前执行导致uni.$zp全局配置无效的问题。相当于props的default中传入一个带有返回值的函数 + return () => { + // 处理z-paging全局配置 + _handleDefaultConfig(); + // 如果全局配置不存在,则返回默认值 + if (!config) return defaultValue; + const value = config[key]; + // 如果全局配置存在但对应的配置项不存在,则返回默认值;反之返回配置项 + return value === undefined ? defaultValue : value; + }; +} + +// 获取最终的touch位置 +function getTouch(e) { + let touch = null; + if (e.touches && e.touches.length) { + touch = e.touches[0]; + } else if (e.changedTouches && e.changedTouches.length) { + touch = e.changedTouches[0]; + } else if (e.datail && e.datail != {}) { + touch = e.datail; + } else { + return { touchX: 0, touchY: 0 } + } + return { + touchX: touch.clientX, + touchY: touch.clientY + }; +} + +// 判断当前手势是否在z-paging内触发 +function getTouchFromZPaging(target) { + if (target && target.tagName && target.tagName !== 'BODY' && target.tagName !== 'UNI-PAGE-BODY') { + const classList = target.classList; + if (classList && classList.contains('z-paging-content')) { + // 此处额外记录当前z-paging是否是页面滚动、是否滚动到了顶部、是否是聊天记录模式以传给renderjs。避免不同z-paging组件renderjs内部判断数据互相影响导致的各种问题 + return { + isFromZp: true, + isPageScroll: classList.contains('z-paging-content-page'), + isReachedTop: classList.contains('z-paging-reached-top'), + isUseChatRecordMode: classList.contains('z-paging-use-chat-record-mode') + }; + } else { + return getTouchFromZPaging(target.parentNode); + } + } else { + return { isFromZp: false }; + } +} + +// 递归获取z-paging所在的parent,如果查找不到则返回null +function getParent(parent) { + if (!parent) return null; + if (parent.$refs.paging) return parent; + return getParent(parent.$parent); +} + +// 打印错误信息 +function consoleErr(err) { + console.error(`[z-paging]${err}`); +} + +// 延时操作,如果key存在,调用时清除对应key之前的延时操作 +function delay(callback, ms = c.delayTime, key) { + const timeout = setTimeout(callback, ms);; + if (!!key) { + timeoutMap[key] && clearTimeout(timeoutMap[key]); + timeoutMap[key] = timeout; + } + return timeout; +} + +// 设置下拉刷新时间 +function setRefesrherTime(time, key) { + const datas = getRefesrherTime() || {}; + datas[key] = time; + uni.setStorageSync(storageKey, datas); +} + +// 获取下拉刷新时间 +function getRefesrherTime() { + return uni.getStorageSync(storageKey); +} + +// 通过下拉刷新标识key获取下拉刷新时间 +function getRefesrherTimeByKey(key) { + const datas = getRefesrherTime(); + return datas && datas[key] ? datas[key] : null; +} + +// 通过下拉刷新标识key获取下拉刷新时间(格式化之后) +function getRefesrherFormatTimeByKey(key, textMap) { + const time = getRefesrherTimeByKey(key); + const timeText = time ? _timeFormat(time, textMap) : textMap.none; + return `${textMap.title}${timeText}`; +} + +// 将文本的px或者rpx转为px的值 +function convertToPx(text) { + const dataType = Object.prototype.toString.call(text); + if (dataType === '[object Number]') return text; + let isRpx = false; + if (text.indexOf('rpx') !== -1 || text.indexOf('upx') !== -1) { + text = text.replace('rpx', '').replace('upx', ''); + isRpx = true; + } else if (text.indexOf('px') !== -1) { + text = text.replace('px', ''); + } + if (!isNaN(text)) { + if (isRpx) return Number(uni.upx2px(text)); + return Number(text); + } + return 0; +} + +// 获取当前时间 +function getTime() { + return (new Date()).getTime(); +} + +// 获取z-paging实例id,随机生成10位数字+字母 +function getInstanceId() { + const s = []; + const hexDigits = "0123456789abcdef"; + for (let i = 0; i < 10; i++) { + s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1); + } + return s.join('') + getTime(); +} + +// 等待一段时间 +function wait(ms) { + return new Promise(resolve => { + setTimeout(resolve, ms); + }); +} + +// 是否是promise +function isPromise(func) { + return Object.prototype.toString.call(func) === '[object Promise]'; +} + +// 添加单位 +function addUnit(value, unit) { + if (Object.prototype.toString.call(value) === '[object String]') { + let tempValue = value; + tempValue = tempValue.replace('rpx', '').replace('upx', '').replace('px', ''); + if (value.indexOf('rpx') === -1 && value.indexOf('upx') === -1 && value.indexOf('px') !== -1) { + tempValue = parseFloat(tempValue) * 2; + } + value = tempValue; + } + return unit === 'rpx' ? value + 'rpx' : (value / 2) + 'px'; +} + +// 深拷贝 +function deepCopy(obj) { + if (typeof obj !== 'object' || obj === null) return obj; + let newObj = Array.isArray(obj) ? [] : {}; + for (let key in obj) { + if (obj.hasOwnProperty(key)) { + newObj[key] = deepCopy(obj[key]); + } + } + return newObj; +} + +// ------------------ 私有方法 ------------------------ +// 处理全局配置 +function _handleDefaultConfig() { + // 确保只加载一次全局配置 + if (configLoaded) return; + // 优先从config.js中读取 + if (zLocalConfig && Object.keys(zLocalConfig).length) { + config = zLocalConfig; + } + // 如果在config.js中读取不到,则尝试到uni.$zp读取 + if (!config && uni.$zp) { + config = uni.$zp.config; + } + // 将config中的短横线写法全部转为驼峰写法,使得读取配置时可以直接通过key去匹配,而非读取每个配置时候再去转,减少不必要的性能开支 + config = config ? Object.keys(config).reduce((result, key) => { + result[_toCamelCase(key)] = config[key]; + return result; + }, {}) : null; + configLoaded = true; +} + +// 时间格式化 +function _timeFormat(time, textMap) { + const date = new Date(time); + const currentDate = new Date(); + // 设置time对应的天,去除时分秒,使得可以直接比较日期 + const dateDay = new Date(time).setHours(0, 0, 0, 0); + // 设置当前的天,去除时分秒,使得可以直接比较日期 + const currentDateDay = new Date().setHours(0, 0, 0, 0); + const disTime = dateDay - currentDateDay; + let dayStr = ''; + const timeStr = _dateTimeFormat(date); + if (disTime === 0) { + dayStr = textMap.today; + } else if (disTime === -86400000) { + dayStr = textMap.yesterday; + } else { + dayStr = _dateDayFormat(date, date.getFullYear() !== currentDate.getFullYear()); + } + return `${dayStr} ${timeStr}`; +} + +// date格式化为年月日 +function _dateDayFormat(date, showYear = true) { + const year = date.getFullYear(); + const month = date.getMonth() + 1; + const day = date.getDate(); + return showYear ? `${year}-${_fullZeroToTwo(month)}-${_fullZeroToTwo(day)}` : `${_fullZeroToTwo(month)}-${_fullZeroToTwo(day)}`; +} + +// data格式化为时分 +function _dateTimeFormat(date) { + const hour = date.getHours(); + const minute = date.getMinutes(); + return `${_fullZeroToTwo(hour)}:${_fullZeroToTwo(minute)}`; +} + +// 不满2位在前面填充0 +function _fullZeroToTwo(str) { + str = str.toString(); + return str.length === 1 ? '0' + str : str; +} + +// 驼峰转短横线 +function _toKebab(value) { + return value.replace(/([A-Z])/g, "-$1").toLowerCase(); +} + +// 短横线转驼峰 +function _toCamelCase(value) { + return value.replace(/-([a-z])/g, (_, group1) => group1.toUpperCase()); +} + + +export default { + gc, + setRefesrherTime, + getRefesrherFormatTimeByKey, + getTouch, + getTouchFromZPaging, + getParent, + convertToPx, + getTime, + getInstanceId, + consoleErr, + delay, + wait, + isPromise, + addUnit, + deepCopy +}; diff --git a/uni_modules/z-paging/components/z-paging/wxs/z-paging-renderjs.js b/uni_modules/z-paging/components/z-paging/wxs/z-paging-renderjs.js new file mode 100644 index 0000000..359f1ac --- /dev/null +++ b/uni_modules/z-paging/components/z-paging/wxs/z-paging-renderjs.js @@ -0,0 +1,67 @@ +// [z-paging]使用renderjs在app-vue和h5中对touchmove事件冒泡进行处理 + +import u from '../js/z-paging-utils' +const data = { + startY: 0, + isTouchFromZPaging: false, + isUsePageScroll: false, + isReachedTop: true, + isIosAndH5: false, + useChatRecordMode: false, + appLaunched: false +} + +export default { + mounted() { + if (window) { + this._handleTouch(); + // #ifdef APP-VUE + this.$ownerInstance.callMethod('_handlePageLaunch'); + // #endif + } + }, + methods: { + // 接收逻辑层发送的数据(是否是ios+h5) + renderPropIsIosAndH5Change(newVal) { + if (newVal === -1) return; + data.isIosAndH5 = newVal; + }, + + // 拦截处理touch事件 + _handleTouch() { + if (!window.$zPagingRenderJsInited) { + window.$zPagingRenderJsInited = true; + window.addEventListener('touchstart', this._handleTouchstart, { passive: true }) + window.addEventListener('touchmove', this._handleTouchmove, { passive: false }) + } + }, + // 处理touch开始 + _handleTouchstart(e) { + const touch = u.getTouch(e); + data.startY = touch.touchY; + const touchResult = u.getTouchFromZPaging(e.target); + data.isTouchFromZPaging = touchResult.isFromZp; + data.isUsePageScroll = touchResult.isPageScroll; + data.isReachedTop = touchResult.isReachedTop; + data.useChatRecordMode = touchResult.isUseChatRecordMode; + }, + // 处理touch中 + _handleTouchmove(e) { + const touch = u.getTouch(e); + const moveY = touch.touchY - data.startY; + // 如果是在z-paging内触摸并且(是在顶部位置且是下拉的情况下(或不是聊天记录滚动模式并且在iOS+h5+scroll-view并且是往上拉的情况:避免在此平台中滚动到底部后上拉有个系统灰色遮罩导致列表被短暂锁定的问题)) + // (data.useChatRecordMode ? moveY < 0 : moveY > 0)是为了判断是否是上拉的情况,聊天记录模式列表倒置,因此moveY < 0为上拉 + if (data.isTouchFromZPaging && ((data.isReachedTop && (data.useChatRecordMode ? moveY < 0 : moveY > 0)) || (!data.useChatRecordMode && data.isIosAndH5 && !data.isUsePageScroll && moveY < 0))) { + if (e.cancelable && !e.defaultPrevented) { + // 阻止事件冒泡,以避免在一些平台中下拉刷新时整个page跟着一起下拉&在iOS+h5+scroll-view中在底部上拉有个系统灰色遮罩导致列表被短暂锁定的问题 + e.preventDefault(); + } + } + }, + // 移除touch相关事件监听 + _removeAllEventListener(){ + window.removeEventListener('touchstart'); + window.removeEventListener('touchmove'); + } + } +}; diff --git a/uni_modules/z-paging/components/z-paging/wxs/z-paging-wxs.wxs b/uni_modules/z-paging/components/z-paging/wxs/z-paging-wxs.wxs new file mode 100644 index 0000000..ed21959 --- /dev/null +++ b/uni_modules/z-paging/components/z-paging/wxs/z-paging-wxs.wxs @@ -0,0 +1,382 @@ +// [z-paging]微信小程序、QQ小程序、app-vue、h5上使用wxs实现自定义下拉刷新,降低逻辑层与视图层的通信折损,提升性能 + +var currentDis = 0; +var isPCFlag = -1; +var startY = -1; + +// 监听js层传过来的数据 +function propObserver(newVal, oldVal, ownerIns, ins) { + var state = ownerIns.getState() || {}; + state.currentIns = ins; + var dataset = ins.getDataset(); + var loading = dataset.loading == true; + // 如果是下拉刷新结束,更新transform + if (newVal && newVal.indexOf('end') != -1) { + var transition = newVal.split('end')[0]; + _setTransform('translateY(0px)', ins, false, transition); + state.moveDis = 0; + state.oldMoveDis = 0; + currentDis = 0; + } else if (newVal && newVal.indexOf('begin') != -1) { + // 如果是下拉刷新开始,更新transform + var refresherThreshold = ins.getDataset().refresherthreshold; + _setTransformValue(refresherThreshold, ins, state, false); + } +} + +// touch开始 +function touchstart(e, ownerIns) { + var ins = _getIns(ownerIns); + var state = {}; + var dataset = {}; + ownerIns.callMethod('_handleListTouchstart'); + if (ins) { + state = ins.getState(); + dataset = ins.getDataset(); + if (_touchDisabled(e, ins, 0)) return; + } + var isTouchEnded = state.isTouchEnded; + state.oldMoveDis = 0; + var touch = _getTouch(e); + var loading = _isTrue(dataset.loading); + state.startY = touch.touchY; + startY = state.startY; + state.lastTouch = touch; + if (!loading && isTouchEnded) { + state.isTouchmoving = false; + } + state.isTouchEnded = false; + // 通知js层touch开始 + ownerIns.callMethod('_handleRefresherTouchstart', touch); +} + +// touch中 +function touchmove(e, ownerIns) { + var touch = _getTouch(e); + var ins = _getIns(ownerIns); + var dataset = ins.getDataset(); + var refresherThreshold = dataset.refresherthreshold; + var refresherF2Threshold = dataset.refresherf2threshold; + var refresherF2Enabled = _isTrue(dataset.refresherf2enabled); + var isIos = _isTrue(dataset.isios); + var state = ins.getState(); + var watchTouchDirectionChange = _isTrue(dataset.watchtouchdirectionchange); + var moveDisObj = {}; + var moveDis = 0; + var prevent = false; + // 如果需要监听touch方向的改变 + if (watchTouchDirectionChange) { + moveDisObj = _getMoveDis(e, ins); + moveDis = moveDisObj.currentDis; + prevent = moveDisObj.isDown; + var direction = prevent ? 'top' : 'bottom'; + // 确保只在touch方向改变时通知一次js层,而不是touchmove中持续通知 + if (prevent == state.oldTouchDirection && prevent != state.oldEmitedTouchDirection) { + ownerIns.callMethod('_handleTouchDirectionChange', { direction: direction }); + state.oldEmitedTouchDirection = prevent; + } + state.oldTouchDirection = prevent; + } + // 判断是否允许下拉刷新 + if (_touchDisabled(e, ins, 1)) { + _handlePullingDown(state, ownerIns, false); + return true; + } + // 判断下拉刷新的角度是否在要求范围内 + if (!_getAngleIsInRange(e, touch, state, dataset)) { + _handlePullingDown(state, ownerIns, false); + return true; + } + moveDisObj = _getMoveDis(e, ins); + moveDis = moveDisObj.currentDis; + prevent = moveDisObj.isDown; + if (moveDis < 0) { + // moveDis小于0,将transform重置为0 + _setTransformValue(0, ins, state, false); + _handlePullingDown(state, ownerIns, false); + return true; + } + if (prevent && !state.disabledBounce) { + // 如果是用户下拉并且需要触发下拉刷新,需要通知js层将列表禁止滚动,防止在下拉刷新过程中列表也可以滚动导致的下拉刷新偏移过大的问题(在下拉刷新过程中仅通知一次) + ownerIns.callMethod('_handleScrollViewBounce', { bounce: false }); + state.disabledBounce = true; + _handlePullingDown(state, ownerIns, prevent); + return !prevent; + } + // 更新transform + _setTransformValue(moveDis, ins, state, false); + var oldRefresherStatus = state.refresherStatus; + var oldIsTouchmoving = _isTrue(dataset.oldistouchmoving); + var hasTouchmove = _isTrue(dataset.hastouchmove); + var isTouchmoving = state.isTouchmoving; + state.refresherStatus = moveDis >= refresherThreshold ? (refresherF2Enabled && moveDis > refresherF2Threshold ? 'goF2' : 'releaseToRefresh') : 'default'; + if (!isTouchmoving) { + state.isTouchmoving = true; + isTouchmoving = true; + } + if (state.isTouchEnded) { + state.isTouchEnded = false; + } + // 如果需要实时监听下拉位置偏移,则需要实时通知js层,此操作会使wxs层与js层频繁通信从而导致在一些性能较差设备中下拉刷新卡顿 + if (hasTouchmove) { + ownerIns.callMethod('_handleWxsPullingDown', { moveDis: moveDis, diffDis: moveDisObj.diffDis }); + } + // 在下拉刷新状态改变时通知js层 + if (oldRefresherStatus == undefined || oldRefresherStatus != state.refresherStatus || oldIsTouchmoving != isTouchmoving) { + ownerIns.callMethod('_handleRefresherTouchmove', moveDis, touch); + } + _handlePullingDown(state, ownerIns, prevent); + return !prevent; +} + +// touch结束 +function touchend(e, ownerIns) { + var touch = _getTouch(e); + var ins = _getIns(ownerIns); + var dataset = ins.getDataset(); + var state = ins.getState(); + if (state.disabledBounce) { + // 通知js允许列表滚动 + ownerIns.callMethod('_handleScrollViewBounce', { bounce: true }); + state.disabledBounce = false; + } + if (_touchDisabled(e, ins, 2)) return; + state.reachMaxAngle = true; + state.hitReachMaxAngleCount = 0; + state.fixedIsTopHitCount = 0; + if (!state.isTouchmoving) return; + var oldRefresherStatus = state.refresherStatus; + var oldMoveDis = state.moveDis; + var refresherThreshold = ins.getDataset().refresherthreshold; + var moveDis = _getMoveDis(e, ins).currentDis; + if (!(moveDis >= refresherThreshold && oldRefresherStatus === 'releaseToRefresh')) { + state.isTouchmoving = false; + } + // 通知js层touch结束 + ownerIns.callMethod('_handleRefresherTouchend', moveDis); + state.isTouchEnded = true; + if (oldMoveDis < refresherThreshold) return; + var animate = false; + if (moveDis >= refresherThreshold) { + moveDis = refresherThreshold; + animate = true; + } + _setTransformValue(moveDis, ins, state, animate); +} + +// #ifdef H5 +// 判断是否是pc平台 +function isPC() { + if (!navigator) return false; + if (isPCFlag != -1) return isPCFlag; + var agents = ["Android", "iPhone", "SymbianOS", "Windows Phone", "iPad", "iPod"]; + isPCFlag = agents.every(function(item) { return navigator.userAgent.indexOf(item) < 0 }); + return isPCFlag; +} + +var movable = false; + +// 在pc平台监听mousedown、mousemove、mouseup等相关事件并转为对应touch事件处理,使得在pc平台也支持通过鼠标进行下拉刷新 + +function mousedown(e, ins) { + if (!isPC()) return; + touchstart(e, ins); + movable = true; +} + +function mousemove(e, ins) { + if (!isPC() || !movable) return; + touchmove(e, ins); +} + +function mouseup(e, ins) { + if (!isPC()) return; + touchend(e, ins); + movable = false; +} + +function mouseleave(e, ins) { + if (!isPC()) return; + movable = false; +} +// #endif + + +// 修改视图层transform +function _setTransformValue(value, ins, state, animate) { + value = value || 0; + if (state.moveDis == value) return; + state.moveDis = value; + _setTransform('translateY(' + value + 'px)', ins, animate, ''); +} + +// 设置视图层transform,直接在视图层操作下拉刷新,使得js层不需要频繁和视图层通信,从而大大提升下拉刷新性能 +function _setTransform(transform, ins, animate, transition) { + var dataset = ins.getDataset(); + if (_isTrue(dataset.refreshernotransform)) return; + transform = transform == 'translateY(0px)' ? 'none' : transform; + ins.requestAnimationFrame(function() { + var stl = { 'transform': transform }; + if (animate) { + stl['transition'] = 'transform .1s linear'; + } + if (transition.length) { + stl['transition'] = transition; + } + ins.setStyle(stl); + }) +} + +// 进一步处理下拉刷新的偏移数据 +function _getMoveDis(e, ins) { + var state = ins.getState(); + var refresherThreshold = parseFloat(ins.getDataset().refresherthreshold); + var refresherOutRate = parseFloat(ins.getDataset().refresheroutrate); + var refresherPullRate = parseFloat(ins.getDataset().refresherpullrate); + var touch = _getTouch(e); + var currentStartY = !state.startY || state.startY == 'NaN' ? startY : state.startY; + var moveDis = touch.touchY - currentStartY; + var oldMoveDis = state.oldMoveDis || 0; + state.oldMoveDis = moveDis; + // 获取当前下拉刷新位置与上次的偏移量 + var diffDis = moveDis - oldMoveDis; + if (diffDis > 0) { + // 对偏移量进行进一步处理,通过refresherPullRate等配置进行约束 + diffDis = diffDis * refresherPullRate; + if (currentDis > refresherThreshold) { + diffDis = diffDis * (1 - refresherOutRate); + } + } + // 控制diffDis过大的情况,比如进入页面突然猛然下拉,此时diffDis不应进行太大的偏移 + diffDis = diffDis > 100 ? diffDis / 100 : (diffDis > 20 ? diffDis / 2.2 : diffDis); + currentDis += diffDis; + currentDis = Math.max(0, currentDis); + return { + currentDis: currentDis, + diffDis: diffDis, + isDown: diffDis > 0 + }; +} + +// 获取经过统一格式包装的当前touch对象 +function _getTouch(e) { + var touch = e; + if (e.touches && e.touches.length) { + touch = e.touches[0]; + } else if (e.changedTouches && e.changedTouches.length) { + touch = e.changedTouches[0]; + } else if (e.datail && e.datail != {}) { + touch = e.datail; + } + return { + touchX: touch.clientX, + touchY: touch.clientY + }; +} + +// 获取当前currentIns +function _getIns(ownerIns) { + var ins = ownerIns.getState().currentIns; + if (!ins) { + ownerIns.callMethod('_handlePropUpdate'); + } + return ins; +} + +// 判断当前状态是否允许下拉刷新 +function _touchDisabled(e, ins, processTag) { + var dataset = ins.getDataset(); + var state = ins.getState(); + var loading = _isTrue(dataset.loading); + var useChatRecordMode = _isTrue(dataset.usechatrecordmode); + var refresherEnabled = _isTrue(dataset.refresherenabled); + var useCustomRefresher = _isTrue(dataset.usecustomrefresher); + var usePageScroll = _isTrue(dataset.usepagescroll); + var pageScrollTop = parseFloat(dataset.pagescrolltop); + var scrollTop = parseFloat(dataset.scrolltop); + var finalScrollTop = usePageScroll ? pageScrollTop : scrollTop; + var fixedIsTop = false; + // 是否要处理滚动到顶部scrollTop不为0时候的容错,为解决在安卓中scroll-view有概率滚动到顶部时scrollTop不为0导致下拉刷新判断异常,但此方案会导致某些情况(例如滚动到距离顶部10px处)下拉抖动,因此改为通过获取zp-scroll-view的节点信息中的scrollTop进行验证的方案 + var handleFaultTolerantMove = false; + if (handleFaultTolerantMove && finalScrollTop == (state.startScrollTop || 0) && finalScrollTop <= 105) { + fixedIsTop = true; + } + var fixedIsTopHitCount = state.fixedIsTopHitCount || 0; + if (fixedIsTop) { + fixedIsTopHitCount ++; + if (fixedIsTopHitCount <= 2) { + fixedIsTop = false; + } + state.fixedIsTopHitCount = fixedIsTopHitCount; + } else { + state.fixedIsTopHitCount = 0; + } + if (handleFaultTolerantMove && processTag === 0) { + state.startScrollTop = finalScrollTop || 0; + } + if (handleFaultTolerantMove && processTag === 2) { + fixedIsTop = true; + } + return loading || useChatRecordMode || !refresherEnabled || !useCustomRefresher || + ((usePageScroll && useCustomRefresher && pageScrollTop > 5) && !fixedIsTop) || + ((!usePageScroll && useCustomRefresher && scrollTop > 5) && !fixedIsTop); +} + +// 判断下拉刷新的角度是否在要求范围内 +function _getAngleIsInRange(e, touch, state, dataset) { + var maxAngle = dataset.refreshermaxangle; + var refresherAecc = _isTrue(dataset.refresheraecc); + var lastTouch = state.lastTouch; + var reachMaxAngle = state.reachMaxAngle; + var moveDis = state.oldMoveDis; + if (!lastTouch) return true; + if (maxAngle >= 0 && maxAngle <= 90 && lastTouch) { + // 考虑下拉刷新手势由水平移动转为垂直方向移动的情况,此时不应当只判断垂直方向角度是否符合要求,应当直接禁止以避免在swiper中使用下拉刷新时,横向切换swiper途中手未离开屏幕还可以下拉刷新的问题 + if ((!moveDis || moveDis < 1) && !refresherAecc && reachMaxAngle != null && !reachMaxAngle) return false; + var x = Math.abs(touch.touchX - lastTouch.touchX); + var y = Math.abs(touch.touchY - lastTouch.touchY); + var z = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)); + if ((x || y) && x > 1) { + // 获取下拉刷新前后两次位移的角度 + var angle = Math.asin(y / z) / Math.PI * 180; + if (angle < maxAngle) { + // 如果角度小于配置要求,则return,同时通过hitReachMaxAngleCount控制角度判断的灵敏程度以最大程度兼容各种使用场景 + var hitReachMaxAngleCount = state.hitReachMaxAngleCount || 0; + state.hitReachMaxAngleCount = ++hitReachMaxAngleCount; + if (state.hitReachMaxAngleCount > 2) { + state.lastTouch = touch; + state.reachMaxAngle = false; + } + return false; + } + } + } + state.lastTouch = touch; + return true; +} + +// 进一步处理是否在下拉刷新并通知js层 +function _handlePullingDown(state, ins, onPullingDown) { + var oldOnPullingDown = state.onPullingDown || false; + if (oldOnPullingDown != onPullingDown) { + ins.callMethod('_handleWxsPullingDownStatusChange', onPullingDown); + } + state.onPullingDown = onPullingDown; +} + +// 判断js层传过来的值是否为true +function _isTrue(value) { + value = (typeof(value) === 'string' ? JSON.parse(value) : value) || false; + return value == true || value == 'true'; +} + +module.exports = { + touchstart: touchstart, + touchmove: touchmove, + touchend: touchend, + mousedown: mousedown, + mousemove: mousemove, + mouseup: mouseup, + mouseleave: mouseleave, + propObserver: propObserver +} diff --git a/uni_modules/z-paging/components/z-paging/z-paging.vue b/uni_modules/z-paging/components/z-paging/z-paging.vue new file mode 100644 index 0000000..1f088d3 --- /dev/null +++ b/uni_modules/z-paging/components/z-paging/z-paging.vue @@ -0,0 +1,345 @@ + + + + + + + + + + + + + + diff --git a/uni_modules/zb-table/components/zb-table/components/table-h5-summary.vue b/uni_modules/zb-table/components/zb-table/components/table-h5-summary.vue new file mode 100644 index 0000000..73734b2 --- /dev/null +++ b/uni_modules/zb-table/components/zb-table/components/table-h5-summary.vue @@ -0,0 +1,78 @@ + + + diff --git a/uni_modules/zb-table/components/zb-table/components/table-side-summary.vue b/uni_modules/zb-table/components/zb-table/components/table-side-summary.vue new file mode 100644 index 0000000..fa27c93 --- /dev/null +++ b/uni_modules/zb-table/components/zb-table/components/table-side-summary.vue @@ -0,0 +1,59 @@ + + + diff --git a/uni_modules/zb-table/components/zb-table/components/table-summary.vue b/uni_modules/zb-table/components/zb-table/components/table-summary.vue new file mode 100644 index 0000000..2850740 --- /dev/null +++ b/uni_modules/zb-table/components/zb-table/components/table-summary.vue @@ -0,0 +1,77 @@ + + + diff --git a/uni_modules/zb-table/components/zb-table/components/zb-load-more.vue b/uni_modules/zb-table/components/zb-table/components/zb-load-more.vue new file mode 100644 index 0000000..5a7da41 --- /dev/null +++ b/uni_modules/zb-table/components/zb-table/components/zb-load-more.vue @@ -0,0 +1,50 @@ + + + diff --git a/uni_modules/zb-table/components/zb-table/js/summary.js b/uni_modules/zb-table/components/zb-table/js/summary.js new file mode 100644 index 0000000..fbd81f1 --- /dev/null +++ b/uni_modules/zb-table/components/zb-table/js/summary.js @@ -0,0 +1,88 @@ +export default { + props:{ + scrollbarSize:{ + type:Number, + default:0 + }, + fixedLeftColumns:{ + type:Array, + default:()=>[] + }, + data:{ + type:Array, + default:()=>[] + }, + transColumns:{ + type:Array, + default:()=>[] + }, + border:{ + type:Boolean, + default:false + }, + showSummary:{ + type:Boolean, + default:false + }, + summaryMethod:{ + type:Function + }, + sumText:{ + type:String, + default:'合计' + }, + headerFooterTableLeft:{ + type:Number, + default:0 + }, + handleFooterTableScrollLeft:Function, + }, + data(){ + return{ + sums:[] + } + }, + watch:{ + 'data':{ + deep:true, + immediate:true, + handler(newValue,oldValue){ + let sums = []; + if (this.summaryMethod) { + sums = this.summaryMethod({ columns: this.transColumns, data: this.data }); + } else { + this.transColumns.forEach((column, index) => { + if (index === 0) { + sums[index] = this.sumText; + return; + } + const values = this.data.map(item => Number(item[column.name])); + const precisions = []; + let notNumber = true; + values.forEach(value => { + if (!isNaN(value)) { + notNumber = false; + let decimal = ('' + value).split('.')[1]; + precisions.push(decimal ? decimal.length : 0); + } + }); + const precision = Math.max.apply(null, precisions); + if (!notNumber) { + sums[index] = values.reduce((prev, curr) => { + const value = Number(curr); + if (!isNaN(value)) { + return parseFloat((prev + curr).toFixed(Math.min(precision, 20))); + } else { + return prev; + } + }, 0); + } else { + sums[index] = ''; + } + }); + } + this.sums = sums + }, + } + } +} diff --git a/uni_modules/zb-table/components/zb-table/js/util.js b/uni_modules/zb-table/components/zb-table/js/util.js new file mode 100644 index 0000000..e682cb2 --- /dev/null +++ b/uni_modules/zb-table/components/zb-table/js/util.js @@ -0,0 +1,51 @@ +/** + * 获取滚动条宽度 + */ +let cached = undefined; + +export const getScrollbarSize = fresh => { + // #ifdef H5 + + if (fresh || cached === undefined) { + let inner = document.createElement("div"); + let innerStyle = inner.style; + + innerStyle.width = "100%"; + innerStyle.height = "200px"; + + let outer = document.createElement("div"); + let outerStyle = outer.style; + + outerStyle.position = "absolute"; + outerStyle.top = 0; + outerStyle.left = 0; + outerStyle.pointerEvents = "none"; + outerStyle.width = "200px"; + outerStyle.height = "150px"; + outerStyle.visibility = "hidden"; + + outer.appendChild(inner); + document.body.appendChild(outer); + + // 设置子元素超出部分隐藏 + outerStyle.overflow = "hidden"; + + let width1 = inner.offsetWidth; + + // 设置子元素超出部分滚动 + outer.style.overflow = "scroll"; + + let width2 = inner.offsetWidth; + + if (width1 === width2) { + width2 = outer.clientWidth; + } + + document.body.removeChild(outer); + + cached = width1 - width2; + } + //#endif + + return cached; +}; diff --git a/uni_modules/zb-table/components/zb-table/zb-table.vue b/uni_modules/zb-table/components/zb-table/zb-table.vue new file mode 100644 index 0000000..e3b334e --- /dev/null +++ b/uni_modules/zb-table/components/zb-table/zb-table.vue @@ -0,0 +1,1342 @@ + + + + diff --git a/uni_modules/zb-table/package.json b/uni_modules/zb-table/package.json new file mode 100644 index 0000000..4bf4a39 --- /dev/null +++ b/uni_modules/zb-table/package.json @@ -0,0 +1,81 @@ +{ + "id": "zb-table", + "displayName": "zb-table(多功能表格)", + "version": "1.2.18", + "description": "表格组件 支持固定表头和首列、上拉加载更多、及固定多列,表格自适应内容,排序,多选checkbox、可点击删除,编辑、合计功能,兼容多端", + "keywords": [ + "table", + "表格", + "固定表头、固定首列、多列", + "上拉加载更多、", + "排序、自适应列宽、多选checkbox、编辑、删除、按钮、合计" +], + "repository": "https://github.com/zouzhibin/zb-table.git", + "engines": { + "HBuilderX": "^3.1.0" + }, +"dcloudext": { + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "", + "type": "component-vue" + }, + "uni_modules": { + "dependencies": [], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "Vue": { + "vue2": "y", + "vue3": "y" + }, + "App": { + "app-vue": "y", + "app-nvue": "u" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "y", + "联盟": "y" + } + } + } + } +} \ No newline at end of file diff --git a/uni_modules/zb-table/readme.md b/uni_modules/zb-table/readme.md new file mode 100644 index 0000000..e3d402f --- /dev/null +++ b/uni_modules/zb-table/readme.md @@ -0,0 +1,189 @@ + +## 介绍 +基于uni-app开发的一个普通的表格组件,功能有固定首列和表头、排序、操作按钮、 +table 表格 固定表头、固定首列、多列 上拉加载更多、 排序、自适应列宽、多选checkbox、编辑、删除、按钮、合计 +已用于生产环境 + +微信=》 19550102670 拉进群 +## -- github 第一时间会更新到github,永远保持最新,有啥想法的可以提PR,共同开发 [地址](https://github.com/zouzhibin/zb-ui) + +## 友情链接 +### 在线预览点击 —— [企业级、通用型中后台前端解决方案 ](https://yuanzbz.gitee.io/vue-admin-perfect/#/login?redirect=/home) +### vue-admin-perfect —— [企业级、通用型中后台前端解决方案(基于vue3.0+TS+Element-Plus 最新版,同时支持电脑,手机,平板)](https://github.com/zouzhibin/vue-admin-perfect) + + +## table 属性 +| 参数 | 说明 | 类型 | 可选值 | 默认值 |是否必须| +| ------ | ------ | ------ | ------ | ------ |------ | +| data | 显示的数据 | array |-- | -- |必须 | +| column | 显示的列数据 | array |-- | -- |必须 | +| stripe | 是否为斑马纹 table| boolean | - |false | 否 | +| fit | 列的宽度是否自撑开 | boolean |true,false | false |否 | +| show-header | 是否显示表头 | boolean |true,false | true |否 | +| cell-style | 单元格的 style 的回调方法,也可以使用一个固定的 Object 为所有单元格设置一样的 Style。 | Function({row, column, rowIndex, columnIndex})/Object |-- | -- |否 | +| cell-header-style | 头部单元格的 style 的回调方法,也可以使用一个固定的 Object 为所有单元格设置一样的 Style。 | Function({ column, columnIndex})/Object |-- | -- |否 | +| formatter | colomn =》formatter 必须设置为true,才有作用,进行格式化数据,进行数据的转换 | Function({row, column, rowIndex, columnIndex})/Object |-- | -- |否 | +| border | 是否带有纵向边框 | boolean |true,false | true |否 | +| highlight | 是否要高亮当前行 | boolean |true,false | false |否 | +| show-summary | 是否在表尾显示合计行 | boolean |true,false | false |否 | +| sum-text | 合计行第一列的文本 | String |- | 合计 |否 | +| summary-method | 自定义的合计计算方法 | Function({ columns, data }) |- | - |否 | +| permissionBtn | 是否动态控制按钮的显示隐藏 | Function({ row, renders,index }) |- | - |否 | +| isShowLoadMore | 是否开启上拉加载 | boolean |true,false | false |否 | +| pullUpLoading | 开启上拉加载后的返回函数,接收参数done是函数,done(type),type为空代表还有数据,继续开启上拉加载,type='ok',代表结束上拉加载 | Function(done) |-- | -- |否 | + +``` +关闭上拉加载的方式1:pullUpLoading((done)=>{ + done(type) +}) +done 接收参数为 type ,type为空代表还有数据,可以继续加载,无数据的时候传入 'ok'代表结束 +``` + +## table 事件 +| 参数 | 说明 | 类型 | 可选值 | 默认值 |是否必须| +| ------ | ------ | ------ |--------------------------| ------ |------ | +| 事件名自定义 | 取决于type类型为operation的 renders参数里面 func 的参数名 | Function | (row,index)=>{} | -- |否 | +| sort-change | 取决于type类型为operation的 renders参数里面 func 的参数名 | Function | (column,model,index)=>{} | -- |否 | +| currentChange | 当表格的当前行发生变化的时候会触发该事件,如果要高亮当前行,请打开表格的 highlight属性,this.$refs.table.resetHighlight()清除选中 | Function | (row,index)=>{} | -- |否 | +| toggleRowSelection | 用于多选表格,切换某一行的选中状态,第一个参数代表选中状态,参数二代表选中的对象 | Function | (selected ,array)=>{} | -- |否 | +| toggleAllSelection | 用于多选表格,切换所有行的选中状态 ,第一个参数代表选中状态,参数二代表选中的对象| Function | (selected ,array)=>{} | -- |否 | +| rowClick | 单击某行 ,第一个参数代表选中对象,参数二代表选中的index| Function | (row ,index)=>{} | -- |否 | +| cellClick | 单击单元格 ,当某个单元格被点击时会触发该事件| Function | (row ,index,column)=>{} | -- |否 | +| pullUpLoading | 开启上拉加载后的返回函数,无参数| Function | -- |-- |否 | + +``` +关闭上拉加载的方式2:this.$refs.zbTable.pullUpCompleteLoading('ok') +接收参数为 type ,type为空代表还有数据,可以继续加载,无数据的时候传入 'ok'代表结束 +``` + +## data 属性 +| 参数 | 说明 | 类型 | 可选值 | 默认值 | +| ------ | ------ | ------ | ------ | ------ | +| checked | 是否被勾选 | boolean |true,false | 无 | + + +## column 属性 +| 参数 | 说明 | 类型 | 可选值 | 默认值 | +| ------ | ------ | ------ | ------ | ------ | +| name | 属性值 | string |-- | 无 | +| label | 显示的标题 | string |-- | 无 | +| width | 列的宽度 | number |-- | 100 | +| fixed | 列是否固定在左侧,true 表示固定在左侧 | boolean |true,false | true | +| sorter | 排序,当设置为custom的时候代表自定义排序,不会再触发默认排序,会触发table事件@sort-change,可以通过接口来进行排序 | boolean |true,false,'custom' | false | +| emptyString | 当值为空的时候默认显示的值 | string | | -- | +| filters | 对象过滤的选项,对象格式,对象中的元素需要有 key 和 value 属性。 | Object | {key:value} | -- | +| align | 对齐方式 | String | left/center/right | left | +| type | 为 operation 的时候代表为操作按钮,img的时候代表图片地址,index代表序列号 | string | operation,img,index | -- | +| renders | type 为operation的时候 必传 | Array | {name:'名字',func:"父元素接收事件名",type:"按钮的类型",size:"大小"} | -- | +``` +type 为 operation 的时候代表为操作按钮 +renders 代表传入的按钮 Array =>[ + { + name:'编辑', + class:"", // 添加class + type:'primary',代表按钮的类型 type 为custom的时候自定义按钮 其他类型取决于uniapp buttom组件按钮 + size:'mini',代表按钮的大小 + func:'edit' // func 代表操作按钮点击的事件名字 父元素接收的事件 父元素 @edit + 例如:// + + } +] +``` +## 示例 +``` + +``` + +## 数据格式 +``` +column:[ + { type:'selection', fixed:true,width:50 }, + { name: 'name', label: '姓名',fixed:false,width:80,emptyString:'--' }, + { name: 'age', label: '年纪',sorter:false,align:'right', }, + { name: 'sex', label: '性别',filters:{0:'男',1:'女'}}, + { name: 'img', label: '图片',type:"img" }, + { name: 'address', label: '地址' }, + { name: 'date', label: '日期',sorter:true }, + { name: 'province', label: '省份' }, + { name: 'city', label: '城市' }, + { name: 'zip', label: '邮编' }, + { name: 'operation', type:'operation',label: '操作',renders:[ + { + name:'编辑', + func:'edit' // func 代表子元素点击的事件 父元素接收的事件 父元素 @edit + }, + { + name:'删除', + type:'warn', + func:"dele" + }, + ]}, + ], + data:[ + { + date: '2016-05-02', + name: '王小虎1', + province: '上海', + sex:'男', + age:'18', + img:"https://img1.baidu.com/it/u=300787145,1214060415&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=500", + city: '普陀区', + address: '上海市普', + zip: 200333 + }, + { + date: '2016-05-02', + name: '王小虎2', + province: '上海', + sex:'男', + age:'18', + city: '普陀区', + address: '上海市普', + zip: 200333 + }, + { + date: '2016-05-02', + name: '王小虎3', + province: '上海', + sex:'男', + age:'18', + city: '普陀区', + address: '上海市普', + zip: 200333 + }, + { + date: '2016-05-02', + name: '王小虎4', + province: '上海', + sex:'男', + age:'18', + city: '普陀区', + address: '上海市普', + zip: 200333 + }, + { + date: '2016-05-02', + name: '王小虎5', + province: '上海', + sex:'男', + age:'18', + city: '普陀区', + address: '上海市普', + zip: 200333 + } + ] +``` diff --git a/uni_modules/zero-loading/changelog.md b/uni_modules/zero-loading/changelog.md new file mode 100644 index 0000000..e6befbb --- /dev/null +++ b/uni_modules/zero-loading/changelog.md @@ -0,0 +1,21 @@ +## 1.3.2(2023-10-31) +修改遮罩默认透明度为0.1 +## 1.3.1(2023-10-31) +## 新增支持,自定义动画颜色(仅部分动画支持) +## 新增动画-annulus(圆环) +## 1.3.0(2023-08-11) +支持vue3使用, 增加动画类型radar(雷达) +## 1.2.2(2023-06-12) +增加 maskOpacity, maskMini, maskDark自定义参数, 提供更丰富的自定义遮罩层能力 +## 1.2.1(2022-09-09) +增加齿轮动画 type=gear +## 1.2.0(2022-05-27) +1. 增加加载类型-剑气(sword),原子(atom) +2. 默认类型改为 atom +3. 遮罩透明度调整 +## 1.1.1(2022-04-02) +更新使用说明 +## 1.1.0(2022-02-23) +增加 type="love" 的心形加载动画 +## 1.0.0(2022-01-28) +首次发布 diff --git a/uni_modules/zero-loading/components/zero-loading/static/loading-annulus.vue b/uni_modules/zero-loading/components/zero-loading/static/loading-annulus.vue new file mode 100644 index 0000000..bbdd9c0 --- /dev/null +++ b/uni_modules/zero-loading/components/zero-loading/static/loading-annulus.vue @@ -0,0 +1,52 @@ + + + + + \ No newline at end of file diff --git a/uni_modules/zero-loading/components/zero-loading/static/loading-atom.vue b/uni_modules/zero-loading/components/zero-loading/static/loading-atom.vue new file mode 100644 index 0000000..26c995b --- /dev/null +++ b/uni_modules/zero-loading/components/zero-loading/static/loading-atom.vue @@ -0,0 +1,115 @@ + + + + + diff --git a/uni_modules/zero-loading/components/zero-loading/static/loading-bounce.vue b/uni_modules/zero-loading/components/zero-loading/static/loading-bounce.vue new file mode 100644 index 0000000..ab3ea28 --- /dev/null +++ b/uni_modules/zero-loading/components/zero-loading/static/loading-bounce.vue @@ -0,0 +1,80 @@ + + + + + diff --git a/uni_modules/zero-loading/components/zero-loading/static/loading-circle.vue b/uni_modules/zero-loading/components/zero-loading/static/loading-circle.vue new file mode 100644 index 0000000..b9264b1 --- /dev/null +++ b/uni_modules/zero-loading/components/zero-loading/static/loading-circle.vue @@ -0,0 +1,90 @@ + + + + + diff --git a/uni_modules/zero-loading/components/zero-loading/static/loading-eyes.vue b/uni_modules/zero-loading/components/zero-loading/static/loading-eyes.vue new file mode 100644 index 0000000..df30a8e --- /dev/null +++ b/uni_modules/zero-loading/components/zero-loading/static/loading-eyes.vue @@ -0,0 +1,80 @@ + + + + + diff --git a/uni_modules/zero-loading/components/zero-loading/static/loading-gear.vue b/uni_modules/zero-loading/components/zero-loading/static/loading-gear.vue new file mode 100644 index 0000000..ffa33d6 --- /dev/null +++ b/uni_modules/zero-loading/components/zero-loading/static/loading-gear.vue @@ -0,0 +1,126 @@ + + + + + diff --git a/uni_modules/zero-loading/components/zero-loading/static/loading-love.vue b/uni_modules/zero-loading/components/zero-loading/static/loading-love.vue new file mode 100644 index 0000000..8635ad5 --- /dev/null +++ b/uni_modules/zero-loading/components/zero-loading/static/loading-love.vue @@ -0,0 +1,210 @@ + + + + + diff --git a/uni_modules/zero-loading/components/zero-loading/static/loading-pulse.vue b/uni_modules/zero-loading/components/zero-loading/static/loading-pulse.vue new file mode 100644 index 0000000..a3804a7 --- /dev/null +++ b/uni_modules/zero-loading/components/zero-loading/static/loading-pulse.vue @@ -0,0 +1,64 @@ + + + + + diff --git a/uni_modules/zero-loading/components/zero-loading/static/loading-radar.vue b/uni_modules/zero-loading/components/zero-loading/static/loading-radar.vue new file mode 100644 index 0000000..8ec9bcd --- /dev/null +++ b/uni_modules/zero-loading/components/zero-loading/static/loading-radar.vue @@ -0,0 +1,137 @@ + + + + + \ No newline at end of file diff --git a/uni_modules/zero-loading/components/zero-loading/static/loading-sun.vue b/uni_modules/zero-loading/components/zero-loading/static/loading-sun.vue new file mode 100644 index 0000000..1b13507 --- /dev/null +++ b/uni_modules/zero-loading/components/zero-loading/static/loading-sun.vue @@ -0,0 +1,148 @@ + + + + + diff --git a/uni_modules/zero-loading/components/zero-loading/static/loading-sword.vue b/uni_modules/zero-loading/components/zero-loading/static/loading-sword.vue new file mode 100644 index 0000000..2433549 --- /dev/null +++ b/uni_modules/zero-loading/components/zero-loading/static/loading-sword.vue @@ -0,0 +1,87 @@ + + + + + diff --git a/uni_modules/zero-loading/components/zero-loading/static/loading-triangle.vue b/uni_modules/zero-loading/components/zero-loading/static/loading-triangle.vue new file mode 100644 index 0000000..89236a7 --- /dev/null +++ b/uni_modules/zero-loading/components/zero-loading/static/loading-triangle.vue @@ -0,0 +1,86 @@ + + + + + diff --git a/uni_modules/zero-loading/components/zero-loading/zero-loading.vue b/uni_modules/zero-loading/components/zero-loading/zero-loading.vue new file mode 100644 index 0000000..4f6c98a --- /dev/null +++ b/uni_modules/zero-loading/components/zero-loading/zero-loading.vue @@ -0,0 +1,134 @@ + + + + + \ No newline at end of file diff --git a/uni_modules/zero-loading/package.json b/uni_modules/zero-loading/package.json new file mode 100644 index 0000000..c0bc0c1 --- /dev/null +++ b/uni_modules/zero-loading/package.json @@ -0,0 +1,80 @@ +{ + "id": "zero-loading", + "displayName": "zero-loading(加载动画)", + "version": "1.3.2", + "description": "纯css加载动画, 一个标签元素即可实现炫酷的全屏loading效果,支持vue2,vue3", + "keywords": [ + "loading", + "加载动画", + "css动画", + "加载" +], + "repository": "", + "engines": { + "HBuilderX": "^3.1.0" + }, +"dcloudext": { + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "插件不采集任何数据", + "permissions": "无" + }, + "npmurl": "", + "type": "component-vue" + }, + "uni_modules": { + "dependencies": [], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "Vue": { + "vue2": "y", + "vue3": "y" + }, + "App": { + "app-vue": "u", + "app-nvue": "u" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "u", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "u", + "百度": "u", + "字节跳动": "u", + "QQ": "u" + }, + "快应用": { + "华为": "u", + "联盟": "u" + } + } + } + } +} \ No newline at end of file diff --git a/uni_modules/zero-loading/readme.md b/uni_modules/zero-loading/readme.md new file mode 100644 index 0000000..4163b82 --- /dev/null +++ b/uni_modules/zero-loading/readme.md @@ -0,0 +1,60 @@ +# zero-loading + +> 仅测试于 vue2, vue3, 微信小程序. 其他平台自行测试 + +## 使用方法 + +导入 `uni_modules` 后直接使用即可 + +提供多种加载动画类型,传入 type 改变 loading 样式,不传默认 circle + +### 全屏使用 + +```html + +``` + +### 局部使用 + +**父元素的 `position` 记得改为 `relative` 不然可能影响效果** + +```html + +``` + +## 参数说明 + +| 参数 | 类型 | 默认值| 描述 | +| -------- | ------- | ------| ------------ | +| type | String | atom | 样式 | +| position | String | fixed | 定位方式 | +| zIndex | Number | 9 | | +| mask | Boolean | false | 是否需要遮罩 (默认为全屏遮罩,背景色默认为黑色)| +| maskOpacity | Number | 0.1 | 遮罩透明度 | +| maskMini | Boolean | false | 传入true时,使用小遮罩 | +| maskDark | Boolean | true | 传入false时,遮罩背景色为白色 | +|color| String | #0396FF | 自定义颜色,仅部分支持 | + +### type 可选值: + +| type 值 | 描述 | +| -------- | ---- | +|annulus | 圆环 (自定义颜色) | +| radar | 雷达 | +| gear | 齿轮 | +| sword | 剑气 (自定义颜色) | +| atom | 原子 (自定义颜色) | +| circle | 圆圈 | +| love | 爱心 | +| pulse | 脉冲 | +| sun | 太阳 | +| eyes | 眼睛 | +| triangle | 三角 | +| bounce | 弹跳 | + +插件预览: +![code](https://img.zerojs.cn/mweb/we_code.jpg) + +> 小程序搜索: zerojs零技术 + +> 预览的小程序不一定能及时更新当前插件